java項(xiàng)目實(shí)現(xiàn)統(tǒng)一打印入?yún)⒊鰠⒌热罩?/h1>
更新時(shí)間:2023年03月31日 09:52:58 作者:Yuhei001
這篇文章主要介紹了java項(xiàng)目實(shí)現(xiàn)統(tǒng)一打印入?yún)⒊鰠⒌热罩痉绞剑哂泻芎玫膮⒖純r(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
1.背景
SpringBoot項(xiàng)目中,之前都是在controller方法的第一行手動(dòng)打印 log,return之前再打印返回值。有多個(gè)返回點(diǎn)時(shí),就需要出現(xiàn)多少重復(fù)代碼,過多的非業(yè)務(wù)代碼顯得十分凌亂。
本文將采用AOP 配置自定義注解實(shí)現(xiàn) 入?yún)ⅰ⒊鰠⒌娜罩敬蛴。ǚ椒ǖ娜雲(yún)⒑头祷刂刀疾捎?fastjson 序列化)。
2.設(shè)計(jì)思路
將特定包下所有的controller生成代理類對象,并交由Spring容器管理,并重寫invoke方法進(jìn)行增強(qiáng)(入?yún)?、出參的打?.
3.核心代碼
3.1 自定義注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({InteractRecordBeanPostProcessor.class})
public @interface EnableInteractRecord {
? ? /**
? ? ?* app對應(yīng)controller包名
? ? ?*/
? ? String[] basePackages() default {};
? ? /**
? ? ?* 排除某些包
? ? ?*/
? ? String[] exclusions() default {};
}
3.2 實(shí)現(xiàn)BeanFactoryPostProcessor接口
作用:獲取EnableInteractRecord注解對象,用于獲取需要?jiǎng)?chuàng)建代理對象的包名,以及需要排除的包名
@Component
public class InteractRecordFactoryPostProcessor implements BeanFactoryPostProcessor {
? ? private static Logger logger = LoggerFactory.getLogger(InteractRecordFactoryPostProcessor.class);
? ? private EnableInteractRecord enableInteractRecord;
? ? @Override
? ? public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
? ? ? ? try {
? ? ? ? ? ? String[] names = beanFactory.getBeanNamesForAnnotation(EnableInteractRecord.class);
? ? ? ? ? ? for (String name : names) {
? ? ? ? ? ? ? ? enableInteractRecord = beanFactory.findAnnotationOnBean(name, EnableInteractRecord.class);
? ? ? ? ? ? ? ? logger.info("開啟交互記錄 ", enableInteractRecord);
? ? ? ? ? ? }
? ? ? ? } catch (Exception e) {
? ? ? ? ? ? logger.error("postProcessBeanFactory() Exception ", e);
? ? ? ? }
? ? }
? ? public EnableInteractRecord getEnableInteractRecord() {
? ? ? ? return enableInteractRecord;
? ? }
}
3.3 實(shí)現(xiàn)MethodInterceptor編寫打印日志邏輯
作用:進(jìn)行入?yún)?、出參打印,包含是否打印邏?/p>
@Component
public class ControllerMethodInterceptor implements MethodInterceptor {
? ? private static Logger logger = LoggerFactory.getLogger(ControllerMethodInterceptor.class);
? ? // 請求開始時(shí)間
? ? ThreadLocal<Long> startTime = new ThreadLocal<>();
? ? private String localIp = "";
? ? @PostConstruct
? ? public void init() {
? ? ? ? try {
? ? ? ? ? ? localIp = InetAddress.getLocalHost().getHostAddress();
? ? ? ? } catch (UnknownHostException e) {
? ? ? ? ? ? logger.error("本地IP初始化失敗 : ", e);
? ? ? ? }
? ? }
? ? @Override
? ? public Object invoke(MethodInvocation invocation) {
? ? ? ? pre(invocation);
? ? ? ? Object result;
? ? ? ? try {
? ? ? ? ? ? result = invocation.proceed();
? ? ? ? ? ? post(invocation, result);
? ? ? ? ? ? return result;
? ? ? ? } catch (Throwable ex) {
? ? ? ? ? ? logger.error("controller 執(zhí)行異常: ", ex);
? ? ? ? ? ? error(invocation, ex);
? ? ? ? }
? ? ? ? return null;
? ? }
? ? public void error(MethodInvocation invocation, Throwable ex) {
? ? ? ? String msgText = ex.getMessage();
? ? ? ? logger.info(startTime.get() + " 異常,請求結(jié)束");
? ? ? ? logger.info("RESPONSE : " + msgText);
? ? ? ? logger.info("SPEND TIME : " + (System.currentTimeMillis() - startTime.get()));
? ? }
? ? private void pre(MethodInvocation invocation) {
? ? ? ? long now = System.currentTimeMillis();
? ? ? ? startTime.set(now);
? ? ? ? logger.info(now + " 請求開始");
? ? ? ? ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
? ? ? ? HttpServletRequest request = attributes.getRequest();
? ? ? ? logger.info("URL : " + request.getRequestURL().toString());
? ? ? ? logger.info("HTTP_METHOD : " + request.getMethod());
? ? ? ? logger.info("REMOTE_IP : " + getRemoteIp(request));
? ? ? ? logger.info("LOCAL_IP : " + localIp);
? ? ? ? logger.info("METHOD : " + request.getMethod());
? ? ? ? logger.info("CLASS_METHOD : " + getTargetClassName(invocation) + "." + invocation.getMethod().getName());
? ? ? ? // 獲取請求頭header參數(shù)
? ? ? ? Map<String, String> map = new HashMap<String, String>();
? ? ? ? Enumeration<String> headerNames = request.getHeaderNames();
? ? ? ? while (headerNames.hasMoreElements()) {
? ? ? ? ? ? String key = (String) headerNames.nextElement();
? ? ? ? ? ? String value = request.getHeader(key);
? ? ? ? ? ? map.put(key, value);
? ? ? ? }
? ? ? ? logger.info("HEADERS : " + JSONObject.toJSONString(map));
? ? ? ? Date createTime = new Date(now);
? ? ? ? // 請求報(bào)文
? ? ? ? Object[] args = invocation.getArguments();// 參數(shù)
? ? ? ? String msgText = "";
? ? ? ? Annotation[][] annotationss = invocation.getMethod().getParameterAnnotations();
? ? ? ? for (int i = 0; i < args.length; i++) {
? ? ? ? ? ? Object arg = args[i];
? ? ? ? ? ? if (!(arg instanceof ServletRequest)
? ? ? ? ? ? ? ? ? ? && !(arg instanceof ServletResponse)
? ? ? ? ? ? ? ? ? ? && !(arg instanceof Model)) {
? ? ? ? ? ? ? ? RequestParam rp = null;
? ? ? ? ? ? ? ? Annotation[] annotations = annotationss[i];
? ? ? ? ? ? ? ? for (Annotation annotation : annotations) {
? ? ? ? ? ? ? ? ? ? if (annotation instanceof RequestParam) {
? ? ? ? ? ? ? ? ? ? ? ? rp = (RequestParam) annotation;
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? if (msgText.equals("")) {
? ? ? ? ? ? ? ? ? ? msgText += (rp != null ? rp.value() + " = " : " ") + JSONObject.toJSONString(arg);
? ? ? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? ? ? msgText += "," + (rp != null ? rp.value() + " = " : " ") + JSONObject.toJSONString(arg);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? logger.info("PARAMS : " + msgText);
? ? }
? ? private void post(MethodInvocation invocation, Object result) {
? ? ? ? logger.info(startTime.get() + " 請求結(jié)束");
? ? ? ? if (!(result instanceof ModelAndView)) {
? ? ? ? ? ? String msgText = JSONObject.toJSONString(result);
? ? ? ? ? ? logger.info("RESPONSE : " + msgText);
? ? ? ? }
? ? ? ? logger.info("SPEND TIME : " + (System.currentTimeMillis() - startTime.get()));
? ? }
? ? private String getRemoteIp(HttpServletRequest request) {
? ? ? ? String remoteIp = null;
? ? ? ? String remoteAddr = request.getRemoteAddr();
? ? ? ? String forwarded = request.getHeader("X-Forwarded-For");
? ? ? ? String realIp = request.getHeader("X-Real-IP");
? ? ? ? if (realIp == null) {
? ? ? ? ? ? if (forwarded == null) {
? ? ? ? ? ? ? ? remoteIp = remoteAddr;
? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? remoteIp = remoteAddr + "/" + forwarded.split(",")[0];
? ? ? ? ? ? }
? ? ? ? } else {
? ? ? ? ? ? if (realIp.equals(forwarded)) {
? ? ? ? ? ? ? ? remoteIp = realIp;
? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? if (forwarded != null) {
? ? ? ? ? ? ? ? ? ? forwarded = forwarded.split(",")[0];
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? remoteIp = realIp + "/" + forwarded;
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? return remoteIp;
? ? }
? ? private String getTargetClassName(MethodInvocation invocation) {
? ? ? ? String targetClassName = "";
? ? ? ? try {
? ? ? ? ? ? targetClassName = AopTargetUtils.getTarget(invocation.getThis()).getClass().getName();
? ? ? ? } catch (Exception e) {
? ? ? ? ? ? targetClassName = invocation.getThis().getClass().getName();
? ? ? ? }
? ? ? ? return targetClassName;
? ? }
}
AopTargetUtils:
public class AopTargetUtils { ?
??
? ? ??
? ? /**?
? ? ?* 獲取 目標(biāo)對象?
? ? ?* @param proxy 代理對象?
? ? ?* @return ?
? ? ?* @throws Exception?
? ? ?*/ ?
? ? public static Object getTarget(Object proxy) throws Exception { ?
? ? ? ? ??
? ? ? ? if(!AopUtils.isAopProxy(proxy)) {
? ? ? ? ? ? return proxy;//不是代理對象 ?
? ? ? ? } ?
? ? ? ? ??
? ? ? ? if(AopUtils.isJdkDynamicProxy(proxy)) {
? ? ? ? ? ? return getJdkDynamicProxyTargetObject(proxy); ?
? ? ? ? } else { //cglib ?
? ? ? ? ? ? return getCglibProxyTargetObject(proxy); ?
? ? ? ? } ?
? ? ? ? ??
? ? ? ? ??
? ? ? ? ??
? ? } ?
??
??
? ? private static Object getCglibProxyTargetObject(Object proxy) throws Exception { ?
? ? ? ? Field h = proxy.getClass().getDeclaredField("CGLIB$CALLBACK_0"); ?
? ? ? ? h.setAccessible(true);
? ? ? ? Object dynamicAdvisedInterceptor = h.get(proxy); ?
? ? ? ? ??
? ? ? ? Field advised = dynamicAdvisedInterceptor.getClass().getDeclaredField("advised"); ?
? ? ? ? advised.setAccessible(true); ?
? ? ? ? ??
? ? ? ? Object target = ((AdvisedSupport)advised.get(dynamicAdvisedInterceptor)).getTargetSource().getTarget();
? ? ? ? ??
? ? ? ? return getTarget(target);
? ? } ?
??
??
? ? private static Object getJdkDynamicProxyTargetObject(Object proxy) throws Exception { ?
? ? ? ? Field h = proxy.getClass().getSuperclass().getDeclaredField("h"); ?
? ? ? ? h.setAccessible(true); ?
? ? ? ? AopProxy aopProxy = (AopProxy) h.get(proxy);
? ? ? ? ??
? ? ? ? Field advised = aopProxy.getClass().getDeclaredField("advised"); ?
? ? ? ? advised.setAccessible(true); ?
? ? ? ? ??
? ? ? ? Object target = ((AdvisedSupport)advised.get(aopProxy)).getTargetSource().getTarget();
? ? ? ? ??
? ? ? ? return getTarget(target);?
? ? } ?
? ? ??
}
3.4 實(shí)現(xiàn)BeanPostProcessor接口
作用:篩選出需要生成代理的類,并生成代理類,返回給Spring容器管理。
public class InteractRecordBeanPostProcessor implements BeanPostProcessor {
? ? private static Logger logger = LoggerFactory.getLogger(InteractRecordBeanPostProcessor.class);
? ? @Autowired
? ? private InteractRecordFactoryPostProcessor interactRecordFactoryPostProcessor;
? ? @Autowired
? ? private ControllerMethodInterceptor controllerMethodInterceptor;
? ? private String BASE_PACKAGES[];//需要攔截的包
? ? private String EXCLUDING[];// 過濾的包
? ? //一層目錄匹配
? ? private static final String ONE_REGEX = "[a-zA-Z0-9_]+";
? ? //多層目錄匹配
? ? private static final String ALL_REGEX = ".*";
? ? private static final String END_ALL_REGEX = "*";
? ? @PostConstruct
? ? public void init() {
? ? ? ? EnableInteractRecord ir = interactRecordFactoryPostProcessor.getEnableInteractRecord();
? ? ? ? BASE_PACKAGES = ir.basePackages();
? ? ? ? EXCLUDING = ir.exclusions();
? ? }
? ? @Override
? ? public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
? ? ? ? try {
? ? ? ? ? ? if (interactRecordFactoryPostProcessor.getEnableInteractRecord() != null) {
? ? ? ? ? ? ? ? // 根據(jù)注解配置的包名記錄對應(yīng)的controller層
? ? ? ? ? ? ? ? if (BASE_PACKAGES != null && BASE_PACKAGES.length > 0) {
? ? ? ? ? ? ? ? ? ? Object proxyObj = doEnhanceForController(bean);
? ? ? ? ? ? ? ? ? ? if (proxyObj != null) {
? ? ? ? ? ? ? ? ? ? ? ? return proxyObj;
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? } catch (Exception e) {
? ? ? ? ? ? logger.error("postProcessAfterInitialization() Exception ", e);
? ? ? ? }
? ? ? ? return bean;
? ? }
? ? @Override
? ? public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
? ? ? ? return bean;
? ? }
? ? private Object doEnhanceForController(Object bean) {
? ? ? ? String beanPackageName = getBeanPackageName(bean);
? ? ? ? if (StringUtils.isNotBlank(beanPackageName)) {
? ? ? ? ? ? for (String basePackage : BASE_PACKAGES) {
? ? ? ? ? ? ? ? if (matchingPackage(basePackage, beanPackageName)) {
? ? ? ? ? ? ? ? ? ? if (EXCLUDING != null && EXCLUDING.length > 0) {
? ? ? ? ? ? ? ? ? ? ? ? for (String excluding : EXCLUDING) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? if (matchingPackage(excluding, beanPackageName)) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? return bean;
? ? ? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? Object target = null;
? ? ? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? ? ? target = AopTargetUtils.getTarget(bean);
? ? ? ? ? ? ? ? ? ? } catch (Exception e) {
? ? ? ? ? ? ? ? ? ? ? ? logger.error("AopTargetUtils.getTarget() exception", e);
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? if (target != null) {
? ? ? ? ? ? ? ? ? ? ? ? boolean isController = target.getClass().isAnnotationPresent(Controller.class);
? ? ? ? ? ? ? ? ? ? ? ? boolean isRestController = target.getClass().isAnnotationPresent(RestController.class);
? ? ? ? ? ? ? ? ? ? ? ? if (isController || isRestController) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ProxyFactory proxy = new ProxyFactory();
? ? ? ? ? ? ? ? ? ? ? ? ? ? proxy.setTarget(bean);
? ? ? ? ? ? ? ? ? ? ? ? ? ? proxy.addAdvice(controllerMethodInterceptor);
? ? ? ? ? ? ? ? ? ? ? ? ? ? return proxy.getProxy();
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? return null;
? ? }
? ? private static boolean matchingPackage(String basePackage, String currentPackage) {
? ? ? ? if (StringUtils.isEmpty(basePackage) || StringUtils.isEmpty(currentPackage)) {
? ? ? ? ? ? return false;
? ? ? ? }
? ? ? ? if (basePackage.indexOf("*") != -1) {
? ? ? ? ? ? String patterns[] = StringUtils.split(basePackage, ".");
? ? ? ? ? ? for (int i = 0; i < patterns.length; i++) {
? ? ? ? ? ? ? ? String patternNode = patterns[i];
? ? ? ? ? ? ? ? if (patternNode.equals("*")) {
? ? ? ? ? ? ? ? ? ? patterns[i] = ONE_REGEX;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? if (patternNode.equals("**")) {
? ? ? ? ? ? ? ? ? ? if (i == patterns.length - 1) {
? ? ? ? ? ? ? ? ? ? ? ? patterns[i] = END_ALL_REGEX;
? ? ? ? ? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? ? ? ? ? patterns[i] = ALL_REGEX;
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? ? ? String basePackageRegex = StringUtils.join(patterns, "\\.");
? ? ? ? ? ? Pattern r = Pattern.compile(basePackageRegex);
? ? ? ? ? ? Matcher m = r.matcher(currentPackage);
? ? ? ? ? ? return m.find();
? ? ? ? } else {
? ? ? ? ? ? return basePackage.equals(currentPackage);
? ? ? ? }
? ? }
? ? private String getBeanPackageName(Object bean) {
? ? ? ? String beanPackageName = "";
? ? ? ? if (bean != null) {
? ? ? ? ? ? Class<?> beanClass = bean.getClass();
? ? ? ? ? ? if (beanClass != null) {
? ? ? ? ? ? ? ? Package beanPackage = beanClass.getPackage();
? ? ? ? ? ? ? ? if (beanPackage != null) {
? ? ? ? ? ? ? ? ? ? beanPackageName = beanPackage.getName();
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? return beanPackageName;
? ? }
}
3.5 啟動(dòng)類配置注解
@EnableInteractRecord(basePackages = “com.test.test.controller”,exclusions = “com.test.demo.controller”)
以上即可實(shí)現(xiàn)入?yún)?、出參日志統(tǒng)一打印,并且可以將特定的controller集中管理,并不進(jìn)行日志的打印(及不進(jìn)生成代理類)。
4.出現(xiàn)的問題(及其解決辦法)
實(shí)際開發(fā)中,特定不需要打印日志的接口,無法統(tǒng)一到一個(gè)包下。大部分需要打印的接口,和不需要打印的接口,大概率會參雜在同一個(gè)controller中,根據(jù)以上設(shè)計(jì)思路,無法進(jìn)行區(qū)分。
解決辦法:
自定義排除入?yún)⒋蛴∽⒔?/p>
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExcludeReqLog {
}
自定義排除出參打印注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExcludeRespLog {
}
增加邏輯
// 1.在解析requestParam之前進(jìn)行判斷
?? ??? ?Method method = invocation.getMethod();
? ? ? ? Annotation[] declaredAnnotations = method.getDeclaredAnnotations();
? ? ? ? boolean flag = true;
? ? ? ? for (Annotation annotation : declaredAnnotations) {
? ? ? ? ? ? if (annotation instanceof ExcludeReqLog) {
? ? ? ? ? ? ? ? flag = false;
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? if (!flag) {
? ? ? ? ? ? logger.info("該方法已排除,不打印入?yún)?);
? ? ? ? ? ? return;
? ? ? ? }
// 2.在解析requestResp之前進(jìn)行判斷
?? ??? ?Method method = invocation.getMethod();
? ? ? ? Annotation[] declaredAnnotations = method.getDeclaredAnnotations();
? ? ? ? boolean flag = true;
? ? ? ? for (Annotation annotation : declaredAnnotations) {
? ? ? ? ? ? if (annotation instanceof ExcludeRespLog) {
? ? ? ? ? ? ? ? flag = false;
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? if (!flag) {
? ? ? ? ? ? logger.info("該方法已排除,不打印出參");
? ? ? ? ? ? return;
? ? ? ? }
使用方法
// 1.不打印入?yún)?
? ? @PostMapping("/uploadImg")
? ? @ExcludeReqLog
? ? public Result<List<Demo>> uploadIdeaImg(@RequestParam(value = "imgFile", required = false) MultipartFile[] imgFile) {
? ? ? ? return demoService.uploadIdeaImg(imgFile);
? ? }
//2.不打印出參
? ? @PostMapping("/uploadImg")
? ? @ExcludeRespLog?
? ? public Result<List<Demo>> uploadIdeaImg(@RequestParam(value = "imgFile", required = false) MultipartFile[] imgFile) {
? ? ? ? return demoService.uploadIdeaImg(imgFile);
? ? }
問題解決
5.總結(jié)
以上即可兼容包排除和注解排除兩種方式,進(jìn)行入?yún)?、出參統(tǒng)一打印的控制。除此之外,還可以根據(jù)需求,進(jìn)行其他增強(qiáng)。
這些僅為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
-
spring+html5實(shí)現(xiàn)安全傳輸隨機(jī)數(shù)字密碼鍵盤
這篇文章主要為大家詳細(xì)介紹了spring html5實(shí)現(xiàn)安全傳輸隨機(jī)數(shù)字密碼鍵盤,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下 2017-04-04
-
Java實(shí)現(xiàn)簡單樹結(jié)構(gòu)
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)簡單樹結(jié)構(gòu)的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下 2017-01-01
-
Java日常練習(xí)題,每天進(jìn)步一點(diǎn)點(diǎn)(60)
下面小編就為大家?guī)硪黄狫ava基礎(chǔ)的幾道練習(xí)題(分享)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧,希望可以幫到你 2021-08-08
-
java如何實(shí)現(xiàn)嵌套對象轉(zhuǎn)大map(扁平化)
這篇文章主要介紹了java如何實(shí)現(xiàn)嵌套對象轉(zhuǎn)大map(扁平化),具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教 2022-10-10
最新評論
1.背景
SpringBoot項(xiàng)目中,之前都是在controller方法的第一行手動(dòng)打印 log,return之前再打印返回值。有多個(gè)返回點(diǎn)時(shí),就需要出現(xiàn)多少重復(fù)代碼,過多的非業(yè)務(wù)代碼顯得十分凌亂。
本文將采用AOP 配置自定義注解實(shí)現(xiàn) 入?yún)ⅰ⒊鰠⒌娜罩敬蛴。ǚ椒ǖ娜雲(yún)⒑头祷刂刀疾捎?fastjson 序列化)。
2.設(shè)計(jì)思路
將特定包下所有的controller生成代理類對象,并交由Spring容器管理,并重寫invoke方法進(jìn)行增強(qiáng)(入?yún)?、出參的打?.
3.核心代碼
3.1 自定義注解
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import({InteractRecordBeanPostProcessor.class}) public @interface EnableInteractRecord { ? ? /** ? ? ?* app對應(yīng)controller包名 ? ? ?*/ ? ? String[] basePackages() default {}; ? ? /** ? ? ?* 排除某些包 ? ? ?*/ ? ? String[] exclusions() default {}; }
3.2 實(shí)現(xiàn)BeanFactoryPostProcessor接口
作用:獲取EnableInteractRecord注解對象,用于獲取需要?jiǎng)?chuàng)建代理對象的包名,以及需要排除的包名
@Component public class InteractRecordFactoryPostProcessor implements BeanFactoryPostProcessor { ? ? private static Logger logger = LoggerFactory.getLogger(InteractRecordFactoryPostProcessor.class); ? ? private EnableInteractRecord enableInteractRecord; ? ? @Override ? ? public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { ? ? ? ? try { ? ? ? ? ? ? String[] names = beanFactory.getBeanNamesForAnnotation(EnableInteractRecord.class); ? ? ? ? ? ? for (String name : names) { ? ? ? ? ? ? ? ? enableInteractRecord = beanFactory.findAnnotationOnBean(name, EnableInteractRecord.class); ? ? ? ? ? ? ? ? logger.info("開啟交互記錄 ", enableInteractRecord); ? ? ? ? ? ? } ? ? ? ? } catch (Exception e) { ? ? ? ? ? ? logger.error("postProcessBeanFactory() Exception ", e); ? ? ? ? } ? ? } ? ? public EnableInteractRecord getEnableInteractRecord() { ? ? ? ? return enableInteractRecord; ? ? } }
3.3 實(shí)現(xiàn)MethodInterceptor編寫打印日志邏輯
作用:進(jìn)行入?yún)?、出參打印,包含是否打印邏?/p>
@Component public class ControllerMethodInterceptor implements MethodInterceptor { ? ? private static Logger logger = LoggerFactory.getLogger(ControllerMethodInterceptor.class); ? ? // 請求開始時(shí)間 ? ? ThreadLocal<Long> startTime = new ThreadLocal<>(); ? ? private String localIp = ""; ? ? @PostConstruct ? ? public void init() { ? ? ? ? try { ? ? ? ? ? ? localIp = InetAddress.getLocalHost().getHostAddress(); ? ? ? ? } catch (UnknownHostException e) { ? ? ? ? ? ? logger.error("本地IP初始化失敗 : ", e); ? ? ? ? } ? ? } ? ? @Override ? ? public Object invoke(MethodInvocation invocation) { ? ? ? ? pre(invocation); ? ? ? ? Object result; ? ? ? ? try { ? ? ? ? ? ? result = invocation.proceed(); ? ? ? ? ? ? post(invocation, result); ? ? ? ? ? ? return result; ? ? ? ? } catch (Throwable ex) { ? ? ? ? ? ? logger.error("controller 執(zhí)行異常: ", ex); ? ? ? ? ? ? error(invocation, ex); ? ? ? ? } ? ? ? ? return null; ? ? } ? ? public void error(MethodInvocation invocation, Throwable ex) { ? ? ? ? String msgText = ex.getMessage(); ? ? ? ? logger.info(startTime.get() + " 異常,請求結(jié)束"); ? ? ? ? logger.info("RESPONSE : " + msgText); ? ? ? ? logger.info("SPEND TIME : " + (System.currentTimeMillis() - startTime.get())); ? ? } ? ? private void pre(MethodInvocation invocation) { ? ? ? ? long now = System.currentTimeMillis(); ? ? ? ? startTime.set(now); ? ? ? ? logger.info(now + " 請求開始"); ? ? ? ? ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); ? ? ? ? HttpServletRequest request = attributes.getRequest(); ? ? ? ? logger.info("URL : " + request.getRequestURL().toString()); ? ? ? ? logger.info("HTTP_METHOD : " + request.getMethod()); ? ? ? ? logger.info("REMOTE_IP : " + getRemoteIp(request)); ? ? ? ? logger.info("LOCAL_IP : " + localIp); ? ? ? ? logger.info("METHOD : " + request.getMethod()); ? ? ? ? logger.info("CLASS_METHOD : " + getTargetClassName(invocation) + "." + invocation.getMethod().getName()); ? ? ? ? // 獲取請求頭header參數(shù) ? ? ? ? Map<String, String> map = new HashMap<String, String>(); ? ? ? ? Enumeration<String> headerNames = request.getHeaderNames(); ? ? ? ? while (headerNames.hasMoreElements()) { ? ? ? ? ? ? String key = (String) headerNames.nextElement(); ? ? ? ? ? ? String value = request.getHeader(key); ? ? ? ? ? ? map.put(key, value); ? ? ? ? } ? ? ? ? logger.info("HEADERS : " + JSONObject.toJSONString(map)); ? ? ? ? Date createTime = new Date(now); ? ? ? ? // 請求報(bào)文 ? ? ? ? Object[] args = invocation.getArguments();// 參數(shù) ? ? ? ? String msgText = ""; ? ? ? ? Annotation[][] annotationss = invocation.getMethod().getParameterAnnotations(); ? ? ? ? for (int i = 0; i < args.length; i++) { ? ? ? ? ? ? Object arg = args[i]; ? ? ? ? ? ? if (!(arg instanceof ServletRequest) ? ? ? ? ? ? ? ? ? ? && !(arg instanceof ServletResponse) ? ? ? ? ? ? ? ? ? ? && !(arg instanceof Model)) { ? ? ? ? ? ? ? ? RequestParam rp = null; ? ? ? ? ? ? ? ? Annotation[] annotations = annotationss[i]; ? ? ? ? ? ? ? ? for (Annotation annotation : annotations) { ? ? ? ? ? ? ? ? ? ? if (annotation instanceof RequestParam) { ? ? ? ? ? ? ? ? ? ? ? ? rp = (RequestParam) annotation; ? ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? if (msgText.equals("")) { ? ? ? ? ? ? ? ? ? ? msgText += (rp != null ? rp.value() + " = " : " ") + JSONObject.toJSONString(arg); ? ? ? ? ? ? ? ? } else { ? ? ? ? ? ? ? ? ? ? msgText += "," + (rp != null ? rp.value() + " = " : " ") + JSONObject.toJSONString(arg); ? ? ? ? ? ? ? ? } ? ? ? ? ? ? } ? ? ? ? } ? ? ? ? logger.info("PARAMS : " + msgText); ? ? } ? ? private void post(MethodInvocation invocation, Object result) { ? ? ? ? logger.info(startTime.get() + " 請求結(jié)束"); ? ? ? ? if (!(result instanceof ModelAndView)) { ? ? ? ? ? ? String msgText = JSONObject.toJSONString(result); ? ? ? ? ? ? logger.info("RESPONSE : " + msgText); ? ? ? ? } ? ? ? ? logger.info("SPEND TIME : " + (System.currentTimeMillis() - startTime.get())); ? ? } ? ? private String getRemoteIp(HttpServletRequest request) { ? ? ? ? String remoteIp = null; ? ? ? ? String remoteAddr = request.getRemoteAddr(); ? ? ? ? String forwarded = request.getHeader("X-Forwarded-For"); ? ? ? ? String realIp = request.getHeader("X-Real-IP"); ? ? ? ? if (realIp == null) { ? ? ? ? ? ? if (forwarded == null) { ? ? ? ? ? ? ? ? remoteIp = remoteAddr; ? ? ? ? ? ? } else { ? ? ? ? ? ? ? ? remoteIp = remoteAddr + "/" + forwarded.split(",")[0]; ? ? ? ? ? ? } ? ? ? ? } else { ? ? ? ? ? ? if (realIp.equals(forwarded)) { ? ? ? ? ? ? ? ? remoteIp = realIp; ? ? ? ? ? ? } else { ? ? ? ? ? ? ? ? if (forwarded != null) { ? ? ? ? ? ? ? ? ? ? forwarded = forwarded.split(",")[0]; ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? remoteIp = realIp + "/" + forwarded; ? ? ? ? ? ? } ? ? ? ? } ? ? ? ? return remoteIp; ? ? } ? ? private String getTargetClassName(MethodInvocation invocation) { ? ? ? ? String targetClassName = ""; ? ? ? ? try { ? ? ? ? ? ? targetClassName = AopTargetUtils.getTarget(invocation.getThis()).getClass().getName(); ? ? ? ? } catch (Exception e) { ? ? ? ? ? ? targetClassName = invocation.getThis().getClass().getName(); ? ? ? ? } ? ? ? ? return targetClassName; ? ? } }
AopTargetUtils:
public class AopTargetUtils { ? ?? ? ? ?? ? ? /**? ? ? ?* 獲取 目標(biāo)對象? ? ? ?* @param proxy 代理對象? ? ? ?* @return ? ? ? ?* @throws Exception? ? ? ?*/ ? ? ? public static Object getTarget(Object proxy) throws Exception { ? ? ? ? ? ?? ? ? ? ? if(!AopUtils.isAopProxy(proxy)) { ? ? ? ? ? ? return proxy;//不是代理對象 ? ? ? ? ? } ? ? ? ? ? ?? ? ? ? ? if(AopUtils.isJdkDynamicProxy(proxy)) { ? ? ? ? ? ? return getJdkDynamicProxyTargetObject(proxy); ? ? ? ? ? } else { //cglib ? ? ? ? ? ? ? return getCglibProxyTargetObject(proxy); ? ? ? ? ? } ? ? ? ? ? ?? ? ? ? ? ?? ? ? ? ? ?? ? ? } ? ?? ?? ? ? private static Object getCglibProxyTargetObject(Object proxy) throws Exception { ? ? ? ? ? Field h = proxy.getClass().getDeclaredField("CGLIB$CALLBACK_0"); ? ? ? ? ? h.setAccessible(true); ? ? ? ? Object dynamicAdvisedInterceptor = h.get(proxy); ? ? ? ? ? ?? ? ? ? ? Field advised = dynamicAdvisedInterceptor.getClass().getDeclaredField("advised"); ? ? ? ? ? advised.setAccessible(true); ? ? ? ? ? ?? ? ? ? ? Object target = ((AdvisedSupport)advised.get(dynamicAdvisedInterceptor)).getTargetSource().getTarget(); ? ? ? ? ?? ? ? ? ? return getTarget(target); ? ? } ? ?? ?? ? ? private static Object getJdkDynamicProxyTargetObject(Object proxy) throws Exception { ? ? ? ? ? Field h = proxy.getClass().getSuperclass().getDeclaredField("h"); ? ? ? ? ? h.setAccessible(true); ? ? ? ? ? AopProxy aopProxy = (AopProxy) h.get(proxy); ? ? ? ? ?? ? ? ? ? Field advised = aopProxy.getClass().getDeclaredField("advised"); ? ? ? ? ? advised.setAccessible(true); ? ? ? ? ? ?? ? ? ? ? Object target = ((AdvisedSupport)advised.get(aopProxy)).getTargetSource().getTarget(); ? ? ? ? ?? ? ? ? ? return getTarget(target);? ? ? } ? ? ? ?? }
3.4 實(shí)現(xiàn)BeanPostProcessor接口
作用:篩選出需要生成代理的類,并生成代理類,返回給Spring容器管理。
public class InteractRecordBeanPostProcessor implements BeanPostProcessor { ? ? private static Logger logger = LoggerFactory.getLogger(InteractRecordBeanPostProcessor.class); ? ? @Autowired ? ? private InteractRecordFactoryPostProcessor interactRecordFactoryPostProcessor; ? ? @Autowired ? ? private ControllerMethodInterceptor controllerMethodInterceptor; ? ? private String BASE_PACKAGES[];//需要攔截的包 ? ? private String EXCLUDING[];// 過濾的包 ? ? //一層目錄匹配 ? ? private static final String ONE_REGEX = "[a-zA-Z0-9_]+"; ? ? //多層目錄匹配 ? ? private static final String ALL_REGEX = ".*"; ? ? private static final String END_ALL_REGEX = "*"; ? ? @PostConstruct ? ? public void init() { ? ? ? ? EnableInteractRecord ir = interactRecordFactoryPostProcessor.getEnableInteractRecord(); ? ? ? ? BASE_PACKAGES = ir.basePackages(); ? ? ? ? EXCLUDING = ir.exclusions(); ? ? } ? ? @Override ? ? public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { ? ? ? ? try { ? ? ? ? ? ? if (interactRecordFactoryPostProcessor.getEnableInteractRecord() != null) { ? ? ? ? ? ? ? ? // 根據(jù)注解配置的包名記錄對應(yīng)的controller層 ? ? ? ? ? ? ? ? if (BASE_PACKAGES != null && BASE_PACKAGES.length > 0) { ? ? ? ? ? ? ? ? ? ? Object proxyObj = doEnhanceForController(bean); ? ? ? ? ? ? ? ? ? ? if (proxyObj != null) { ? ? ? ? ? ? ? ? ? ? ? ? return proxyObj; ? ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? } ? ? ? ? ? ? } ? ? ? ? } catch (Exception e) { ? ? ? ? ? ? logger.error("postProcessAfterInitialization() Exception ", e); ? ? ? ? } ? ? ? ? return bean; ? ? } ? ? @Override ? ? public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { ? ? ? ? return bean; ? ? } ? ? private Object doEnhanceForController(Object bean) { ? ? ? ? String beanPackageName = getBeanPackageName(bean); ? ? ? ? if (StringUtils.isNotBlank(beanPackageName)) { ? ? ? ? ? ? for (String basePackage : BASE_PACKAGES) { ? ? ? ? ? ? ? ? if (matchingPackage(basePackage, beanPackageName)) { ? ? ? ? ? ? ? ? ? ? if (EXCLUDING != null && EXCLUDING.length > 0) { ? ? ? ? ? ? ? ? ? ? ? ? for (String excluding : EXCLUDING) { ? ? ? ? ? ? ? ? ? ? ? ? ? ? if (matchingPackage(excluding, beanPackageName)) { ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? return bean; ? ? ? ? ? ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? ? ? Object target = null; ? ? ? ? ? ? ? ? ? ? try { ? ? ? ? ? ? ? ? ? ? ? ? target = AopTargetUtils.getTarget(bean); ? ? ? ? ? ? ? ? ? ? } catch (Exception e) { ? ? ? ? ? ? ? ? ? ? ? ? logger.error("AopTargetUtils.getTarget() exception", e); ? ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? ? ? if (target != null) { ? ? ? ? ? ? ? ? ? ? ? ? boolean isController = target.getClass().isAnnotationPresent(Controller.class); ? ? ? ? ? ? ? ? ? ? ? ? boolean isRestController = target.getClass().isAnnotationPresent(RestController.class); ? ? ? ? ? ? ? ? ? ? ? ? if (isController || isRestController) { ? ? ? ? ? ? ? ? ? ? ? ? ? ? ProxyFactory proxy = new ProxyFactory(); ? ? ? ? ? ? ? ? ? ? ? ? ? ? proxy.setTarget(bean); ? ? ? ? ? ? ? ? ? ? ? ? ? ? proxy.addAdvice(controllerMethodInterceptor); ? ? ? ? ? ? ? ? ? ? ? ? ? ? return proxy.getProxy(); ? ? ? ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? } ? ? ? ? ? ? } ? ? ? ? } ? ? ? ? return null; ? ? } ? ? private static boolean matchingPackage(String basePackage, String currentPackage) { ? ? ? ? if (StringUtils.isEmpty(basePackage) || StringUtils.isEmpty(currentPackage)) { ? ? ? ? ? ? return false; ? ? ? ? } ? ? ? ? if (basePackage.indexOf("*") != -1) { ? ? ? ? ? ? String patterns[] = StringUtils.split(basePackage, "."); ? ? ? ? ? ? for (int i = 0; i < patterns.length; i++) { ? ? ? ? ? ? ? ? String patternNode = patterns[i]; ? ? ? ? ? ? ? ? if (patternNode.equals("*")) { ? ? ? ? ? ? ? ? ? ? patterns[i] = ONE_REGEX; ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? if (patternNode.equals("**")) { ? ? ? ? ? ? ? ? ? ? if (i == patterns.length - 1) { ? ? ? ? ? ? ? ? ? ? ? ? patterns[i] = END_ALL_REGEX; ? ? ? ? ? ? ? ? ? ? } else { ? ? ? ? ? ? ? ? ? ? ? ? patterns[i] = ALL_REGEX; ? ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? } ? ? ? ? ? ? } ? ? ? ? ? ? String basePackageRegex = StringUtils.join(patterns, "\\."); ? ? ? ? ? ? Pattern r = Pattern.compile(basePackageRegex); ? ? ? ? ? ? Matcher m = r.matcher(currentPackage); ? ? ? ? ? ? return m.find(); ? ? ? ? } else { ? ? ? ? ? ? return basePackage.equals(currentPackage); ? ? ? ? } ? ? } ? ? private String getBeanPackageName(Object bean) { ? ? ? ? String beanPackageName = ""; ? ? ? ? if (bean != null) { ? ? ? ? ? ? Class<?> beanClass = bean.getClass(); ? ? ? ? ? ? if (beanClass != null) { ? ? ? ? ? ? ? ? Package beanPackage = beanClass.getPackage(); ? ? ? ? ? ? ? ? if (beanPackage != null) { ? ? ? ? ? ? ? ? ? ? beanPackageName = beanPackage.getName(); ? ? ? ? ? ? ? ? } ? ? ? ? ? ? } ? ? ? ? } ? ? ? ? return beanPackageName; ? ? } }
3.5 啟動(dòng)類配置注解
@EnableInteractRecord(basePackages = “com.test.test.controller”,exclusions = “com.test.demo.controller”)
以上即可實(shí)現(xiàn)入?yún)?、出參日志統(tǒng)一打印,并且可以將特定的controller集中管理,并不進(jìn)行日志的打印(及不進(jìn)生成代理類)。
4.出現(xiàn)的問題(及其解決辦法)
實(shí)際開發(fā)中,特定不需要打印日志的接口,無法統(tǒng)一到一個(gè)包下。大部分需要打印的接口,和不需要打印的接口,大概率會參雜在同一個(gè)controller中,根據(jù)以上設(shè)計(jì)思路,無法進(jìn)行區(qū)分。
解決辦法:
自定義排除入?yún)⒋蛴∽⒔?/p>
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ExcludeReqLog { }
自定義排除出參打印注解
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ExcludeRespLog { }
增加邏輯
// 1.在解析requestParam之前進(jìn)行判斷 ?? ??? ?Method method = invocation.getMethod(); ? ? ? ? Annotation[] declaredAnnotations = method.getDeclaredAnnotations(); ? ? ? ? boolean flag = true; ? ? ? ? for (Annotation annotation : declaredAnnotations) { ? ? ? ? ? ? if (annotation instanceof ExcludeReqLog) { ? ? ? ? ? ? ? ? flag = false; ? ? ? ? ? ? } ? ? ? ? } ? ? ? ? if (!flag) { ? ? ? ? ? ? logger.info("該方法已排除,不打印入?yún)?); ? ? ? ? ? ? return; ? ? ? ? } // 2.在解析requestResp之前進(jìn)行判斷 ?? ??? ?Method method = invocation.getMethod(); ? ? ? ? Annotation[] declaredAnnotations = method.getDeclaredAnnotations(); ? ? ? ? boolean flag = true; ? ? ? ? for (Annotation annotation : declaredAnnotations) { ? ? ? ? ? ? if (annotation instanceof ExcludeRespLog) { ? ? ? ? ? ? ? ? flag = false; ? ? ? ? ? ? } ? ? ? ? } ? ? ? ? if (!flag) { ? ? ? ? ? ? logger.info("該方法已排除,不打印出參"); ? ? ? ? ? ? return; ? ? ? ? }
使用方法
// 1.不打印入?yún)? ? ? @PostMapping("/uploadImg") ? ? @ExcludeReqLog ? ? public Result<List<Demo>> uploadIdeaImg(@RequestParam(value = "imgFile", required = false) MultipartFile[] imgFile) { ? ? ? ? return demoService.uploadIdeaImg(imgFile); ? ? } //2.不打印出參 ? ? @PostMapping("/uploadImg") ? ? @ExcludeRespLog? ? ? public Result<List<Demo>> uploadIdeaImg(@RequestParam(value = "imgFile", required = false) MultipartFile[] imgFile) { ? ? ? ? return demoService.uploadIdeaImg(imgFile); ? ? }
問題解決
5.總結(jié)
以上即可兼容包排除和注解排除兩種方式,進(jìn)行入?yún)?、出參統(tǒng)一打印的控制。除此之外,還可以根據(jù)需求,進(jìn)行其他增強(qiáng)。
這些僅為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
spring+html5實(shí)現(xiàn)安全傳輸隨機(jī)數(shù)字密碼鍵盤
這篇文章主要為大家詳細(xì)介紹了spring html5實(shí)現(xiàn)安全傳輸隨機(jī)數(shù)字密碼鍵盤,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04Java實(shí)現(xiàn)簡單樹結(jié)構(gòu)
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)簡單樹結(jié)構(gòu)的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-01-01Java日常練習(xí)題,每天進(jìn)步一點(diǎn)點(diǎn)(60)
下面小編就為大家?guī)硪黄狫ava基礎(chǔ)的幾道練習(xí)題(分享)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧,希望可以幫到你2021-08-08java如何實(shí)現(xiàn)嵌套對象轉(zhuǎn)大map(扁平化)
這篇文章主要介紹了java如何實(shí)現(xiàn)嵌套對象轉(zhuǎn)大map(扁平化),具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10