SpEL简介与功能特性
Spring表达式语言(简称SpEL)是一个支持查询并在运行时操纵一个对象图的功能强大的表达式语言。SpEL语言的语法类似于统一EL,但提供了更多的功能,最主要的是显式方法调用和基本字符串模板函数。
同很多可用的Java 表达式语言相比,例如OGNL,MVEL和JBoss EL,SpEL的诞生是为了给Spring社区提供一个可以给Spring目录中所有产品提供单一良好支持的表达式语言。其语言特性由Spring目录中的项目需求驱动,包括基于eclipse的SpringSource套件中的代码补全工具需求。也就是说,SpEL是一个基于技术中立的API允许需要时与其他表达式语言集成。
SpEL作为Spring目录中表达式求值的基础,它并不是直接依赖于Spring而是可以被独立使用。为了能够自包含,本章中的许多示例把SpEL作为一个独立的表达式语言来使用。这就需要创建一些如解析器的引导基础组件类。大多数Spring用户只需要为求值编写表达式字符串而不需要关心这些基础组件
SpEL功能特性:
- 字符表达式
- 布尔和关系操作符
- 正则表达式
- 类表达式
- 访问properties,arrays,lists,maps
- 方法调用
- 关系操作符
- 赋值
- 调用构造器
- 三元操作符
- 变量
- 用户自定义函数
- 集合投影
- 集合选择
- 模板表达式
一、为什么需要Spring表达式语言
1.1、新建一个Maven Web项目,添加依赖,pom.xml如下所示:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.zhangguo</groupId> <artifactId>Spring053</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>Spring053</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <spring.version>4.3.0.RELEASE</spring.version> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> <version>4.10</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.9</version> </dependency> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.2.4</version> </dependency> </dependencies> </project>
表达式语言所依赖的包如下所示:
2.2、为了IOC,定义了用户类User.java与Order.java,如下所示:
用户类:
订单类:
package com.zhangguo.Spring053.spel01; /** * 订单类 * */ public class Order { /** * 订单名称 */ private String orderName; /* * 用户姓名 */ private String userName; /** * 用户对象 */ private User customer; public String getOrderName() { return orderName; } public void setOrderName(String orderName) { this.orderName = orderName; } public User getCustomer() { return customer; } public void setCustomer(User customer) { this.customer = customer; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } }
2.3、编写容器初始化的配置文件spel01.xml,内容如下:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd"> <bean id="gyl" class="com.zhangguo.Spring053.spel01.User" p:id="9527"> <property name="name" value="郭永乐"> </property> </bean> <bean id="order001" class="com.zhangguo.Spring053.spel01.Order"> <property name="customer" ref="gyl"></property> <property name="name" value="#{gyl.name}"></property> <property name="orderName" value='#{"Apples".toUpperCase()}'></property> </bean> </beans>
在配置文件中,出现了#{}形式的表达式,我们就称为Spel表达式。#{gyl.name}作用是找到名称为gyl的bean取出中间的name值;#{"Apples".toUpperCase()}把字符串Apples转换成大写并输出。
2.4、取出bean测试
package com.zhangguo.Spring053.spel01; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test { public static void main(String[] args) { ApplicationContext ctx=new ClassPathXmlApplicationContext("spel01.xml"); Order order=ctx.getBean("order001",Order.class); System.out.println(order); } }
2.5、运行结果
二、SpEL表达式Hello World!
Spring表达式语言(SpEL)从3.X开始支持,它是一种能够支持运行时查询和操作对象图的强大的表达式,其表达式语法类似于统一表达式语言。
SpEL支持如下表达式:
基本表达式:字面量表达式、关系,逻辑与算数运算表达式、字符串连接及截取表达式、三目运算、正则表达式、括号优先级表达式;
类相关表达式:类类型表达式、类实例化、instanceof表达式、变量定义及引用、赋值表达式、自定义函数、对象属性存取及安全导航表达式、对象方法调用、Bean引用;
集合相关表达式:内联List、内联数组、集合,字典访问、列表,字典,数组修改、集合投影、集合选择;不支持多维内联数组初始化;不支持内联字典定义;
其他表达式:模板表达式。
从一个Hello World!的示例开始:
package com.zhangguo.Spring053.spel02; import org.springframework.expression.Expression; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; public class Test { public static void main(String[] args) { //创建SpEL表达式的解析器 ExpressionParser parser=new SpelExpressionParser(); //解析表达式'Hello '+' World!' Expression exp=parser.parseExpression("'Hello '+' World!'"); //取出解析结果 String result=exp.getValue().toString(); //输出结果 System.out.println(result); } }
运行结果:
Hello World!
从示例中可以看出java成功的将一个字符解析出了结果,如果我们把要解析的内容设置成1+1则会解析出2来,这里的1+1就是一个SpEL表达式,该表达式在java中只是一个String,通过解析可以得到字符串本身的意见,有点类型编译程序的感觉。
三、SpEL表达式
3.1、文字表达式
支持的文字表达的类型是字符串,日期,数值(整型,实型,和十六进制),布尔和空。字符串是由单引号分隔。使用反斜杠字符转移把一个单引号字符本身放在字符串中。
ExpressionParser ep= new SpelExpressionParser(); System.out.println(ep.parseExpression("'HelloWorld'").getValue()); System.out.println(ep.parseExpression("0xffffff").getValue()); System.out.println(ep.parseExpression("1.234345e+3").getValue()); System.out.println(ep.parseExpression("new java.util.Date()").getValue());
运行结果:
HelloWorld
16777215
1234.345
Fri Jul 01 14:50:59 CST 2016
3.2、SPEL语言特性
3.2.1、属性
//创建SpEL表达式的解析器 ExpressionParser parser=new SpelExpressionParser(); User user=new User(9527,"周星驰"); //解析表达式需要的上下文,解析时有一个默认的上下文 EvaluationContext ctx = new StandardEvaluationContext(); //在上下文中设置变量,变量名为user,内容为user对象 ctx.setVariable("user", user); //从用户对象中获得id并+1900,获得解析后的值在ctx上下文中 int id = (Integer) parser.parseExpression("#user.getId() + 1900").getValue(ctx); System.out.println(id);
运行结果:
11427
User类在前面已定义,这里增加了一个有参构造方法。上面的示例是调用方法,其实可以这样:引用对象属性,只需使用一个句点来表示一个嵌套的属性值,如下代码所示:
int id = (Integer) parser.parseExpression("#user.id + 1900").getValue(ctx);
运行结果:
11427
要注意的是此时#user后不再是一个方法而是.id,直接访问属性,在java中这样做是不行的,便SpEL中允许
3.2.2、数组
String[] students=new String[]{"tom","jack","rose","mark","lucy"}; ctx.setVariable("students", students); String student = parser.parseExpression("#students[3]").getValue(ctx, String.class); System.out.println(student);
结果:mark
3.2.3、列表
List numbers = (List) parser.parseExpression("{1,2,3,4,5}").getValue(); System.out.println(numbers.get(2)+""); List listOfLists = (List) parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(); System.out.println(((List)listOfLists.get(1)).get(1));
结果:3 y
3.2.4、索引器、与字典
User user1=new User(9001,"邹任飞"); User user2=new User(9002,"练汶峰"); List<User> users=new ArrayList<User>(); users.add(user1); users.add(user2); ctx.setVariable("users", users); String name = parser.parseExpression("#users[1].name").getValue(ctx,String.class); System.out.println(name);
结果:练汶峰
在java中访问集合中的对象通过get(索引)而在SpEL中我们可以直接像数组那样访问对象。如果是一个字典可以这样:#users["tom"].id
3.2.5、方法
方法调用使用典型的Java编程语法。 你可能 还在文字调用方法。 也支持可变参数。
String c = parser.parseExpression("'abcdef'.substring(2, 3)").getValue(String.class); System.out.println(c);
结果:c
3.2.6、操作符
关系操作符:使用标准的操作符号支持关系操作符:等于,不等于,小于,小于等于,大于,大于等于。
逻辑操作符:支持的逻辑操作符包括and,or和not(!),不支持&&和||。
算术操作符:加法运算符可以用于数字,字符串和日期。减法可用于数字和日期。乘法和除法仅可以用于。其他支持的数学运算包括取模(%)和指数幂(^)。使用标准的运算符优先级。
关系运算符:
//true boolean trueValue1 = parser.parseExpression("2 == 2").getValue(Boolean.class); //false boolean falseValue1 = parser.parseExpression("2 < -5.0").getValue(Boolean.class); //true boolean trueValue2 = parser.parseExpression("'black' < 'block'").getValue(Boolean.class); //false,字符xyz是否为int类型 boolean falseValue2 = parser.parseExpression("'xyz' instanceof T(int)").getValue(Boolean.class); //true,正则是否匹配 boolean trueValue3 =parser.parseExpression("'5.00' matches '^-?\d+(\.\d{2})?$'").getValue(Boolean.class); //false boolean falseValue3=parser.parseExpression("'5.0067' matches '^-?\d+(\.\d{2})?$'").getValue(Boolean.class);
逻辑运算:
// -- AND 与运算 -- //false boolean falseValue4 = parser.parseExpression("true and false").getValue(Boolean.class); //true,isMember方法用于测试是否为某个对象的成员 String expression = "isMember('Nikola Tesla') and isMember('Mihajlo Pupin')"; boolean trueValue4 = parser.parseExpression(expression).getValue(Boolean.class); // -- OR 或运算-- //true boolean trueValue5 = parser.parseExpression("true or false").getValue(Boolean.class); //true String expression1 = "isMember('Nikola Tesla') or isMember('Albert Einstein')"; boolean trueValue6 = parser.parseExpression(expression).getValue( Boolean.class); //false boolean falseValue5 = parser.parseExpression("!true").getValue(Boolean.class); //false String expression2 = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')"; boolean falseValue6 = parser.parseExpression(expression).getValue(Boolean.class);
算术运算:
// Addition int two = parser.parseExpression("1 + 1").getValue(Integer.class); // 2 String testString = parser.parseExpression("'test' + ' ' + 'string'").getValue(String.class); // 'test string' // Subtraction int four = parser.parseExpression("1 - -3").getValue(Integer.class); // 4 double d = parser.parseExpression("1000.00 - 1e4").getValue(Double.class); // -9000 // Multiplication int six = parser.parseExpression("-2 * -3").getValue(Integer.class); // 6 double twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double.class); // 24.0 // Division int minusTwo = parser.parseExpression("6 / -3").getValue(Integer.class); // -2 double one = parser.parseExpression("8.0 / 4e0 / 2").getValue(Double.class); // 1.0 // Modulus int three = parser.parseExpression("7 % 4").getValue(Integer.class); // 3 int one = parser.parseExpression("8 / 5 % 2").getValue(Integer.class); // 1 // Operator precedence int minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Integer.class); // -21
3.2.7、表达式支持定义bean、基于XML的配置
在xml配置中可以自由的使用SpEL,如下所示:
<bean id="gyl" class="com.zhangguo.Spring053.spel01.User" p:id="9527"> <property name="name" value="郭永乐"> </property> </bean> <bean id="order001" class="com.zhangguo.Spring053.spel01.Order"> <property name="customer" ref="gyl"></property> <property name="userName" value="#{gyl.name}"></property> <property name="orderName" value='#{"Apples".toUpperCase()}'></property> </bean> <bean id="numberGuess" class="org.spring.samples.NumberGuess"> <!--调用静态方法random() --> <property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }" /> </bean> <bean id="taxCalculator" class="org.spring.samples.TaxCalculator"> <property name="defaultLocale" value="#{ systemProperties['user.region'] }" /> </bean> <bean id="numberGuess" class="org.spring.samples.NumberGuess"> <property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }" /> </bean> <bean id="shapeGuess" class="org.spring.samples.ShapeGuess"> <property name="initialShapeSeed" value="#{ numberGuess.randomNumber }" /> </bean>
3.2.8、表达式支持定义bean、基于注解的配置
用户类User.java:
package com.zhangguo.Spring053.spel03; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; /** * 用户类 */ @Component("user1") public class User { /** * 编号 */ @Value("#{9527+100}") private int id; /** * 姓名 */ @Value("#{'Hello'.toUpperCase()}") private String name; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
订单类:Order.java
package com.zhangguo.Spring053.spel03; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Repository; /** * 订单类 * */ @Repository("Order03") public class Order { /** * 订单名称 */ @Value("#{'Apple订单'}") private String orderName; /* * 用户姓名 */ @Value("#{user1.name}") private String userName; /** * 用户对象 */ @Value("#{user1}") private User customer; public String getOrderName() { return orderName; } public void setOrderName(String orderName) { this.orderName = orderName; } public User getCustomer() { return customer; } public void setCustomer(User customer) { this.customer = customer; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } @Override public String toString() { return "订单名:"+this.getOrderName()+",姓名:"+this.getUserName()+",编号:"+this.getCustomer().getId(); } }
测试代码Test.java
package com.zhangguo.Spring053.spel03; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.zhangguo.Spring053.spel03.User; public class Test { public static void main(String[] args) { ApplicationContext ctx=new ClassPathXmlApplicationContext("spel03.xml"); User user1=ctx.getBean("user1",User.class); System.out.println(user1.getId()+","+user1.getName()); Order Order03=ctx.getBean("Order03",Order.class); System.out.println(Order03); } }
运行结果:
9627,HELLO
订单名:Apple订单,姓名:HELLO,编号:9627
3.2.9、操作符
ExpressionParser ep = new SpelExpressionParser(); // 关系操作符 System.out.println(ep.parseExpression("5>2").getValue()); System.out.println(ep.parseExpression("2 between {1,9}").getValue()); // 逻辑运算符 System.out.println(ep.parseExpression("(5>2) and (2==1)").getValue()); // 算术操作符 System.out.println(ep.parseExpression("100-2^2").getValue());
结果:
true
true
false
96
3.2.10、变量与赋值
变量:变量可以在表达式中使用语法#’变量名’引用
ExpressionParser ep= new SpelExpressionParser();
//创建上下文变量
EvaluationContext ctx = new StandardEvaluationContext();
ctx.setVariable(“name”, “Hello”);
System.out.println(ep.parseExpression("#name").getValue(ctx));
输出:Hello
赋值:属性设置是通过使用赋值运算符。这通常是在调用setValue中执行但也可以在调用getValue内,也可通过”#varName=value”的形式给变量赋值。
System.out.println(ep.parseExpression("#name='Ryo'").getValue(ctx));