小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
系列描述
spring 源码光学习实在是太枯燥了,所以想试试能否用它集成的一些组件应用到我们的日常开发中,这个系列的文章都是这个目的
引言
从Spring 3开始引入了Spring表达式语言,它能够以一种强大而简洁的方式将值装配到Bean属性和构造器参数中,在这个过程中所使用的表达式会在运行时计算得到值。使用SpEL你可以实现超乎想象的装配效果,这是其他装配技术很难做到的。它包含了很多特性:
使用bean的ID来引用bean;
调用方法和访问对象的属性;
对值进行算术、关系和逻辑运算;
正则表达式匹配;
集合操作;
我们时常用到SpEL表达式,例如Spring Security也支持使用SpEL表达式定义安全限制规则。另外在Spring MVC应用中使用Thymeleaf模版作为视图的话,那么这些模版可以在SpEL表达式引用模型数据。
总之,spel很强大
应用思路
其实我们可以用这个组件去实现一些方法入参的校验判断
void methodDemo(User user,Config conf){
if(user.name == null){
log.info("user 中name不能为空!!");
return;
}
}
假设如上的伪代码,那么我们在项目中可能需要做大量的判断,其实这时候可以用spel去做自动校验
这是spel直接获取属性的demo
public static void main(String[] args) {
SpelExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("#root.purchaseName");
Order order = new Order(); order.setPurchaseName("张三");
System.out.println(expression.getValue(order));
}
代码实现
// 校验规则注解类
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface AutoValid {
String sp();
String message() default "";
}
// 多个校验规则注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AutoValidL {
/**
* 表达式
*/
AutoValid[] value();
}
校验切面
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import javax.validation.constraints.NotEmpty;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;
@Aspect
@Order(100)
@Slf4j
public class AutoValidAspect {
@Pointcut("@annotation(com.eco.common.annotation.AutoValidL)")
public void PointCut() {
}
;
@Around("PointCut()")
public Object deleteCacheHandler(@NotEmpty ProceedingJoinPoint joinPoint) throws Throwable {
Object target = joinPoint.getTarget();
Signature sig = joinPoint.getSignature();
MethodSignature msig = null;
if (!(sig instanceof MethodSignature)) {
throw new IllegalArgumentException("该注解只能用于方法");
}
msig = (MethodSignature) sig;
Object[] args = joinPoint.getArgs();
Method currentMethod = null;
try {
currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
} catch (NoSuchMethodException e) {
}
if (currentMethod == null) {
return joinPoint.proceed();
}
AutoValidL annotation = currentMethod.getAnnotation(AutoValidL.class);
AutoValid[] value = annotation.value();
StandardEvaluationContext context = new StandardEvaluationContext();
// 将每个方法入参变量注入到spel 解析容器
Parameter[] parameters = currentMethod.getParameters();
for (int i = 0; i < parameters.length; i++) {
context.setVariable(parameters[i].getName(), args[i]);
}
// 如果方法入参只有一个,则默认为 root
if (parameters.length == 1) {
context.setRootObject(args[0]);
}
List<String> errorMsg = new ArrayList<>();
for (AutoValid autoValid : value) {
ExpressionParser parser = new SpelExpressionParser();
Boolean aBoolean = null;
try {
// 校验每个规则
aBoolean = parser.parseExpression(autoValid.sp()).getValue(context, Boolean.class);
} catch (Exception e) {
e.printStackTrace();
}
if (BooleanUtils.isFalse(aBoolean)) {
errorMsg.add(autoValid.message());
}
}
if (CollectionUtil.isEmpty(errorMsg)) {
return joinPoint.proceed();
}else {
throw new ServiceException(String.join("\n", errorMsg));
}
}
}
使用
@AutoValidL(value = {
@AutoValid(sp = "#stationInfo.tkId > #userInfo.type")
})
public void valid(StationInfo stationInfo, UserInfo userInfo) {
}