写在SpEL之前
EL表达式是JSP中一个十分重要的概念。它允许我们从model中读取数据以及条件性的渲染JSP页面。Spring在3.0的版本中也引入了Spring Expression Language从而使得spring具有更好的可扩展性和跨平台性。
虽然SpEL引擎作为Spring 组合里的表达式解析的基础 ,但它不直接依赖于Spring,可独立使用。这也是SpEL的最大优点。
这一章内容将包括SpEL的使用,并且会举一个例子从配置文件中读取相应的属性。因为最终的项目是为了实现thymeleaf+springboot+mysql, 而很多地方都无形中使用了SpEL,比如Thymeleaf获取model数据,比如项目从config文件中获取常量配置。让我们赶紧来感受一下这个功能的强大吧。
SpEL Operators
SpEL支持在表达式中使用多种类型的运算符,具体情况如上图所示。SpEL默认的格式为#{expression}。当然,它也支持嵌套一般的属性获取符${properties},如#{${someProperty} + 2}。这些值可以使用@Value(“”)注释注入到相应的属性上。
下面我们将举一些例子来说明这些运算符的使用
算数运算符
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
| @Value("#{19 + 1}") private double add; @Value("#{'String1 ' + 'string2'}") private String addString; @Value("#{20 - 1}") private double subtract; @Value("#{10 * 2}") private double multiply; @Value("#{36 / 2}") private double divide; @Value("#{36 div 2}") private double divideAlphabetic; @Value("#{37 % 10}") private double modulo; @Value("#{37 mod 10}") private double moduloAlphabetic; @Value("#{2 ^ 9}") private double powerOf; @Value("#{(2 + 2) * 2 + 9}") private double brackets;
|
这里之所以给/赋予等价符号div是为了让XML配置形式兼容,无需进行转义。
关联和逻辑运算符
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
| @Value("#{1 == 1}") private boolean equal; @Value("#{1 eq 1}") private boolean equalAlphabetic; @Value("#{1 != 1}") private boolean notEqual; @Value("#{1 ne 1}") private boolean notEqualAlphabetic; @Value("#{1 < 1}") private boolean lessThan; @Value("#{1 lt 1}") private boolean lessThanAlphabetic; @Value("#{1 <= 1}") private boolean lessThanOrEqual; @Value("#{1 le 1}") private boolean lessThanOrEqualAlphabetic; @Value("#{1 > 1}") private boolean greaterThan; @Value("#{1 gt 1}") private boolean greaterThanAlphabetic; @Value("#{1 >= 1}") private boolean greaterThanOrEqual; @Value("#{1 ge 1}") private boolean greaterThanOrEqualAlphabetic;
@Value("#{250 > 200 && 200 < 4000}") private boolean and; @Value("#{250 > 200 and 200 < 4000}") private boolean andAlphabetic; @Value("#{400 > 300 || 150 < 100}") private boolean or; @Value("#{400 > 300 or 150 < 100}") private boolean orAlphabetic; @Value("#{!true}") private boolean not; @Value("#{not true}") private boolean notAlphabetic;
|
条件运算符
condition ? a : b是指condition成立时值为a,否则为b
这种条件运算符可以用于实现if-else语句,也可以用于判断属性是否为空,并且在属性为空时提供默认值。
1 2 3 4 5 6 7 8
| @Value("#{2 > 1 ? 'a' : 'b'}") private String ternary;
@Value("#{someBean.someProperty != null ? someBean.someProperty : 'default'}") private String ternary;
@Value("#{someBean.someProperty ?: 'default'}") private String elvis;
|
正则表达式
使用matches语句来判断当前的字符串是否满足特定的正则表达式
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Value("#{'100' matches '\\d+' }") private boolean validNumericStringResult; @Value("#{'100fghdjf' matches '\\d+' }") private boolean invalidNumericStringResult; @Value("#{'valid alphabetic string' matches '[a-zA-Z\\s]+' }") private boolean validAlphabeticStringResult; @Value("#{'invalid alphabetic string #$1' matches '[a-zA-Z\\s]+' }") private boolean invalidAlphabeticStringResult; @Value("#{someBean.someValue matches '\d+'}") private boolean validNumericValue;
|
获取list和map数据类型
假设现在有一个Bean名称为workersHolder,这个组件中有两个fields,一个是workers的链表,这里记录了所有的员工名称,还有一个是salaryByWorkers键值对map,里面存储了员工姓名和其对应的薪水。现在这个bean中初始化了四个员工信息,如何能将这四个员工的信息读取出来?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| @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); } }
|
map采用componentName.mapName['key']读取key对应的value
list采用componentName.listName[index]读取对应index下标的元素
同样也支持对方法的调用,整体的语法其实和JAVA是相似的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Value("#{workersHolder.salaryByWorkers['John']}") private Integer johnSalary; @Value("#{workersHolder.salaryByWorkers['George']}") private Integer georgeSalary; @Value("#{workersHolder.salaryByWorkers['Susie']}") private Integer susieSalary; @Value("#{workersHolder.workers[0]}") private String firstWorker; @Value("#{workersHolder.workers[3]}") private String lastWorker; @Value("#{workersHolder.workers.size()}") private Integer numberOfWorkers;
|
从上面这些例子我们可以看出,SpEL不仅可以调用属性,还可以访问一些公开的方法,甚至进行正则表达式的匹配,可以说是非常轻量级而且全能了。
至此我们已经了解了如何通过annotation来使用SpEL。其实SpEL还可以通过XML进行配置,甚至可以脱离Spring环境使用,下面可以简单介绍。
XML配置
这里直接从别的网站上找了一个实例,有兴趣的可以至底下的references中查看,这里就不详细叙述了。
SpEL in Spring 真实场景示例
现在我们的项目有一个需求,结合配置文件,从配置文件中读取我们想要知道的数据,并赋值给类中的变量。这里我们的配置文件采用yaml格式(不熟悉yaml请先参考我的这篇博客)。现在我们先看配置文件的内容。
配置文件名称为application.yml,application-test.yml和application-validate.yml,位于classpath:/resources/config目录底下,其中application.yml是主配置文件,决定选择哪个配置文件作为当前配置。application-test.yml是测试环境的配置文件,application-validate.yml是校验信息的配置文件。内容分别如下所示。
application.yml
1 2 3 4
| spring: profiles: active: test,validate
|
application-test.yml
1 2 3 4 5
| server: port: 8080
...
|
application-validate.yml
1 2 3 4
| login: username: notempty: 用户名不可以为空
|
下面展示了在代码中使用annotation获得配置文件中的值
1 2 3 4 5
| @Value("${server.port}") private int port;
@Value("${login.username.notempty}") private String notEmpty;
|
当然,为了方便起见,spring还允许我们将所有的配置文件映射到一个专门的JAVA Class中,封装成一bean。这样在需要注入的时候,只需使用@Autowired注释将其引用进来就可以使用。
1 2 3 4 5 6
| @Data @Component @ConfigurationProperties(prefix = "server") public class AppConfig{ private int port; }
|
这里需要注意的是还需要在spring-boot的入口文件上加标签
1 2 3 4 5 6 7 8
| @SpringBootApplication @EnableConfigurationProperties({AppConfig.class}) public class Application {
public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
|
在调用的类中注入AppConfig
1 2
| @Autowired private AppConfig config;
|
References
项目地址
SpEL官方文档中文版
快速教程
spring-boot自定义配置文件
spring-boot yaml使用方式