文章目录1. 简单介绍1.1. 什么是 Spring EL1.2. 为什么要使用 Spring EL1.3. 如何使用 Spring EL2. 简单使用3. EL 表达式解析引擎3.1. 带缓存的 EL 表达式工具方法3.2. 设置上下文的 EL 表达式4. 总结5. 补充示例5.1. 使用 Spring EL 进行赋值5.2. 使用 Spring EL 进行计算本文主体部分来自于KILLKISS的 SpringEL详解及应用。对文中部分代码做了校验和补充并添加了自己的部分代码。1. 简单介绍1.1. 什么是 Spring ELSpring3 中引入了 Spring 表达式语言 — Spring ELSpEL 是一种强大简洁的装配 Bean 的方式它可以通过运行期间执行的表达式将值装配到我们的属性或构造函数当中更可以调用 JDK 中提供的静态常量获取外部 Properties 文件中的的配置。1.2. 为什么要使用 Spring EL我们平常通过配置文件或注解注入的 Bean其实都可以称为静态性注入试想一下如果我的 Bean A 中有变量 A它的值需要根据 Bean B 的 B 变量为参考在这个场景下静态注入就显得非常无力而 Spring3 增加的 Spring EL 就可以完全满足这种需求而且还可以对不同 Bean 的字段进行计算再进行赋值功能非常强大。1.3. 如何使用 Spring ELSpring EL 从名字来看就能看出和 EL 是有关系的Spring EL 的使用和 EL 表达式的使用非常相似EL 表达式在 JSP 页面更方便的获取后台中的值而 Spring EL 就是为了更方便获取 Spring 容器中的 Bean 的值EL 使用${}而 Spring EL 使用#{}进行表达式的声明。2. 简单使用辅助类package com.example.spel.bean; import org.springframework.stereotype.Component; import java.util.*; Component public class TestConstant { public static final String STR 测试SpEL; public String nickname 一线大码; public String name 笑傲江湖; public int num 5; public ListString testList Arrays.asList(aaa, bbb, ccc); public Map testMap new HashMap() {{ put(aaa, 元宇宙算法); put(hello, world); }}; public List cityList new ArrayListCity() {{ add(new City(aaa, 500)); add(new City(bbb, 600)); add(new City(ccc, 1000)); add(new City(ddd, 1000)); add(new City(eee, 2000)); add(new City(fff, 3000)); }}; public String showProperty() { return Hello; } public String showProperty(String str) { return Hello str !; } } package com.example.spel.bean; import lombok.AllArgsConstructor; import lombok.Data; Data AllArgsConstructor public class City { private String name; private long population; }测试代码package com.example.spel.el; import com.example.spel.bean.City; import com.example.spel.bean.TestConstant; import lombok.ToString; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.util.List; ToString Component public class TestSpringEL { /** * 注入简单值输出num为5 */ Value(#{5}) private Integer num; Value(#{rain.toUpperCase()}) private String name; //注入bean访问属性和方法 /** * 注入ID为testConstant的Bean */ Value(#{testConstant}) private TestConstant testConstant; /** * 注入ID为testConstant的Bean中的STR常量/变量 */ Value(#{testConstant.STR}) private String str; /** * 调用无参方法 */ Value(#{testConstant.showProperty()}) private String method1; /** * 调用有参方法接收字符串 */ Value(#{testConstant.showProperty(World)}) private String method2; /** * 方法返回的String为大写 */ Value(#{testConstant.showProperty().toUpperCase()}) private String method3; /** * 使用method3这种方式如果showProperty返回为null将会抛出NullPointerException可以使用以下方式避免。 * 使用?.符号表示如果左边的值为null将不执行右边方法 */ Value(#{testConstant.showProperty()?.toUpperCase}) private String method4; //注入JDK中的工具类常量或调用工具类的方法 /** * 获取Math的PI常量 */ Value(#{T(java.lang.Math).PI}) private double pi; /** * 调用random方法获取返回值 */ Value(#{T(java.lang.Math).random()}) private double ramdom; /** * 获取文件路径符号 */ Value(#{T(java.io.File).separator}) private String separator; //使用SpringEL进行运算及逻辑操作 /** * 拼接字符串 */ Value(#{testConstant.nickname testConstant.name}) private String concatString; /** * 对数字类型进行运算 */ Value(#{ 3 * T(java.lang.Math).PI testConstant.num}) private double operation; /** * 进行逻辑运算 */ Value(#{testConstant.num 100 and testConstant.num 200}) private boolean logicOperation; /** * 进行或非逻辑操作 */ Value(#{not(testConstant.num 100 or testConstant.num 200)}) private Boolean logicOperation2; /** * 使用三元运算符 */ Value(#{testConstant.num 100 ? testConstant.num : testConstant.num 100}) private Integer logicOperation3; //SpringEL使用正则表达式 /** * 验证是否邮箱地址正则表达式 */ Value(#{testConstant.STR matches \w([\.-]?\w)*\w([\.-]?\w)*(\.\w{2,3})}) private boolean regularExpression; //TestConstant类中有名为testList的List变量和名为testMap的Map变量 /** * 获取下标为0的元素 */ Value(#{testConstant.testList[0]}) private String firstStr; /** * 获取下标为0元素的大写形式 */ Value(#{testConstant.testList[0]?.toUpperCase()}) private String upperFirstStr; /** * 获取map中key为hello的value */ Value(#{testConstant.testMap[hello]}) private String mapValue; /** * 根据testList下标为0元素作为key获取testMap的value */ Value(#{testConstant.testMap[testConstant.testList[0]]}) private String mapValueByTestList; //声明City类有population人口属性。testConstant拥有名为cityList的City类List集合 /** * 过滤testConstant中cityList集合population属性大于1000的全部数据注入到本属性 */ Value(#{testConstant.cityList.?[population 1000]}) private ListCity cityList; /** * 过滤testConstant中cityList集合population属性等于1000的第一条数据注入到本属性 */ Value(#{testConstant.cityList.^[population 1000]}) private City city; /** * 过滤testConstant中cityList集合population属性小于1000的最后一条数据注入到本属性 */ Value(#{testConstant.cityList.$[population 1000]}) private City city2; /* * 首先为city增加name属性代表城市的名称 */ /** * 假如我们在过滤城市集合后只想保留城市的名称可以使用如下方式进行投影 */ Value(#{testConstant.cityList.?[population 1000].![name]}) private ListString cityName; }执行测试package com.example.spel; import com.example.spel.el.TestSpringEL; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; /** * author wangbo * date 2021/11/26 */ SpringBootTest class Test1 { Autowired private TestSpringEL testSpringEL; Test void test(){ System.out.println(testSpringEL); } }测试结果TestSpringEL(num5, namerain, name1RAIN, testConstantcom.example.spel.bean.TestConstant35cd68d4, str测试SpEL, method1Hello, method2Hello World!, method3HELLO, method4HELLO, pi3.141592653589793, ramdom0.38512203414759527, separator, concatString一线大码 笑傲江湖, operation14.42477796076938, logicOperationfalse, logicOperation2false, logicOperation3105, regularExpressionfalse, firstStraaa, upperFirstStrAAA, mapValueworld, mapValueByTestList元宇宙算法, cityList[City(nameeee, population2000), City(namefff, population3000)], cityCity(nameccc, population1000), city2City(namebbb, population600), cityName[eee, fff])3. EL 表达式解析引擎3.1. 带缓存的 EL 表达式工具方法package gtcom.governance.impl.util; import org.apache.commons.lang3.StringUtils; import org.springframework.expression.Expression; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * SpEL表达式解析 * * author wangbo * date 2021/11/26 */ public class ExpressionUtils { private static final ExpressionParser EXPRESSION_PARSER new SpelExpressionParser(); private static final MapString, Expression EXPRESSION_CACHE new ConcurrentHashMap(); /** * 获取解析后的表达式 * * param expression EL表达式字符串 * return 解析后的表达式如果之前已经解析过则返回缓存的表达式 */ public static Expression getExpression(String expression) { if (StringUtils.isBlank(expression)) { return null; } expression expression.trim(); return EXPRESSION_CACHE.computeIfAbsent(expression, EXPRESSION_PARSER::parseExpression); } }上面工具的测试代码package gtcom.governance.impl.util; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import org.springframework.expression.Expression; import java.util.Objects; /** * 测试SpEL表达式解析 * * author wangbo * date 2021/11/26 */ public class TestExpression { public static void main(String[] args) throws JsonProcessingException { ObjectMapper mapper new ObjectMapper(); ObjectNode root mapper.createObjectNode(); root.put(pubTime, 11111); ObjectNode supplier root.putObject(supplier); supplier.put(sourceType, TestSourceType); supplier.put(comFrom, QX); System.out.println(mapper.writeValueAsString(root)); String sourceType root.get(supplier).path(sourceType).asText(); System.out.println(sourceType); String expression1 get(supplier).path(sourceType).asText; Expression expr1 ExpressionUtils.getExpression(expression1); System.out.println(expr1); String sourceType1 Objects.requireNonNull(expr1).getValue(root, String.class); System.out.println(sourceType1); String expression2 get(supplier).path(sourceType).asText; Expression expr2 ExpressionUtils.getExpression(expression2); System.out.println(expr2); String sourceType2 Objects.requireNonNull(expr2).getValue(root, String.class); System.out.println(sourceType2); } }3.2. 设置上下文的 EL 表达式package com.example.spel; import org.springframework.expression.Expression; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; public class TestStringSubExpression { public static void main(String[] args) { String expressionStr hello world.toUpperCase().substring(1,5); //指定SpelExpressionParser解析器实现类 ExpressionParser parser new SpelExpressionParser(); //解析表达式 Expression expression parser.parseExpression(expressionStr); System.out.println(expression.getValue()); } }等价设置了上下文的代码package com.example.spel; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; public class TestStringSubExpression { public static void main(String[] args) { String expressionStr hello world.toUpperCase().substring(#start, #end); //指定SpelExpressionParser解析器实现类 ExpressionParser parser new SpelExpressionParser(); //解析表达式 Expression expression parser.parseExpression(expressionStr); //设置对象模型基础 EvaluationContext context new StandardEvaluationContext(); context.setVariable(start, 1); context.setVariable(end, 5); System.out.println(expression.getValue(context)); } }另一个设置了上下文的 SpEL 测试代码package com.example.spel; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; import org.springframework.expression.ExpressionParser; import org.springframework.expression.common.TemplateParserContext; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; public class Test { public static void main(String[] args) { //测试SpringEL解析器。 //设置文字模板其中#{}表示表达式的起止#user是表达式字符串表示引用一个变量。 String template #{#user}早上好; //创建表达式解析器。 ExpressionParser parser new SpelExpressionParser(); //通过evaluationContext.setVariable可以在上下文中设定变量。 EvaluationContext context new StandardEvaluationContext(); context.setVariable(user, 黎明); //解析表达式如果表达式是一个模板表达式需要为解析传入模板解析器上下文。 Expression expression parser.parseExpression(template, new TemplateParserContext()); //使用Expression.getValue()获取表达式的值这里传入了Evaluation上下文第二个参数是类型参数表示返回值的类型。 System.out.println(expression.getValue(context, String.class)); } }代码执行结果黎明早上好4. 总结优点Spring EL 功能非常强大在 Annotation 的方式开发时可能感觉并不强烈因为可以直接编写到源代码来实现 Spring EL 的功能但如果是在 XML 文件中进行配置Spring EL 可以弥补 XML 静态注入的不足从而实现更强大的注入。缺点Spring EL 在使用时仅仅是一个字符串不易于排错与测试也没有 IDE 检查我们的语法目前 DIEA 可以检测 EL 语言当出现错误时较难检测。笔者实际应用笔者开发的项目当中比较频繁的使用 Spring EL例如通过 Spring EL 获取外部 properties 中的值又或者项目当中的数据字典亦是使用 Spring EL 的一个场景我们抽象出一个 Param 类的集合通过 Spring EL 集合筛选和投影获取我们想要的字段参数添加到我们的程序逻辑当中笔者项目中的 Spring Security 亦使用 Spring EL但本文章不加以叙述。总结Spring3.0 让人为之惊艳的非 Spring EL 莫属为我们的注入提供了另一种强大的形式传统注入能做到的事情和做不到的事情Spring EL 一概能完成但在项目当中并不适宜大量使用 Spring EL适当的技术方在适当的位置才能更好的完成事情。5. 补充示例Value中的${...}表示占位符它会读取上下文的属性值装配到属性中。Value中的#{...}表示启用 Spring 表达式具有运算功能。T(...)表示引入类System是java.lang.*包下的类是 Java 默认加载的包因此可以不用写全限定名如果是其它的包则需要写出全限定名才能引用类。Value(${database.driverName}) private String driver; Value(#{T(System).currentTimeMillis()}) private Long initTime null;5.1. 使用 Spring EL 进行赋值//赋值字符串 Value(#{使用 Spring EL 赋值字符串}) private String str null; //科学计数法赋值 Value(#{9.3E3}) private double d; //赋值浮点数 Value(#{3.14}) private float pi; //赋值 bean 的属性 Value(#{beanName.str}) private String otherBeanProp null; //?表示判断是否为 null不为空才会执行后面的表达式 Value(#{beanName.str?.toUpperCase()}) private String otherBeanProp1 null;5.2. 使用 Spring EL 进行计算//数学运算 Value(#{12}) private int run; //浮点数比较运算 Value(#{beanName.pi 3.14f}) private boolean piFlag; //字符串比较运算 Value(#{beanName.str eq Spring Boot}) private boolean strFlag; //字符串连接 Value(#{beanName.str 连接字符串}) private String strApp null; //三元运算 Value(#{beanName.d 1000 ? 大于 : 小于}) private String resultDesc null;