一、SpringAOP简述
SpringAOP可以帮助我们在不修改源代码的前提下实现功能增强,其底层实现原理基于Java动态代理或者CGLIB。
之前我们使用 execution表达式指定被AOP增强的方法:(execution关键字用于描述哪些方法需要切面逻辑)
但是这样使用非常不灵活,因为并不是Service中所有的方法都需要被增强。
其实我们可以参考Spring声明式事务注解@Transactional,在项目中利用自定义注解实现了大量共性需求。
SpringAOP+自定义注解的应用场景:
- 收集上报指定关键方法的入参、执行时间、返回结果等关键信息,用作后期调优处理;
- 关键方法在幂等性前置校验(基于本地消息表);
- 类似于Spring-Retryt模块,提供关键方法多次调用重试机制;
- 提供关键方法自定义的快速熔断、服务降级等职责;
- 关键方法的共性入参校验、权限校验;
- 关键方法在执行后的扩展行为,例如记录日志、启动其他任务等;
- …
二、SpringAOP+自定义注解使用
1、创建springboot工程,在pom.xml中引入aspectjweaver依赖
<!-- AOP切面编程框架 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<!--<version>1.9.4</version>-->
</dependency>
2、编写自定义注解(使用@interface关键字定义注解)
package cn.baidou.dianping.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义方法注解
*/
// @Target(ElementType.METHOD) 用来约束这个自定义注解只能用在方法上
@Target(ElementType.METHOD)
// @Retention 用来控制注解的生命周期,RUNTIME表示这个注解一直存活 (作用在源码阶段,字节码文件阶段,运行阶段)
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodExporter {
}
3、编写切面类
package cn.baidou.dianping.aop;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
/**
* 获取目标入参、执行时间、执行过程、返回结果等细节并打印到日志上
*
* @author 白豆五
* @version 2023/06/15
* @since JDK8
*/
@Aspect //设置当前类为切面类
@Component //配置成Spring管理的bean
@Slf4j
public class MethodExporterAspect {
/*
@Around:环绕通知,最强大的通知类型,可以控制方法入参、执行、返回结果等各方面细节
"@annotation(xxx.MethodExporter)":表示任何添加@MethodExporter注解的目标方法都将在执行方法前先执行该切面方法
*/
@Around("@annotation(cn.baidou.dianping.annotation.MethodExporter)")
public Object methodExporter(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();//开始时间
Object proceed = joinPoint.proceed(); // 执行目标方法,获取方法返回值
long endTime = System.currentTimeMillis(); //结束时间
ObjectMapper mapper = new ObjectMapper();
// 将入参JSON序列化
String jsonParam = mapper.writeValueAsString(joinPoint.getArgs());//joinPoint.getArgs()获取目标方法的参数
// 将返回结果JSON序列化
String jsonResult = null;
if (jsonResult != null) {
jsonResult = mapper.writeValueAsString(proceed);//mapper.writeValueAsString()可用于将任何Java值序列化为字符串
} else {
jsonResult = "null";
}
// 模拟上报过程
log.debug("正在上报服务器调用过程:\ntarget:{}.{}()\nexecution:{}ms,\nparameter:{}\nresult:{}",
joinPoint.getTarget().getClass().getSimpleName(),
joinPoint.getSignature().getName(),
(endTime - startTime),
jsonParam,
jsonResult);
return proceed;
}
}
4、编写测试代码
package cn.baidou.dianping.controller;
import cn.baidou.dianping.annotation.MethodExporter;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.TimeUnit;
/**
* @author 白豆五
* @version 2023/06/15
* @since JDK8
*/
@RestController
@RequestMapping("/test")
public class TestController {
@MethodExporter
@GetMapping("/list")
public Map test() {
Map resultMap = new LinkedHashMap();
resultMap.put("code", "200");
resultMap.put("message", "ok");
try {
TimeUnit.SECONDS.sleep(new Random().nextInt(10));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return resultMap;
}
}
5、重启项目,测试接口
控制台输出: