Spel + AOP动态赋值
1、注解Annotation
/**
* created by guanjian on 2020/11/30 19:58
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Spel {
/**
* 参数列表 --- 解析map类型
*/
String variables() default "";
/**
* 业务ID --- 解析字符串类型
*/
String bizId() default "";
/**
* 操作类型 --- 枚举类型
*/
Operation operation();
}
2、切面Aspect
/**
* created by guanjian on 2020/11/30 20:00
*/
@Aspect
@Component("spelAspect")
public class SpelAspect {
/**
* spEl parser
*/
private final static ExpressionParser parser = new SpelExpressionParser();
/**
* Param discover
*/
private final static ParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
@Around("@annotation(com.github.java.learning.spring.spel.annotation.Spel)")
public Object around(ProceedingJoinPoint pjp) {
/**
* 解析MAP类型
*/
String variables = getAnnotation(pjp).variables();
System.out.println("variables= " + variables);
Map<String, Object> map = (Map) parser.parseExpression(variables).getValue();
//1、解析map {name:'#req.name',age:'#req.age'}
System.out.println("map= " + map);
//2、获取到方法形参
String[] params = discoverer.getParameterNames(getMethod(pjp));
System.out.println("形参param= " + JSON.toJSONString(params));
//3、获取到方法实参
Object[] args = getArgs(pjp);
System.out.println("实参args= " + JSON.toJSONString(args));
//4、构建spel的context
EvaluationContext context = new StandardEvaluationContext();
for (int index = 0; index < params.length; index++) {
//匹配形参、实参的对应关系注入到context环境中
context.setVariable(params[index], args[index]);
}
//5、遍历map={name=#req.name, age=#req.age},把对应的value的形参赋值成实参值
map.forEach((k, spel) -> {
String value = parser.parseExpression(String.valueOf(spel)).getValue(context, String.class);
System.out.format("key=%s,spel=%s,value=%s\n", k, spel, value);
});
/**
* 解析String类型
*/
String bizId = getAnnotation(pjp).bizId();
System.out.println("bizId= " + parser.parseExpression(bizId).getValue(context, String.class));
//target method execute...
Object result = null;
try {
result = pjp.proceed();
} catch (Throwable e) {
e.getStackTrace();
} finally {
return result;
}
}
protected Method getMethod(ProceedingJoinPoint pjp) {
Method method = null;
try {
MethodSignature ms = (MethodSignature) pjp.getSignature();
method = pjp.getTarget()
.getClass()
.getMethod(ms.getName(), ms.getParameterTypes());
} catch (NoSuchMethodException e) {
//ignore
}
return method;
}
protected Object[] getArgs(ProceedingJoinPoint pjp) {
return pjp.getArgs();
}
protected static Spel getAnnotation(ProceedingJoinPoint pjp) {
Annotation annotation = null;
try {
MethodSignature ms = (MethodSignature) pjp.getSignature();
annotation = pjp.getTarget()
.getClass()
.getMethod(ms.getName(), ms.getParameterTypes())
.getAnnotation(Spel.class);
} catch (Exception e) {
e.printStackTrace();
}
return (Spel) annotation;
}
}
3、业务类Service
/**
* created by guanjian on 2020/11/30 20:13
*/
@Component("spelService")
public class SpelService {
@Spel(
variables = "{name:'#req.name',age:'#req.age'}",
bizId = "#req.bizId",
operation = Operation.START
)
public Object test(User req) {
return new Object();
}
}
4、测试类Test
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/spring-config.xml")
public class SpelTest{
@Autowired
private SpelService spelService;
@Test
public void test() {
User user = new User("zhangsan");
user.setAge(10);
user.setBizId("12312212521512");
spelService.test(user);
}
}
控制台打印输出
variables= {name:'#req.name',age:'#req.age'}
map= {name=#req.name, age=#req.age}
形参param= ["req"]
实参args= [{"age":10,"bizId":"12312212521512","name":"zhangsan"}]
key=name,spel=#req.name,value=zhangsan
key=age,spel=#req.age,value=10
bizId= 12312212521512
5、用法总结
Aspect不支持Map、List等常用的数据结构做传参,但实际业务场景中需要使用的话需要做些变通。基于Spel提供的强大解析功能能够非常方便的拆解Map、List等,通过ParameterNameDiscoverer和Aop进行形参、实参解析和匹配,根据配置的Spel表达式进行解析,最终达到动态赋值的目的。
6、参考
https://blog.csdn.net/shichen2010/article/details/96504008