认识SpEL表达式
前言
最近项目接入苞米豆的lock4j用于分布式的锁控制,良好的控制在多台服务器下请求分流导致的数据重复问题,使用上也比较简单,在需要分布式锁的方法上添加一个@Lock4j注解并添加相应的参数即可,在使用中发现其中有一个属性keys = {"#userId", "#user.sex"},并且支持自定义重写分布式锁键的生成策略。在好奇心的驱使下,查看了默认实现的分布式锁键生成策略是通过SpEL的方式解析参数信息。
SpEL概述
Spring表达式语言的全拼为Spring Expression Language,缩写为SpEL。并且SpEL属于spring-core模块,不直接与Spring绑定,是一个独立模块,不依赖于其他模块,可以单独使用。
核心接口
- 解析器
ExpressionParser,用于将字符串表达式转换为Expression表达式对象。 
- 表达式
Expression,最后通过它的getValute方法对表达式进行计算取值。 
- 上下文
EvaluationContext,通过上下文对象结合表达式来计算最后的结果。 
简单使用
1 2 3 4 5 6 7 8
   | public static void main(String[] args) {      ExpressionParser parser = new SpelExpressionParser();      Expression expression = parser.parseExpression("'Hello' + 'World'");      System.out.println(expression.getValue(context)); }
  | 
 
进行一些简单的运算
1 2 3 4 5 6 7 8 9 10 11 12 13
   | public static void main(String[] args) {      ExpressionParser parser = new SpelExpressionParser();         System.out.println(parser.parseExpression("'Hello' + 'World'").getValue(String.class));      System.out.println(parser.parseExpression("1+2").getValue());      System.out.println(parser.parseExpression("1>2").getValue());      System.out.println(parser.parseExpression("2>1 and (!true)").getValue()); }
  | 
 
通过ParseContext对象设置自定义的解析规则:这里设置表达式的解析前缀为#{解析后缀为},最后通过表达式对象expression.getValue()获取到表达式中的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
   | public static void main(String[] args) {   ExpressionParser parser = new SpelExpressionParser();   ParserContext parserContext = new ParserContext() {     @Override     public boolean isTemplate() {       return true;     }
      @Override     public String getExpressionPrefix() {       return "#{";     }
      @Override     public String getExpressionSuffix() {       return "}";     }   };   String template = "#{'Hello'}#{'World!'}";   Expression expression = parser.parseExpression(template, parserContext);   System.out.println(expression.getValue()); }
  | 
 
还有很多不同的取值方式,比如参数(上下文)是个对象,获取这个对象中的某个属性;或者参数是一个List获取某一个索引值;又或者是一个Map对象,根据某个Key获取对应的值等等。
实际应用
    如果平时有使用Spring框架应该都会有用到比如@Value注解,就是通过SpEL方式进行赋值。
1 2 3 4 5 6 7 8 9 10
   | public class UserFacade { 		   	     @Value("#{'tom'}")     private String name;   	   	     @Value("#{user.value}")     private String value; }
  | 
 
在比如接触过Spring Security或者Shiro等身份验证和授权的框架中,对不同的角色有不同的接口权限,会使用到如下场景,其中对@PreAuthorize("hasAuthority('ROLE_DMIN'))中hasAuthority('ROLE_ADMIN')就是通过SpEL进行参数解析后,对当前用户的角色进行校验。
1 2 3 4 5 6 7 8 9 10 11 12 13
   | @RestController @RequestMapping("/admin/user") public class UserController {
      
 
      @PreAuthorize("hasAuthority('ROLE_ADMIN'))     @PostMapping("/getUserById/{userId}")     public Result<List<SysUser>> getUserById(String userId) {         return new Result<>(userFacade.getUserById(userId));     } }
   | 
 
重构
    之前在项目中记录系统中一些敏感接口的请求日志信息,采用的是AOP的方式,在请求进入控制层之前拦截进入AOP的切面方法,但是记录的日志部分关键信息需要从请求的参数中获取,在之前的实现中是通过约定一种表达式,对应列表List、Map、bean对象的取值是自实现,且仅仅支持二级取值,确实在使用上有很大的缺陷。这种场景下,就可以使用SpEL进行方法参数解析,省了重复造轮子的过程,且使用上更为灵活。
SpEL结合AOP重构请求日志保存,这边只做简单的通过SpEL方式进行对象等取值处理,不考虑具体实际场景中的复杂业务逻辑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
   | 
 
 
 
  @RestController @RequestMapping("basic") @Api(tags = "测试") public class BasicVersionController {
      @ApiOperation(value="测试",notes="测试")     @PostMapping("test")     @ControllerMethodLog(name = "测试保存请求日志", description = "测试保存请求日志")     @LogAssistParams(value={             @LogAssistParam(logField="projectName",objField="#bidProjectInfo.id"),             @LogAssistParam(logField="id",objField="#bidProjectInfo.projectName")     })     public RestResponse<BidPackageInvitationVo> test(@RequestBody ProjectInfo projectInfo){         return null;     } }
 
  | 
 
AOP切面类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
   | 
 
 
 
 
  @Aspect @Component @Slf4j public class OperationTestLogAspect {
     @Autowired    private OperationLogFacade operationLogFacade;
     
 
     @Pointcut("@annotation(cn.com.xiaocainiaoya.annotation.ControllerMethodLog)")    public void operationLog() {    }
     @Around("operationLog()")    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {       OperationLog operationLog = new OperationLog();       operationLog.setStatus(1);
        Object thing = null;       try {                    thing = joinPoint.proceed();          operationLog.setOperEndTime(DateTime.now().toJdkDate());          return thing;       } catch (Throwable e) {          log.error(e.getMessage(), e);          operationLog.setStatus(0);          operationLog.setResultContext(e.getMessage());          throw e;       } finally {          insertOperationLog(operationLog, joinPoint, thing);       }    }
     private static final ParameterNameDiscoverer NAME_DISCOVERER = new DefaultParameterNameDiscoverer();
     private static final ExpressionParser PARSER = new SpelExpressionParser();
     
 
 
 
 
 
 
 
 
     private void insertOperationLog(OperationLog operationLog, ProceedingJoinPoint joinPoint, Object thing) {       MethodSignature signature = (MethodSignature) joinPoint.getSignature();       ControllerMethodLog methodAnnotation = signature.getMethod().getAnnotation(ControllerMethodLog.class);       Api typeAnnotation = (Api) signature.getDeclaringType().getAnnotation(Api.class);              if (methodAnnotation == null || typeAnnotation == null) {          return;       }       LogAssistParams logAssistParams = signature.getMethod().getAnnotation(LogAssistParams.class);       if(methodAnnotation == null){          return ;       }       LogAssistParam[] assistParams = logAssistParams.value();       if(ObjectUtil.isNull(assistParams) || assistParams.length == 0){          return ;       }       for(int i = 0; i < assistParams.length; i++){          
 
           EvaluationContext context = new MethodBasedEvaluationContext((Object) null, signature.getMethod(), joinPoint.getArgs(), NAME_DISCOVERER);          String value = (String)PARSER.parseExpression(assistParams[i].objField()).getValue(context);          ReflectUtil.setFieldValue(operationLog, assistParams[i].logField(), value);       }       operationLogFacade.insertSelective(operationLog);    } }
 
  | 
 
博客地址:https://xiaocainiaoya.github.io/
联系方式:xiaocainiaoya@foxmail.com
