如何利用aop的环绕消息处理log, 以及各种坑的记录
本文链接: https://www.cnblogs.com/zizaiwuyou/p/11667423.html
因为项目里有很多地方要打log, 所以决定改为AOP统一处理, 好不容易整好了, 特此记录一下:
一, 新建项目, 添加注解类和切面类
pom.xml文件如下:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.jhyx</groupId> <artifactId>demo</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>项目名</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.6.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <!-- aop dependency --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <!-- web starter dependency --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- spring boot start denpendency --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- slf4j log dependency --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> </dependency> <!-- json dependency --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>LATEST</version> </dependency> <!-- test dependency --> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-all</artifactId> <version>LATEST</version> <scope>test</scope> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-core</artifactId> <version>LATEST</version> <scope>test</scope> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-api-mockito</artifactId> <version>LATEST</version> <scope>test</scope> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-module-testng</artifactId> <version>1.7.0</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> <repositories> <repository> <id>spring-snapshots</id> <name>Spring Snapshots</name> <url>https://repo.spring.io/snapshot</url> <snapshots> <enabled>true</enabled> </snapshots> </repository> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>spring-snapshots</id> <name>Spring Snapshots</name> <url>https://repo.spring.io/snapshot</url> <snapshots> <enabled>true</enabled> </snapshots> </pluginRepository> <pluginRepository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </pluginRepository> </pluginRepositories> </project>
注解类内容如下:
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.qingniu.qfpay.rvwbackend.controller..*(..)) || execution( * com.qingniu.qfpay.rvwbackend.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.qingniu.qfpay.rvwbackend.controller..*(..)) || execution( * com.qingniu.qfpay.rvwbackend.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.qingniu.qfpay.rvwbackend, 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