SpEL 概述
Spring 动态语言(SpEL)是一个支持运行时查询和操作对象的动态语言,其语法类似于 EL 表达式,具有如显示方式调用、字符串模板函数等强大特性,同时能够很好地与其他动态语言进行集成;
使用 SpEL 时需要导入 spring-expression 依赖;
基本使用
ExpressionParser parser = new SpelExpressionParser(); //创建 SpEL 表达式解析器
Expression exp = parser.parseExpression("'Hello World!'"); //对表达式进行解析
String result = (String) exp.getValue(); //对表达式进行求值
System.out.println(result);
使用 SpEL 编译器
默认情况下,SpEL 表达式只有在求值时才会进行表达式计算,所有表达式可以在运行时动态修改,但对于同一个表达式,如果每次求值都要进行动态解析,在一些调用频率比较高的场景下,对性能的影响比较大;
因此 Spring 提供了 SpelCompiler SpEL 编译器来解决这个问题,SpEL 编译器会将表达式编译成字节码,如果后续运行时表达式发生变化,才需要重新编译;
China china = new China();
//创建解析配置
SpelParserConfiguration config = new
SpelParserConfiguration(SpelCompilerMode.IMMEDIATE,Compiler.class.getClassLoader());
//由解析配置创建解析器
SpelExpressionParser parser = new SpelExpressionParser(config);
//创建取值上下文
EvaluationContext context = new StandardEvaluationContext(china);
//解析表达式
SpelExpression exp = parser.parseRaw("isCapital('Beijing') && isCapital('Shanghai')");
//解析表达式
System.out.println(exp.getValue(context)); //第一次调用
System.out.println(exp.getValue(context)); //第二次调用
以上示例中,China是一个对象,含有一个返回boolean的isCaptal 成员方法;
在创建SpelParserConfiguration 配置对象时候,spelCompilerMode 参数含有以下3个枚举值可选:
- SpelCompilerMode.OFF:默认编译模式,不启用编译;
- SpelCompilerMode.MIXED:混合编译模式,前几次执行表达式取值采用解释型处理,当达到一个阈值(100)后,才启用编译处理;
- SpelCompilerMode.IMMEDIAT:立即启用编译,实际上SpEL内部不会立即启用编译,而是在第二次执行表达式取值时才会启用编译;
SpEL 表达式语法
文本字符解析
SpEL 支持字符串、日期、数字(正数、负数、十六进制数)、布尔类型、null;
其中字符串需要使用类似
“'Hello world!'” "\Hello world!"\"" 的表述方式;
单引号可以使用
"\'" 来表示;
ExpressionParser parser = new SpelExpressionParser();
String helloworld = parser.parseExpression("\"Hello world\"").getValue(String.class); //字符串解析
double doublenum = parser.parseExpression("3.141596E+10").getValue(Double.class); //浮点数解析
int intnum = parser.parseExpression("0x7FFFFFFF").getValue(Integer.class); //十六进制数解析
boolean trueValue = parser.parseExpression("true").getValue(Boolean.class); //布尔值解析
Object nullValue = parser.parseExpression("null").getValue(); //null值解析
操作符支持
SpEL 支持Java 标准的操作符,包括:
- 关系操作符:> , < , >= , <= , = ,正则表达式 , instanceof 运算符;
- 逻辑运算符:&&,||,!,此外还也提供了 and 和 or 2个运算符分别等同于 && 和 ||;
- 算数运算符:+,-,*,/,%,^;
- 三元运算符:<表达式1>?<表达式2>:<表达式3>;
此外还支持 Groovy 的以下操作符:
- 安全导航操作符:<对象>.?<变量属性/方法>
安全导航操作符用于避免空指针异常,通常在获取对象属性方法时验证该对象是否为空,需要加上一系列的验证代码,使用安全导航操作符可以节省这个过程;
//user是User类的实例,User类含有实例username
//原来代码:
if(user != null){
String name = user.username;
}
//使用安全导航操作符的代码;
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = new StandardEvaluationContext(user);
String name = parser.parseExpression("user.?username").getValue(String.class);
- Elvis操作符:<变量>?<变量值>
在三元运算中,通常要重复2次变量,Elvis用于简化这个过程;
//三元元算符的表达
String name = "Al-assad";
String displayName = name!=null?name:"UnkonwnName";
//Elvis运算符的表达
String displayName = name?"UnknowName";
对象操作
对象属性解析,方法解析,属性赋值
1、SpEL 可以使用类似 ”xxx.yyy.zzz“ 的对象属性来访问对象属性;
2、SpEL可以直接访问上下文对象中的实例方法、静态方法;
3、SpEL 还可以对上下文对象的属性进行赋值;
以下示例中 User 类包含数据域 name,age,birthMessage(BirthMessage),包含成员方法 boolean validatePass(String passworld);
BirthMessage 类包含数据域 birthday(Date),city;
//测试对象
User user = new User("Al-assad",22, new BirthMessage(new SimpleDateFormat("yyyy-MM-dd").parse("1996-12-11"),"Chaozhou"));
//构造SpEL解析上下文
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = new StandardEvaluationContext(user); //设置上下文对象
/*1、获取对象属性*/
String name = parser.parseExpression("name").getValue(context,String.class); //获取对象属性
int age = parser.parseExpression("age").getValue(context,Integer.class);
String city = parser.parseExpression("birthMessage.city").getValue(context,String.class); //获取嵌套对象属性
System.out.println(name+" "+age+" "+city);
/*2、调用对象方法*/
boolean isValidate = parser.parseExpression("validatePass('1234455')").getValue(context,Boolean.class);
System.out.println(isValidate);
/*3、对象属性赋值*/
//① 通过setValue赋值
parser.parseExpression("name").setValue(context,"Vancy");
System.out.println(user.getName());
//② 通过表达式赋值
parser.parseExpression("name='Mofess'").getValue(context);
System.out.println(user.getName());
T 类型操作符- T(全限定名)
T 类型操作符用于从类路径中加载指定类名称(全限定名)的 Class 对象,该功能类似于 ClassLoader#loadClass() ;
ExpressionParser parser = new SpelExpressionParser();
//获取类型对象
Class stringclass = parser.parseExpression("T(java.lang.String)").getValue(Class.class);
System.out.println(stringclass.getName());
//调用类静态方法
double randomValue = parser.parseExpression("T(java.lang.Math).random()").getValue(Double.class);
System.out.println(randomValue);
构造器调用
在SpEL 中可以通过 new 运算符调用构造器构造一个对象实例,除了基本类型和字符串,其他类需要使用全限定名;
ExpressionParser parser = new SpelExpressionParser();
//调用构造器构建一个实例
String str = parser.parseExpression("new String('Hello world')");
//调用构造器创建实例,并调用该实例的实例方法
int randomValue2 = parser.parseExpression("new java.util.Random().nextInt(10)").getValue(Integer.class);
System.out.println(randomValue2);
上下文变量设置和获取
在 EvaluationContext 上下文对象中的变量可以使用 ”#变量名“ 引用,可以使用 EvaluationContext
#setVariable(var,value) 设置变量;
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = new StandardEvaluationContext(user);
//为上下文添加新的变量值
context.setVariable("TempName","assad");
//在表达式中获取新设置的变量值
parser.parseExpression("name=#TempName").getValue(context);
数组、集合操作
数组、集合解析
SpEL 支持数组、集合类型(List、Map)的解析;
数组支持标准 Java 中的创建方法,如 "
new int[]{1,2,3,4}";
List 的创建格式,"
{1,2,3,4}" 、"{{'a','b'},{'c','d'}}"’ ;
Map 的创建格式,"
{{username:'Al-assad',city:'Guangzhou'},{username:'Vancy',city:'Shanghai'}}" ;
//数组解析
int[] array = (int[])parser.parseExpression("new int[]{1,2,3,4}").getValue();
//List解析
List list = parser.parseExpression("{1,2,3,4}").getValue(List.class);
List lists = parser.parseExpression
("{{'Beijing','Shanghai','Guangzhou'},{'NewYork','Seattle'}}").getValue(List.class);
//Map解析
Map userInfo = parser.parseExpression
("{{name:'Al-assad',city:'Guanzhou'},{name:'Vancy',city:'Shanghai'}}").getValue(Map.class);
String name = (String)userInfo.get("name");
System.out.println(name);
集合过滤
SpEL 支持动态语言的集合过滤,允许通过一个过滤条件获取元集合的子集,集合过滤的语法为:
”?[selectExpression]";
以下例子演示过滤获取一个List中大于5的元素;
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = new StandardEvaluationContext();
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
context.setVariable("list",list); //将集合设置为上下文属性
//过滤集合
List<Integer> listfilted = (List<Integer>)parser.parseExpression("#list.?[#this>5]").getValue(context);
listfilted.forEach(System.out::println);
//output:6,7,8,9,10
集合转换
集合转行允许使用一个算子对集合内的元素进行运算,会得到一个新元素类型的集合,集合转换的语法为:
”![projectionExpression]"
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = new StandardEvaluationContext();
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
context.setVariable("list",list);
//集合转换:将一个List<Integer> 根据规定的条件,转化为以一个 List<Boolean>
List<Boolean> filterResult = (List<Boolean>)parser.parseExpression("#list.![#this>5]").getValue(context);
filterResult.forEach(System.out::println);
//output:false,false,false,false,false,true,true,true,true,true,
在 XML文件&注解中使用SpEL
在SpEL的实际使用场景中,经常会在Bean装配过程中,在XML配置文件或Bean注解中使用 SpEL,这两种方式都是使用统一的语法使用 SpEL 表达式:
#{<expression string>};
在XML文件中使用SpEL
以下是在xml文件中使用SpEL的示例:
<beans ...>
<bean id="numGuess" class="site.assad.SpELTest.NumberGuess"
p:randomNumber="#{T(java.lang.Math).random()*100.0}"/> <!--使用T元算符的SpEL调用类实例方法-->
<bean id="shapeGuess" class="site.assad.SpELTest.ShapeGuess"
p:initalShapeSeed="#{numGuess.randomNumber}}" /> <!--在 SpEL 中调用定义的bean的属性-->
</beans>
在注解中使用SpEL
@Value 注解可以标注在类的属性、方法和构造函数上,用于配置文件中加载一个参数值;
以下实例演示使用SpEL加载数据源连接参数:
数据源加载 Bean:DataSource.class
public class DateSource {
"#{dataSourceProperties['driverClassName']}") (
private String driverName;
"#{dataSourceProperties['url']}") (
private String url;
"#{dataSourceProperties['userName']}") (
private String userName;
"#{dataSourceProperties['password']}") (
private String password;
}
bean自动扫描配置:bean.xml
<beans ...>
<!--引入用于使用SpEL表达式变量的util工具命名空间-->
<util:properties id="dataSourceProperties" location="classpath:jdbc.properties" />
</beans>
储存数据源连接参数的配置文件:jdbc.properties
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql//127.0.0.1/testDb
userName=root
password=1234
实际上,SpEL还提供了一种用于简化@Value中参数获取的表达方式——属性占位符
bean自动扫描配置:bean.xml 更改为:
<beans ...>
<!--引入用于使用SpEL表达式变量的util工具命名空间-->
<util:properties id="dataSourceProperties" location="classpath:jdbc.properties" />
<!--配置属性占位符工具-->
<context:property-placeholder properties-ref="dataSourceProperties" />
</beans>
数据源加载 Bean:DataSource.class 可以如下:
public class DataSource {
"#{'driverClassName'}") (
private String driverName;
"#{'url'}") (
private String url;
"#{'userName'}") (
private String userName;
"#{'password'}") (
private String password;
}