如何利用aop的环绕消息处理log, 以及各种坑的记录
本文链接: https://www.cnblogs.com/zizaiwuyou/p/11667423.html
因为项目里有很多地方要打log, 所以决定改为AOP统一处理, 好不容易整好了, 特此记录一下:
一, 新建项目, 添加注解类和切面类
pom.xml文件如下:
4.0.0 com.jhyx demo 0.0.1-SNAPSHOT jar 项目名 Demo project for Spring Boot org.springframework.boot spring-boot-starter-parent 1.5.6.RELEASE UTF-8 UTF-8 1.8 org.springframework.boot spring-boot-starter-aop org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-test test org.slf4j slf4j-api com.alibaba fastjson LATEST org.mockito mockito-all LATEST test org.powermock powermock-core LATEST test org.powermock powermock-api-mockito LATEST test org.powermock powermock-module-testng 1.7.0 test org.springframework.boot spring-boot-maven-plugin spring-snapshots Spring Snapshots https://repo.spring.io/snapshot true spring-milestones Spring Milestones https://repo.spring.io/milestone false spring-snapshots Spring Snapshots https://repo.spring.io/snapshot true spring-milestones Spring Milestones https://repo.spring.io/milestone false
注解类内容如下:
package com.xxx.common.log; import org.springframework.stereotype.Component; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * TYPE -> Class, interface (including annotation type), or enum declaration * METHOD -> Method declaration */ @Target({ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface Log { String value() default ""; boolean ignore() default false; }
切面类如下:
package com.xxx.common.log; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.serializer.SerializerFeature; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import java.lang.reflect.Method; /** * Log 注解类实现 */ @Aspect @Order(100) @Component public class LogAspect { public static final Logger log = LoggerFactory.getLogger(LogAspect.class); public static final String dateformat = "yyyy:MM:dd HH:mm:ss"; public static final String STIRNG_START = "\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"; public static final String STIRNG_END = "\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"; // execution the scan of pakage 切点package @Pointcut("execution( * com.controller..*(..)) || execution( * com.service..*(..))") public void serviceLog(){ } @Around("serviceLog()") public Object around(ProceedingJoinPoint joinPoint) { // ProceedingJoinPoint 为JoinPoint 的子类,在父类基础上多了proceed()方法,用于增强切面 try { // 获取方法签名 MethodSignature signature = (MethodSignature) joinPoint.getSignature(); //java reflect相关类,通过反射得到注解 Method method = signature.getMethod(); Class> targetClass = method.getDeclaringClass(); StringBuffer classAndMethod = new StringBuffer(); //获取类注解Log Log classAnnotation = targetClass.getAnnotation(Log.class); //获取方法注解Log Log methodAnnotation = method.getAnnotation(Log.class); //如果类上Log注解不为空,则执行proceed() if (classAnnotation != null) { if (classAnnotation.ignore()) { //proceed() 方法执行切面方法,并返回方法返回值 return joinPoint.proceed(); } classAndMethod.append(classAnnotation.value()).append("-"); } //如果方法上Log注解不为空,则执行proceed() if (methodAnnotation != null) { if (methodAnnotation.ignore()) { return joinPoint.proceed(); } classAndMethod.append(methodAnnotation.value()); } //拼凑目标类名和参数名 String target = targetClass.getName() + "#" + method.getName(); String params = JSONObject.toJSONStringWithDateFormat(joinPoint.getArgs(), dateformat, SerializerFeature.WriteMapNullValue); log.info(STIRNG_START + "{} 开始调用--> {} 参数:{}", classAndMethod.toString(), target, params); long start = System.currentTimeMillis(); //如果类名上和方法上都没有Log注解,则直接执行 proceed() Object result = joinPoint.proceed(); long timeConsuming = System.currentTimeMillis() - start; // log.info("\n{} 调用结束<-- {} 返回值:{} 耗时:{}ms" + STIRNG_END, classAndMethod.toString(), target, JSONObject.toJSONStringWithDateFormat(result, dateformat, SerializerFeature.WriteMapNullValue), timeConsuming); log.info("\n{} 调用结束<-- {} 耗时:{}ms" + STIRNG_END, classAndMethod.toString(), target, timeConsuming); return result; } catch (Throwable throwable) { log.error("调用异常", throwable.getMessage(), throwable); } return null; } }
踩坑:@Pointcut("execution( * com.controller..*(..)) || execution( * com.service..*(..))")
设置execution表达式的具体详情请参考https://blog.csdn.net/yangshangwei/article/details/77627825
@Around("serviceLog()")定义的是触发时机, 通过一个空的方法来转换, 这种方式可以写多个环绕消息, 示例中只写了一个
二,导出JAR包
在eclipse中, 右键项目, Export- > JAR File
踩坑:
图中的这一项必须勾选, 不然无法被springBoot扫描
三, 在需要使用log的项目中导入jar包
我使用的是gradle, builde.gradle文件中加入下列配置
然后在项目根目录加一个libs文件夹, 把jar放进去
修改项目启动文件:
package com.qingniu.qfpay.rvwbackend; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Import; @SpringBootApplication(exclude = SecurityAutoConfiguration.class) @Import(AppConfig.class) @ComponentScan(basePackages = {"com.xxx, com.xxx.common.log"}) public class App { public static void main(final String[] args) { SpringApplication.run(App.class, args); } }
踩坑:这里的@ComponentScan配置,如果不声明, spring默认是扫描当前文件层级以及子孙级,但是如果声明的话, 就全按照声明的路径扫描, 所以要加上当前路径
四, 启动项目,
所有controller和service包下的类中的方法都会打印进出log, 如果要log入库, 在切面类中自定义操作就行了, 注解的作用在于定制化打印log, 可以按照自己的使用需求, 更改代码
参考资料:
https://my.oschina.net/xiaomingnevermind/blog/1619274
https://blog.csdn.net/d_ohko/article/details/78962458
https://blog.csdn.net/yangshangwei/article/details/77627825
https://www.cnblogs.com/cac2020/p/9216509.html