本示例为一个基于注解的切面编程实践,该切面功能:主要是用来统计被注解标识的方法执行时的耗时时长
1,首先 配置maven依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
2,其次定义一个注解
package com.example.interceptor.aop;
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TimeLog {
String value() default "";
}
3,再定义一个基于注解的切面及编写相应的前置、后置增强方法
package com.example.interceptor.aop;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* 监控日志拦切面截器
* @author weichangzhong
* @Aspect 作用是把当前类标识为一个切面供容器读取
*/
@Slf4j
@Aspect
@Component
@Order(1)
public class LogAspect {
private ThreadLocal<Long> startTime = new ThreadLocal<>();
/**
* 切入点 JoinPoint的集合,是程序中需要注入Advice的位置的集合,指明Advice要在什么样的条件下才能被触发,在程序中主要体现为书写切入点表达式
* 由下列方式来定义或者通过 &&、 ||、 !、 的方式进行组合, 如:
* execution:用于匹配方法执行的连接点
* @annotation:用于匹配当前执行方法持有指定注解的方法
*/
@Pointcut("@annotation(com.example.interceptor.aop.TimeLog)")
public void logPointCut() {
}
/**
* 标识一个前置增强方法,相当于BeforeAdvice的功能. 在切点方法之前执行
*/
@Before("logPointCut()")
public void before() {
log.info("--------------before----------logPointCut");
startTime.set(System.currentTimeMillis());
}
/**
* 后置增强,似于AfterReturningAdvice, 方法返回后、正常退出时执行
* returning的值是对应的是业务方法 的返回值,且其名称要与afterReturning方法的参数名相同
*/
@AfterReturning(pointcut = "logPointCut()", returning = "response")
public void afterReturning(JoinPoint joinPoint, Object response) {
Long cost = System.currentTimeMillis() - startTime.get();
log.info("--------------afterReturn--------logPointCut, time: {}", cost);
//获取方法类型
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
String className = joinPoint.getTarget().getClass().getName();
TimeLog annotation = method.getAnnotation(TimeLog.class);
if(annotation == null) {
log.info("ClassName: {}, methodName: {}", className, method.getName());
}else {
log.info("The value of annotation is: {}, className: {}, methodName: {}", annotation.value(), className, method.getName());
}
//获取请求参数
Object[] args = joinPoint.getArgs();
if (ArrayUtils.isNotEmpty(args)) {
//httpServletRequest、httpServletResponse、multipartFile的参数为异步组装的,不能序列化,因此需要过滤掉
List<Object> filterArg = Arrays.stream(args).filter(
arg -> !(arg instanceof HttpServletRequest) && !(arg instanceof HttpServletResponse) && !(arg instanceof MultipartFile)
).collect(Collectors.toList());
log.info("The parameter is: {}", filterArg.toString());
}else{
log.info("The parameter is: {}", args);
}
}
/**
* 异常抛出增强,相当于ThrowsAdvice. 切点方法抛异常执行
* @param joinPoint
* @param ex
*/
@AfterThrowing(pointcut = "logPointCut()", throwing = "ex")
public void afterThrowing(JoinPoint joinPoint, Throwable ex) {
Long cost = System.currentTimeMillis() - startTime.get();
if (log.isDebugEnabled()) {
log.debug("----------afterThrow--------logPointCut, time: {}", cost);
}
}
}
3,最后定义一个被拦截的controller方法
package com.example.interceptor.controller;
import com.example.interceptor.aop.TimeLog;
import com.example.interceptor.service.TestService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* @author weichangzhong
*/
@RestController
@RequestMapping("/test")
public class TestController {
@Resource
private TestService testService;
@GetMapping
@TimeLog("test-1")
public String getTest(@RequestParam String name, String email) {
return testService.getTestString();
}
}
service层:
package com.example.interceptor.service;
import com.example.interceptor.aop.TimeLog;
import org.springframework.stereotype.Service;
/**
* @author weichangzhong
*/
@Service
public class TestService {
@TimeLog
public String getTestString() {
return "test response";
}
}
启动服务后,调用接口:localhost:8080/test?name=me&email=emailTest