Java에서 입력 매개변수 및 출력 매개변수 로그의 통합 인쇄를 달성하는 방법
1.背景
SpringBoot项目中,之前都是在controller方法的第一行手动打印 log,return之前再打印返回值。有多个返回点时,就需要出现多少重复代码,过多的非业务代码显得十分凌乱。
本文将采用AOP 配置自定义注解实现 入参、出参的日志打印(方法的入参和返回值都采用 fastjson 序列化)。
2.设计思路
将特定包下所有的controller生成代理类对象,并交由Spring容器管理,并重写invoke方法进行增强(入参、出参的打印).
3.核心代码
3.1 自定义注解
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import({InteractRecordBeanPostProcessor.class}) public @interface EnableInteractRecord { /** * app对应controller包名 */ String[] basePackages() default {}; /** * 排除某些包 */ String[] exclusions() default {}; }
3.2 实现BeanFactoryPostProcessor接口
作用:获取EnableInteractRecord注解对象,用于获取需要创建代理对象的包名,以及需要排除的包名
@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 实现MethodInterceptor编写打印日志逻辑
作用:进行入参、出参打印,包含是否打印逻辑
@Component public class ControllerMethodInterceptor implements MethodInterceptor { private static Logger logger = LoggerFactory.getLogger(ControllerMethodInterceptor.class); // 请求开始时间 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 执行异常: ", ex); error(invocation, ex); } return null; } public void error(MethodInvocation invocation, Throwable ex) { String msgText = ex.getMessage(); logger.info(startTime.get() + " 异常,请求结束"); 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参数 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); // 请求报文 Object[] args = invocation.getArguments();// 参数 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() + " 请求结束"); 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 { /** * 获取 目标对象 * @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 实现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) { // 根据注解配置的包名记录对应的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 启动类配置注解
@EnableInteractRecord(basePackages = “com.test.test.controller”,exclusions = “com.test.demo.controller”)
以上即可实现入参、出参日志统一打印,并且可以将特定的controller集中管理,并不进行日志的打印(及不进生成代理类)。
4.出现的问题(及其解决办法)
实际开发中,特定不需要打印日志的接口,无法统一到一个包下。大部分需要打印的接口,和不需要打印的接口,大概率会参杂在同一个controller中,根据以上设计思路,无法进行区分。
解决办法:
自定义排除入参打印注解
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ExcludeReqLog { }
自定义排除出参打印注解
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ExcludeRespLog { }
增加逻辑
// 1.在解析requestParam之前进行判断 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("该方法已排除,不打印入参"); return; } // 2.在解析requestResp之前进行判断 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.不打印入参 @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); }
위 내용은 Java에서 입력 매개변수 및 출력 매개변수 로그의 통합 인쇄를 달성하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

AI Hentai Generator
AI Hentai를 무료로 생성하십시오.

인기 기사

뜨거운 도구

메모장++7.3.1
사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전
중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기
강력한 PHP 통합 개발 환경

드림위버 CS6
시각적 웹 개발 도구

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)

뜨거운 주제











Java의 난수 생성기 안내. 여기서는 예제를 통해 Java의 함수와 예제를 통해 두 가지 다른 생성기에 대해 설명합니다.

Java의 Weka 가이드. 여기에서는 소개, weka java 사용 방법, 플랫폼 유형 및 장점을 예제와 함께 설명합니다.

Java의 Smith Number 가이드. 여기서는 정의, Java에서 스미스 번호를 확인하는 방법에 대해 논의합니다. 코드 구현의 예.

이 기사에서는 가장 많이 묻는 Java Spring 면접 질문과 자세한 답변을 보관했습니다. 그래야 면접에 합격할 수 있습니다.

Java 8은 스트림 API를 소개하여 데이터 컬렉션을 처리하는 강력하고 표현적인 방법을 제공합니다. 그러나 스트림을 사용할 때 일반적인 질문은 다음과 같은 것입니다. 기존 루프는 조기 중단 또는 반환을 허용하지만 스트림의 Foreach 메소드는이 방법을 직접 지원하지 않습니다. 이 기사는 이유를 설명하고 스트림 처리 시스템에서 조기 종료를 구현하기위한 대체 방법을 탐색합니다. 추가 읽기 : Java Stream API 개선 스트림 foreach를 이해하십시오 Foreach 메소드는 스트림의 각 요소에서 하나의 작업을 수행하는 터미널 작동입니다. 디자인 의도입니다

Java의 TimeStamp to Date 안내. 여기서는 소개와 예제와 함께 Java에서 타임스탬프를 날짜로 변환하는 방법에 대해서도 설명합니다.

Java는 초보자와 숙련된 개발자 모두가 배울 수 있는 인기 있는 프로그래밍 언어입니다. 이 튜토리얼은 기본 개념부터 시작하여 고급 주제를 통해 진행됩니다. Java Development Kit를 설치한 후 간단한 "Hello, World!" 프로그램을 작성하여 프로그래밍을 연습할 수 있습니다. 코드를 이해한 후 명령 프롬프트를 사용하여 프로그램을 컴파일하고 실행하면 "Hello, World!"가 콘솔에 출력됩니다. Java를 배우면 프로그래밍 여정이 시작되고, 숙달이 깊어짐에 따라 더 복잡한 애플리케이션을 만들 수 있습니다.
