本文转载自微信公众号「Java大厂面试官」,从零作者laker。搭建达式的简转载本文请联系Java大厂面试官公众号。脚手架S介和 简介 算术运算符 关系运算符 逻辑运算符 三目运算符 正则运算符 访问List和Map 以编程方式解析表达式 ExpressionParser EvaluationContext 高级应用 Bean引用 #this和#root 表达式模板 实战 1.注册常用的实战用户、Request、应用Response、从零工具类到上下文 2.访问Spring容器中的搭建达式的简任意Bean并调用其方法 3.自定义注解+获取方法入参 Sping EL(Spring Expression Language 简称 SpEL)是一种强大的表达式语言,支持在运行时查询和操作对象,脚手架S介和它可以与 XML 或基于注解的实战 Spring 配置一起使用。语言语法类似于统一 EL,应用但提供了额外的从零功能,方法调用和字符串模板功能。
虽然还有其他几种可用的搭建达式的简 Java 表达式语言,OGNL、脚手架S介和MVEL 和 JBoss EL等,实战但创建 Spring 表达式语言是应用为了向 Spring 社区提供一种受良好支持的表达式语言,SpEL基于与技术无关的 API,允许在需要时集成其他表达式语言实现。
SpEL支持以下运算符
类型操作符算术运算符 +, -, *, /, %, ^, div, mod 关系运算符 <, >, ==, !=, <=, >=, lt, gt, eq, ne, le, ge 逻辑运算符 and, or, not, &&, ||, ! 三目运算符 ?: 正则运算符 matches以注解的方式举例如下:
SpEL表达式以#符号开头,并用大括号括起来:#{expression}。可以以类似的源码下载方式引用属性,以$符号开头,并用大括号括起来:${property.name}。属性占位符不能包含 SpEL 表达式,但表达式可以包含属性引用.
#{${someProperty} + 2} someProperty 的值为 2,计算结果为 4。支持所有基本算术运算符。
@Value("#{19 + 1}") // 20 private double add; @Value("#{String1 + string2}") // "String1 string2" private String addString; @Value("#{20 - 1}") // 19 private double subtract; @Value("#{10 * 2}") // 20 private double multiply; @Value("#{36 / 2}") // 19 private double divide; @Value("#{36 div 2}") // 18, the same as for / operator private double divideAlphabetic; @Value("#{37 % 10}") // 7 private double modulo; @Value("#{37 mod 10}") // 7, the same as for % operator private double moduloAlphabetic; @Value("#{2 ^ 9}") // 512 private double powerOf; @Value("#{(2 + 2) * 2 + 9}") // 17 private double brackets;当计算表达式解析properties, methods, fields,并帮助执行类型转换, 使用接口EvaluationContext 这是一个开箱即用的实现, StandardEvaluationContext,使用反射来操纵对象, 缓存java.lang.reflect的Method,Field,和Constructor实例 提高性能。
class Simple { public List<Boolean> booleanList = new ArrayList<Boolean>(); } Simple simple = new Simple(); simple.booleanList.add(true); StandardEvaluationContext simpleContext = new StandardEvaluationContext(simple); // false is passed in here as a string. SpEL and the conversion service will // correctly recognize that it needs to be a Boolean and convert it parser.parseExpression("booleanList[0]").setValue(simpleContext, "false"); // b will be false Boolean b = simple.booleanList.get(0);如果解析上下文已经配置,那么bean解析器能够 从表达式使用(@)符号查找bean类。
ExpressionParser parser = new SpelExpressionParser(); StandardEvaluationContext context = new StandardEvaluationContext(); context.setBeanResolver(new MyBeanResolver()); // This will end up calling resolve(context,"foo") on MyBeanResolver during evaluation Object bean = parser.parseExpression("@foo").getValue(context);如果需要获取Bean工厂本身而不是它构造的Bean,可以使用&Bean名称。
Object bean = parser.parseExpression("&foo").getValue(context);#this和#root代表了表达式上下文的对象,#root就是云南idc服务商当前的表达式上下文对象,#this则根据当前求值环境的不同而变化。下面的例子中,#this即每次循环的值。
// create an array of integers List<Integer> primes = new ArrayList<Integer>(); primes.addAll(Arrays.asList(2,3,5,7,11,13,17)); // create parser and set variable primes as the array of integers ExpressionParser parser = new SpelExpressionParser(); StandardEvaluationContext context = new StandardEvaluationContext(); context.setVariable("primes",primes); // all prime numbers > 10 from the list (using selection ?{...}) // evaluates to [11, 13, 17] List<Integer> primesGreaterThanTen = (List<Integer>) parser.parseExpression("#primes.?[#this>10]").getValue(context);表达式模板使用#{}定义,它允许我们混合多种结果。下面就是一个例子,首先Spring会先对模板中的表达式求值,在这里是返回一个随机值,然后将结果和外部的表达式组合起来。最终的结果就向下面这样了。
String randomPhrase = parser.parseExpression( "random number is #{T(java.lang.Math).random()}", new TemplateParserContext()).getValue(String.class); // 结果是 "random number is 0.7038186818312008"以上都是官网的理论值,现总结下项目实战中常用的技巧。
注册常用的用户、Request、云服务器Response、工具类到上下文,以便于在表达式中引用业务无关的对象。
ExpressionParser parser = new SpelExpressionParser();// 这个是线程安全的 定义为全局变量。 String expression = "#{user.id + request.getQuringString()}"; Expression exp = parser.parseExpression(expression); EvaluationContext context = new StandardEvaluationContext(); context.setVariable("user", user); context.setVariable("request", request); context.setVariable("dateUtils", dateUtils); String value = (String) exp.getValue(context);要访问 bean 对象,那么EvaluationContext中需要包含 bean 对象才行,可以借助BeanResolver来实现,如context.setBeanResolver(new BeanFactoryResolver(applicationContext)),访问 bean 的前缀修饰为@符号。
我们需要获取ApplicationContext,可以继承ApplicationContextAware,或者使用@Autowired获取。
StandardEvaluationContext context = new StandardEvaluationContext(); context.setBeanResolver(new BeanFactoryResolver(applicationContext)); // 获取bean对象 LakerService lakerService = parser.parseExpression("@lakerService").getValue(context, LakerService.class); System.out.println("lakerService : " + lakerService); // 访问bean方法 String result = parser.parseExpression("@lakerService.print(lakernote)").getValue(context, String.class); System.out.println("return result : " + result);1.定义自定义注解
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Laker { String value(); }2.针对自定义注解定义切面拦截
@Aspect @Component @Slf4j public class LakerAspect { private SpelExpressionParser parserSpel = new SpelExpressionParser(); private DefaultParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer(); @Pointcut("@annotation(com.laker.map.moudle.spel.Laker)") private void elPoint() { } @Around("elPoint()") public void cache(ProceedingJoinPoint pjp) { Method method = ((MethodSignature) pjp.getSignature()).getMethod(); Laker laker = method.getAnnotation(Laker.class); String value = getValue(laker.value(), pjp); log.info(value); try { pjp.proceed(); } catch (Throwable e) { log.error("", e); } } public String getValue(String key, ProceedingJoinPoint pjp) { Expression expression = parserSpel.parseExpression(key); EvaluationContext context = new StandardEvaluationContext(); User user = new User(); user.id = 123L; context.setVariable("user", user);// 模拟设置用户信息 MethodSignature methodSignature = (MethodSignature) pjp.getSignature(); Object[] args = pjp.getArgs(); String[] paramNames = parameterNameDiscoverer.getParameterNames(methodSignature.getMethod()); for (int i = 0; i < args.length; i++) { context.setVariable(paramNames[i], args[i]); } return expression.getValue(context).toString(); } class User { public Long id; } }3.在业务类上使用自定义注解
@Service public class LakerService { @Laker("#user.id + #msg") //要符合SpEL表达式格式 public void print(String msg) { System.out.println(msg); } }参考:
https://docs.spring.io/spring-framework/docs/current/reference/html/ https://www.baeldung.com/spring-expression-language