Spring framework(6):SpEL 表达式

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
 
@Component
public class DateSource {
    @Value("#{dataSourceProperties['driverClassName']}")
    private String driverName;
    
    @Value("#{dataSourceProperties['url']}")
    private String url;
    
    @Value("#{dataSourceProperties['userName']}")
    private String userName;
    
    @Value("#{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 可以如下:
 
@Component
public class DataSource {
    @Value("#{'driverClassName'}")
    private String driverName;
    @Value("#{'url'}")
    private String url;
    @Value("#{'userName'}")
    private String userName;
    @Value("#{'password'}")
    private String password;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值