aop面向切面编程算是spring中比较重要的概念。恰好最近在开发过程中有通过aop+自定义注解来实现日志功能的想法,特此记录。
- 引入相关依赖,这边主要是引入spring boot相关的起步依赖,javassist用来获取入参信息。
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.zwj</groupId>
<artifactId>aopdemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>aopdemo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- 自定义一个注解类
package com.zwj.aopdemo.annotation;
import java.lang.annotation.*;
/**
* 自定义日志注解
* @author zev.zhang
* @version V1.0.0
*/
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysLog {
String description() default "";
}
- 编写切面类,在建好的切面类上使用@Aspect和@Component注解,@Component注解将建立的切面类注入spring容器,@Aspect注解将该类标注为切面类,来实现前置通知、后置通知、返回通知、异常通知、环绕通知。使用@PointCut注解定义切入点,切入点即为刚刚建好的自定义注解@SysLog。
- 前置通知 :@Before,即在目标方法执行之前运行该方法体的内容。
- 后置通知 :@After,即在目标方法运行之后运行该方法体的内容。
- 返回通知 :@AfterReturning,即在方法返回后运行该方法体的内容。
- 异常通知 :@AfterThrowing,即在方法发生异常后运行该方法体的内容。
- 环绕通知 : @Around,即在目标方法执行前和执行后都运行该方法体的内容。
package com.zwj.aopdemo.aspect;
import com.alibaba.fastjson.JSONObject;
import com.zwj.aopdemo.util.LogAopUtil;
import javassist.NotFoundException;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
/**
* @version 1.0.0
* @description:
* @author: Zev.Zhang
* @date: 2019/12/18 9:05 下午
*/
@Aspect
@Component
public class LogAspect {
private Logger log = LoggerFactory.getLogger(LogAspect.class);
//方法开始时间
private long startTimeMillis = 0;
//方法结束时间
private long endTimeMillis = 0;
/**
* @desc 定义切入点
*/
@Pointcut("@annotation(com.zwj.aopdemo.annotation.SysLog)")
public void addLog() {
}
/**
* @desc 前置通知
*/
@Before("addLog()")
public void doBeforeExec() {
startTimeMillis = System.currentTimeMillis();
log.info("方法开始..........");
}
@After("addLog()")
public void doAfterExec() {
endTimeMillis = System.currentTimeMillis();
log.info("方法结束..........");
}
@AfterReturning(pointcut = "addLog()", returning = "ret")
public void doAfterReturning(JoinPoint joinPoint, Object ret) throws ClassNotFoundException, NotFoundException {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder
.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
StringBuffer requestURL = request.getRequestURL();
log.info("请求路径:"+requestURL.toString());
Object[] args = joinPoint.getArgs();
String classType = joinPoint.getTarget().getClass().getName();
Class<?> clazz = Class.forName(classType);
String clazzName = clazz.getName();
// 获取方法名称
String methodName = joinPoint.getSignature().getName();
// 获取参数名称和值
String innerText = LogAopUtil.getNameAndArgs(this.getClass(), clazzName, methodName, args).toString();
log.info("请求类方法参数名称和值:" + innerText);
//返回值
String outerText = ret.toString();
//执行时间
Long excuteTime = endTimeMillis - startTimeMillis;
log.info("入栈[INNERTEXT]:{},[OUTERTEXT]:{},[EXCUTETIME]:{}", innerText, outerText, excuteTime);
}
}
- 编写一个rest接口进行测试
package com.zwj.aopdemo.controller;
import com.zwj.aopdemo.annotation.SysLog;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @version 1.0.0
* @description:
* @author: Zev.Zhang
* @date: 2019/12/18 9:40 下午
*/
@RestController
public class TestController {
@SysLog
@GetMapping("/test")
public String test(String name){
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "进入接口 /test";
}
}
- 启动项目,调用接口查看返回结果。
2019-12-18 22:16:18.621 INFO 41818 --- [nio-8080-exec-4] com.zwj.aopdemo.aspect.LogAspect : 方法开始..........
2019-12-18 22:16:18.723 INFO 41818 --- [nio-8080-exec-4] com.zwj.aopdemo.aspect.LogAspect : 方法结束..........
2019-12-18 22:16:18.723 INFO 41818 --- [nio-8080-exec-4] com.zwj.aopdemo.aspect.LogAspect : 请求路径:http://localhost:8080/test
2019-12-18 22:16:18.723 INFO 41818 --- [nio-8080-exec-4] com.zwj.aopdemo.aspect.LogAspect : 请求类方法参数名称和值:{"name":"aaa"}
2019-12-18 22:16:18.723 INFO 41818 --- [nio-8080-exec-4] com.zwj.aopdemo.aspect.LogAspect : 入栈[INNERTEXT]:{"name":"aaa"},[OUTERTEXT]:进入接口 /test,[EXCUTETIME]:102ms