SpEL即Spring表达式语言(Spring Expression Language)。
从我通常的使用场景(API开发)来说,SpEL提供的大部分能力都可以划到奇技淫巧的范畴内。但是在一些场景下如缓存配置、ThymeLeaf取值等,SpEL还是大有可为的。
SpEL表达式的默认格式为: #{expression} 。SpEL表达式以“#”开头,表达式主体包围在花括号中。
我们通常使用的属性取值表达式(也可称为属性占位符,格式 $ { expression } )不可以嵌套SpEL表达式。不过SpEL表达式可以嵌套属性取值表达式,如下:
#{${someProperty} + 2}
在上面的这个表达式里面,如果属性“someProperty”的值是2,这个表达式的值就是4。
下表列出了SpEL支持的运算符:
Type | Operators |
---|---|
Arithmetic | +, -, *, /, %, ^, div, mod |
Relational | <, >, ==, !=, <=, >=, lt, gt, eq, ne, le, ge |
Logical | and, or, not, &&, |
Conditional | ?: |
Regex | matches |
接下来我们主要以 @ Value 注解的形式介绍并演示这些运算符在SpEL中的应用。
如下是算数运算符的使用示例:
@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}") // 18 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;
除运算和取模运算都有字母形式的别名(除运算“div”,取模运算“mod”)。“+”运算符还可以用来执行字符串连接。
如下是关系运算符的使用示例:
@Value("#{1 == 1}") // true private boolean equal; @Value("#{1 eq 1}") // true private boolean equalAlphabetic; @Value("#{1 != 1}") // false private boolean notEqual; @Value("#{1 ne 1}") // false private boolean notEqualAlphabetic; @Value("#{1 < 1}") // false private boolean lessThan; @Value("#{1 lt 1}") // false private boolean lessThanAlphabetic; @Value("#{1 <= 1}") // true private boolean lessThanOrEqual; @Value("#{1 le 1}") // true private boolean lessThanOrEqualAlphabetic; @Value("#{1 > 1}") // false private boolean greaterThan; @Value("#{1 gt 1}") // false private boolean greaterThanAlphabetic; @Value("#{1 >= 1}") // true private boolean greaterThanOrEqual; @Value("#{1 ge 1}") // true private boolean greaterThanOrEqualAlphabetic;
所有的关系运算都有字母形式的别名。主要是为了适配使用xml配置文件的场景。在xml中使用带有三角符号的运算符(如小于“<”,大于“>”等)是不被允许的,此时我们可以使用字母形式的别名( lt , gt 等)来进行运算。
如下是逻辑运算符的使用示例:
@Value("#{250 > 200 && 200 < 4000}") // true private boolean and; @Value("#{250 > 200 and 200 < 4000}") // true private boolean andAlphabetic; @Value("#{400 > 300 || 150 < 100}") // true private boolean or; @Value("#{400 > 300 or 150 < 100}") // true private boolean orAlphabetic; @Value("#{!true}") // false private boolean not; @Value("#{not true}") // false private boolean notAlphabetic;
同关系运算符一样,每个逻辑运算符也都有字母形式的别名。
条件运算符,顾名思义是用来根据不同的情况来注入不同的值。实际上,就是一个三目运算符:
@Value("#{2 > 1 ? 'a' : 'b'}") // "a" private String ternary;
三目运算符主要被用来处理“if-then-else”这样的判定。
三目运算符通常的使用场景式是判断一个属性是否为null,如果是的话就返回一个默认值,如下:
@Value("#{worker.name != null ? worker.name : 'zhyea'}") private String ternaryForNull;
此外还有一种“Elvis”运算符,简化了上面这种“判定是否为空,为空则返回默认值”的场景:
@Value("#{worker.name ?: 'zhyea'}") private String elvis;
如上,“Elvis”运算符的符号是“ ?: ”,我们可以在Groovy语言中见到它。现在SpEL也引入了这个运算符。
正则运算符被用来校验字符串是否匹配某个指定的正则表达式。如下:
@Value("#{'100' matches '//d+' }") // true private boolean validNumericStringResult; @Value("#{'100fghdjf' matches '//d+' }") // false private boolean invalidNumericStringResult; @Value("#{'valid alphabetic string' matches '[a-zA-Z//s]+' }") // true private boolean validAlphabeticStringResult; @Value("#{'invalid alphabetic string #$1' matches '[a-zA-Z//s]+' }") // false private boolean invalidAlphabeticStringResult; @Value("#{worker.id matches '//d+'}") // true if someValue contains only digits private boolean validNumericValue;
使用SpEL,我们还可以访问容器中的任何Map或List对象。看个例子:
@Component("workersHolder") public class WorkersHolder { private List<String> workers = new LinkedList<>(); private Map<String, Integer> salaryByWorkers = new HashMap<>(); public WorkersHolder() { workers.add("John"); workers.add("Susie"); workers.add("Alex"); workers.add("George"); salaryByWorkers.put("John", 35000); salaryByWorkers.put("Susie", 47000); salaryByWorkers.put("Alex", 12000); salaryByWorkers.put("George", 14000); } // getters & setters ... }
这里我们创建了一个List对象来存放工人名字,一个Map对象来存放每个工人的工资。
接下来我们使用SpEL来访问这两个集合对象里面的元素:
@Value("#{workersHolder.salaryByWorkers['John']}") // 35000 private Integer johnSalary; @Value("#{workersHolder.salaryByWorkers['George']}") // 14000 private Integer georgeSalary; @Value("#{workersHolder.salaryByWorkers['Susie']}") // 47000 private Integer susieSalary; @Value("#{workersHolder.workers[0]}") // John private String firstWorker; @Value("#{workersHolder.workers[3]}") // George private String lastWorker; @Value("#{workersHolder.workers.size()}") // 4 private Integer numberOfWorkers;
有时候会需要编程处理SpEL表达式。Spring也为我们提供了相关的工具类。这些类都位于“spring-expression”包下。下面是一个封装好的解析SpEL表达式的方法:
public static <T> T parse(String expr) { ExpressionParser expressionParser = new SpelExpressionParser(); Expression expression = expressionParser.parseExpression(expr); return (T) expression.getValue(); }
调用 ExpressionParser . parseExpression ( ) 后获得的值是 Object 类型的,这里会通过强制类型转换转为需要的类型。
现在我们将字符串(’zhyea’)作为SpEL表达式传入并执行,毫无疑问,执行结果也应当返回字符串“zhyea”:
String expr = "'zhyea'"; String r = parse(expr); Assert.assertEquals("zhyea", r);
在SpEL中还可以调用方法,访问属性,使用构造器。代码大致如下:
// call method length() String expr = "'zhyea'.length()"; int l = parse(expr); // call constructor String expr = "new String('chobit').length()"; int l = parse(expr); // visit properties String expr = "'zhyea'.bytes"; byte[] r = parse(expr);
代码都上传到了 github 上,我就不一一贴执行结果了。
之前我们获取解析后的值是通过了一次强制类型转换的。Spring也提供了一个传入泛型类型来获取目标类型结果的方法,简单做了下封装:
public static <T> T parse(String expr, Class<T> clazz) { ExpressionParser expressionParser = new SpelExpressionParser(); Expression expression = expressionParser.parseExpression(expr); return expression.getValue(clazz); }
程序处理SpEL这块儿还有很多的内容,不过从我个人的角度来说,到目前的程度已经够了。如果想继续多了解一些的话可以查阅下方的参考文档。话说,spring官方文档中对SpEL的说明完全是基于程序处理来进行的。
相关代码已上传到了 GITHUB/zhyea ,如有需要请直接查看。