spring 组件应用(一)强大的SPEL 表达式

487 阅读2分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

系列描述

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) {

}