场景
有时候 aop 的参数获取可能比较复杂,比如 id 标识的获取,每一个方法都不一样。
那么可以考虑通过 spring expression 的方式,让我们的参数获取更加灵活强大。
一、依赖
<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>
二、注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Limiter {
/**
* 使用spel表达式获取限流的key
* @return
*/
String value();
}
三、AOP切面的应用
@Aspect
@Component
public class LimiterAspect {
private ExpressionParser parser = new SpelExpressionParser();
private LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
@Pointcut("@annotation(limiter)")
public void pointcut(Limiter limiter) {
}
@Around("pointcut(limiter)")
public Object around(ProceedingJoinPoint pjp, Limiter limiter) throws Throwable {
Method method = this.getMethod(pjp);
String methodName = method.toString();
//获取方法的参数值
Object[] args = pjp.getArgs();
EvaluationContext context = this.bindParam(method, args);
//根据spel表达式获取值
Expression expression = parser.parseExpression(limiter.value());
Object key = expression.getValue(context);
//打印
System.out.println(key);
return pjp.proceed();
}
/**
* 获取当前执行的方法
*
* @param pjp
* @return
* @throws NoSuchMethodException
*/
private Method getMethod(ProceedingJoinPoint pjp) throws NoSuchMethodException {
MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
Method method = methodSignature.getMethod();
Method targetMethod = pjp.getTarget().getClass().getMethod(method.getName(), method.getParameterTypes());
return targetMethod;
}
/**
* 将方法的参数名和参数值绑定
*
* @param method 方法,根据方法获取参数名
* @param args 方法的参数值
* @return
*/
private EvaluationContext bindParam(Method method, Object[] args) {
//获取方法的参数名
String[] params = discoverer.getParameterNames(method);
//将参数名与参数值对应起来
EvaluationContext context = new StandardEvaluationContext();
for (int len = 0; len < params.length; len++) {
context.setVariable(params[len], args[len]);
}
return context;
}
四、Controller
@RestController
public class TestController {
//获取名为id的参数
@Limiter("#id")
@GetMapping("test")
public String test(Long id){
return "hello";
}
}
五、获取对象(补充)
1、注解
@Limter(value = "#testObj")
public JSONObject test01(TestObj testObj){
// ......
}
多个切点同时获取
/**
* 设置操作日志切入点 记录操作日志 在注解的位置切入代码
*/
//@Pointcut("@annotation(com.test.Limter)")
@Pointcut("@annotation(limter)")
public void operLogPointCut(Limter limter) {
}
@Pointcut("execution(public * com.test.aaa..*.*(..))")
public void operLogPointMethod() {
}
线程变量的使用,当前切面类中使用线程变量存储变量
@Aspect
@Component
public class LogAspect {
@Autowired
private LogsService logsService;
// 线程变量
private ThreadLocal<String> threadLocal = new ThreadLocal<>();
/**
* 设置操作日志切入点 记录操作日志 在注解的位置切入代码
*/
方法体中存入数据
public void savaData(){
threadLocal.set("asdf");
}
在另一个方法体中获取当前线程数据
public void savaData(){
String value = threadLocal.get();
}
切点多条件限制 &&
@AfterReturning(value = "operLogPointCut(limter) && operLogPointMethod()", returning = "returnValue")
public void saveOperLog(JoinPoint joinPoint, Limter limter, Object returnValue) {
Object[] args = joinPoint.getArgs();
// 从切面织入点处通过反射机制获取织入点处的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 获取切入点所在的方法
Method method = signature.getMethod();
EvaluationContext context = this.bindParam(method, args);
Expression expression = parser.parseExpression(limter.value());
TestObj testObj= expression.getValue(context, TestObj.class);
// ......
new Thread(() -> logsService.saveLogs(Obj...)).start();
// 存入数据库后移除当前线程变量
threadLocal.remove();
}