几个月前使用了Spring的注解@Cacheable的注解,觉得这个注解真强大。刚好接到一个业务需求,打算利用AOP来实现。解耦、整洁。
- 第一步,先自定义一个注解。设置注解的作用范围、字段等。
package com.wework.doorservice.core.eventcollector;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 支持SpEL
* @author Grace.Pan
* @date 2019/8/29
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AnnotationDemo {
String channel() default "";
}
- 第二步,利用AOP,实现该注解的逻辑处理。
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* @author Grace.Pan
* @date 2019/8/29
*/
@Slf4j
@Aspect
@Component
public class AnnotationDemoAdvisor {
@Autowired
private ThreadPoolTaskExecutor executor;
/**
* 注解的方法执行结束后的处理
*
* */
@AfterReturning(value = "@annotation(annotationDemo)", returning = "returnResult")
public void openAfterReturning(JoinPoint joinPoint, AnnotationDemo annotationDemo , Object returnResult) {
if (AnnotationDemo == null) {
return;
}
String channel = getValueByKey(eventCollectorOpenSender.channel(), joinPoint, returnResult);
// TODO 取出注解上赋值的参数做相应的业务处理。
System.out.println("channel="+channel);
}
/**
* 发生异常的处理
*
* */
@AfterThrowing(value = "@annotation(annotationDemo)", throwing = "exception")
public void openAfterThrows(JoinPoint joinPoint, AnnotationDemo annotationDemo , Exception exception) {
if (eventCollectorOpenSender == null) {
return;
}
String channel = getValueByKey(eventCollectorOpenSender.channel(), joinPoint, returnResult);
// TODO 取出注解上赋值的参数做相应的业务处理。
System.out.println("channel="+channel);
}
/**
* 获取key的值
* key 定义在注解上,支持SPEL表达式
*
* @return
*/
private String getValueByKey(String key, JoinPoint joinPoint, Object returnResult) {
if (StringUtils.isEmpty(key)) {
return null;
}
if (!key.startsWith("#")) {
return key;
}
String value = "";
if (key.startsWith("#root")) {
if (returnResult != null) {
value = getValueByProperty(key, returnResult);
}
} else {
value = getValueByParameterName(key, joinPoint);
}
return value;
}
/**
* 根据参数名获取参数值
*
* @param paramterName
* @param joinPoint
* @return
*/
private String getValueByParameterName(String paramterName, JoinPoint joinPoint) {
// get method
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
// get args
Object[] args = joinPoint.getArgs();
//获取被拦截方法参数名列表(使用Spring支持类库)
LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
String[] paraNameArr = u.getParameterNames(method);
//使用SPEL进行key的解析
ExpressionParser parser = new SpelExpressionParser();
//SPEL上下文
StandardEvaluationContext context = new StandardEvaluationContext();
//把方法参数放入SPEL上下文中
for (int i = 0; i < paraNameArr.length; i++) {
context.setVariable(paraNameArr[i], args[i]);
}
return parser.parseExpression(paramterName).getValue(context, String.class);
}
/**
* 根据属性名获取对应的属性值
*
* @param property
* @param object
* @return
*/
private String getValueByProperty(String property, Object object) {
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext(object);
return parser.parseExpression(property).getValue(context, String.class);
}
}
- 第三步:在相应的方法上使用该注解。
/**
* 切面获取的值为 入参channel的值
* */
@AnnotationDemo(channel="#channel")
public String methoDemo(String channel) {
// todo 业务代码
}
/**
* 切面获取的值就是demo
* */
@AnnotationDemo(channel="demo")
public String methoDemo(String channel) {
// todo 业务代码
}
public class Cat {
String name;
}
/**
* 切面获取的值就是cat.name的值,这里必须以root.开头,表示获取入参的属性值。
* */
@AnnotationDemo(channel="#root.name")
public String methoDemo(Cat cat) {
// todo 业务代码
}