Spring EL表达式使用详解

Spring EL

Spring EL表达式使用详解

什么是Spring EL表达式

  • Spring EL 表达式是Spring表达式语言,支持在xml和注解中使用表达式,类似于JSP的EL,JSTL表达式语言。Spring开发中我们会经常涉及到调用各种资源的情况,包含普通文件、网址、正则表达式、系统变量、其他Bean的一些属性、配置文件、集合等等,我们就可以使用Spring的表达式语言实现资源的注入

  • 试想,我们平常通过注解或xml配置文件方式注入的Bean或Bean属性,其实都是静态注入,如果,Bean A中的一个成员变量m的值需要参考Bean B中的成员变量n的值,这种情况静态注入就显得无力。而Spring EL表达式就完全可以满足我们的种种动态的需求,甚至还能进行一些计算,功能非常强大

  • 使用Spring表达式语言,我们在项目中不需要手动管理Spring表达式的相关的接口和实例,只需要直接编写Spring表达式,Spring就会自动解析并转换表达式。

  • Spring EL的格式为 #{SpEL expression}。Spring表达式主要写在注解 @Value的参数中,它的作用是通过spring把值注入给某个属性。

注入字面值

表达式支持各种类型的字面值, 字符串或字符类型的字面值需要使用单引号包括,其他类型字面值直接写就行

@Data
@Component
public class Computer {
    @Value("#{2}")
    private Integer id;
    @Value("#{'黑色'}")
    private String color;
    @Value("#{'联想'}")
    private String brand;
}
@Data
@Component
public class User {
    //注入整数类型
    @Value("#{18}")
    private Integer age;

    //注入浮点数类型
    @Value("#{58.5}")
    private Double weight;

    //注入布尔数类型
    @Value("#{true}")
    private Boolean isGirl;

    //注入字符类型
    @Value("#{'f'}")
    private Character gender;

    //注入字符串类型
    @Value("#{'lucy'}")
    private String username;

    //注入id为computer的bean
    @Value("#{computer}")
    private Computer computer;
}

从容器中获取user对象并打印,结果如下:

八月 01, 2019 10:06:58 上午 org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@7daf6ecc: startup date [Thu Aug 01 10:06:58 CST 2019]; root of context hierarchy
八月 01, 2019 10:06:58 上午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [applicationContext.xml]
User(age=18, weight=58.5, isGirl=true, gender=f, username=lucy, computer=Computer(id=2, color=黑色, brand=联想))

Process finished with exit code 0

注入操作系统(OS)的属性

Spring EL表达式还可以获取操作系统的属性,我们可以注入到需要的变量中,示例如下:

@Data
@Component
public class User {
    //注入操作系统的属性
    @Value("#{systemProperties['os.name']}")
    private String OSName;
    //注入操作系统的属性
    @Value("#{systemProperties['file.encoding']}")
    private String fileEncoding;
}

从容器中获取对象信息,并和手动获取的操作系统属性进行对比

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
System.out.println(applicationContext.getBean("user"));

System.out.println("========手动获取信息=======");
Properties properties = System.getProperties();
System.out.println("os.name:" + properties.getProperty("os.name"));
System.out.println("file.encoding:" + properties.getProperty("file.encoding"));  

运行结果如下:

信息: Loading XML bean definitions from class path resource [applicationContext.xml]
User(OSName=Windows 10, fileEncoding=UTF-8)
========手动获取信息=======
os.name:Windows 10
file.encoding:UTF-8

Process finished with exit code 0

注入配置文件中数据

可以在需要的时候取出properties配置文件中的数据,注入到bean变量。我们知道spring配置文件中可以通过util:propertiescontext:property-placeholder两种标签来加载properties配置文件。不同加载方式,我们在Spring EL表达式中获取值的方式也不一样,区别如下:

  • util:properties 它是以声明bean方式来使用,创建了一个bean,这样我们在下面使用Spring EL取值的时候格式为 #{id['key']} 获取bean的属性。id为util:properties标签中id属性,key为配置文件中的key

  • context:property-placeholder 它是将配置文件加载至spring上下文中,我们只能通过${key}取得值,常用于bean的属性上,key为配置文件中的key

db1.properties

# db1.properties
mysql.driver=com.mysql.jdbc.Driver
mysql.url=jdbc:mysql://127.0.0.1:3306/java

db2.properties

# db2.properties
mysql.username=root
mysql.password=abc

applicationContext.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:context="http://www.springframework.org/schema/context"
       xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">

    <!--使用context:property-placeholder加载类路径下的db1.properties-->
    <context:property-placeholder location="classpath:db1.properties"/>

    <!--使用util:properties加载类路径下的db2.properties配置文件,id属性将在Spring EL中使用-->
    <util:properties id="db" location="classpath:db2.properties"/>

    <!--指定自动扫描的包路径-->
    <context:component-scan base-package="com.ls.entity"/>
</beans>
@Data
@Component
public class User {

    /*注入db1.properties中数据,db1.properties是xml配置文件中使用context:property-placeholder标签加载的
    mysql.username为db1.properties中的key
     */
    @Value("${mysql.driver}")
    private String driver;

    /*注入db2.properties中数据,db2.properties是xml配置文件中util:properties标签来加载的
    db为util:properties标签声明的id属性值
    mysql.driver为db2.properties中的key
     */
    @Value("#{db['mysql.username']}")
    private String username;
}

获取user对象并打印,结果如下:

八月 01, 2019 11:15:44 上午 org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@7daf6ecc: startup date [Thu Aug 01 11:15:44 CST 2019]; root of context hierarchy
八月 01, 2019 11:15:44 上午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [applicationContext.xml]
八月 01, 2019 11:15:45 上午 org.springframework.context.support.PropertySourcesPlaceholderConfigurer loadProperties
信息: Loading properties file from class path resource [db1.properties]
八月 01, 2019 11:15:45 上午 org.springframework.beans.factory.config.PropertiesFactoryBean loadProperties
信息: Loading properties file from class path resource [db2.properties]
User(driver=com.mysql.jdbc.Driver, username=root)

Process finished with exit code 0

Bean属性调用

还可以通过Spring EL表达式,调用容器中已有其他bean的属性。只要使用点号引用属性即可。属性可直接使用属性名, 属性名首字母大小写均可(只有首字母可不区分大小写); 如将容器中id为computer的对象的brand属性取出,赋值为user的likeBrand变量

@Data
@Component
public class Computer {
    @Value("#{'联想'}")
    private String brand;
}
@Data
@Component
public class User {
    //将容器中id为computer的对象的brand属性取出,赋值为user的likeBrand
    @Value("#{computer.brand}")
    private String likeBrand;
}

获取对象并打印结果如下:

信息: Loading properties file from class path resource [db2.properties]
User(likeBrand=联想)

Process finished with exit code 0

Bean方法调用

可以通过Spring EL表达式,调用容器中已有其他bean的成员或字符串的方法。Spring 会将方法返回值注入到属性中。只要使用点号引用方法名即可。方法使用方式和Java语法一样。

@Data
@Component
public class Computer {
    public String getStr() {
        return "Hello";
    }
    public String getStr(String string, int i) {
        return "Hello " + string + i;
    }
}
@Data
@Component
public class User {
    //调用id为computer的bean的无参方法getStr,返回值注入str1
    @Value("#{computer.getStr()}")
    private String str1;

    //调用id为computer的bean的有参方法getStr,参数可以直接传递
    @Value("#{computer.getStr('World',666)}")
    private String str2;

    //如果希望方法返回值的小写形式注入,可以直接继续调用方法
    @Value("#{computer.getStr('World',666).toLowerCase()}")
    private String str3;

    //也可以直接调用字符串的某个方法,将返回值注入,如将字符串的所有空格替换成空字符串的结果注入
    @Value("#{'h e   l l o w o r l d'.replaceAll(' ','')}")
    private String str4;
}

获取user对象并打印,结果如下:

信息: Loading properties file from class path resource [db2.properties]
User(str1=Hello, str2=Hello World666, str3=hello world666, str4=helloworld)

Process finished with exit code 0

T[运算符]

T操作符可以获取表达式对象的类型, 可以调用表达式对象的静态方法

@Data
@Component
public class Computer {
    public static String getStr() {
        return "Hello ";
    }
    public static String getStr(String string, double price) {
        return "Hello " + string + ",电脑价格为:" + price;
    }
}
@Data
@Component
public class User {
    //获取表达式对象的类型
    @Value("#{T(java.lang.String)}")
    private Class clazz1;
    //如果类型是java.lang包下的可以省略包名
    @Value("#{T(String)}")
    private Class clazz2;
    //如果类型不是java.lang包下的,一定不能省略,否则报错
    @Value("#{T(java.util.Scanner)}")
    private Class clazz3;
    @Value("#{T(com.ls.entity.Computer)}")
    private Class clazz4;
    //调用静态方法
    @Value("#{T(com.ls.entity.Computer).getStr()}")
    private String str;
    //调用静态方法
    @Value("#{T(com.ls.entity.Computer).getStr('World',55.5)}")
    private String str2;
}

获取user对象并打印,结果如下:

信息: Loading properties file from class path resource [db2.properties]
User(clazz1=class java.lang.String, clazz2=class java.lang.String, clazz3=class java.util.Scanner, clazz4=class com.ls.entity.Computer, str=Hello , str2=Hello World,电脑价格为:55.5)

Process finished with exit code 0

构造器

Spring EL表达式中,也使用new关键字来调用构造器,如果new的是java.lang包下的类的对象,可以省略包名。如果是自定义的类或者非java.lang包下的类,类名需要写全限定名。

@Data
@Component
public class User {
    //调用Computer的两个参数的构造方法,为User的Computer属性注入Computer对象
    @Value("#{new com.ls.entity.Computer('白色',66)}")
    private Computer computer;
    //注入新new的StringBuffer对象
    @Value("#{new StringBuffer('hello world!')}")
    private StringBuffer stringBuffer;
}

获取user对象并打印,结果如下:

信息: Loading properties file from class path resource [db2.properties]
User(computer=Computer(color=白色, price=66), stringBuffer=hello world!)

Process finished with exit code 0

内联集合给集合赋值

可以直接在表达式中定义集合,直接给bean对象的集合变量赋值。这就是内联。使用花括号语法。

@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
public class Computer {
    private String color;
    private Double price;
}
@Data
@Component
public class User {
    // 给Integer数组赋值
    @Value("#{{1,2,3,4}}")
    private Integer[] ids;
    // 给String数组赋值,使用单引号,单引号里面可以写字符串
    @Value("#{{'a','b','c'}}")
    private String[] hobbies;
    // 给char数组赋值,使用单引号,单引号里面只能写字符
    @Value("#{{'a','b','c'}}")
    private char[] chars;
    //给List集合赋值
    @Value("#{{1,2,3,4}}")
    private List<Integer> ids2;
    //给List集合赋值
    @Value("#{{'aa','b','c'}}")
    private List<String> hobbies2;
    //给List集合赋bean类型的值,不会自动去掉重复的
    @Value("#{{new com.ls.entity.Computer('白色',55.5),new com.ls.entity.Computer('黑色',66),new com.ls.entity.Computer('白色',55.5)}}")
    private List<Computer> computers;
    //给Set集合赋值,会自动去掉重复的
    @Value("#{{1,2,1,4}}")
    private Set<Integer> ids3;
    //给Set集合赋值,会自动去掉重复的
    @Value("#{{'aa','b','aa'}}")
    private Set<String> hobbies3;
    //给Set集合赋bean类型的值,因为Computer重写了hashCode和equals方法,set集合会自动去掉重复的Computer
    @Value("#{{new com.ls.entity.Computer('白色',55.5),new com.ls.entity.Computer('黑色',66),new com.ls.entity.Computer('白色',55.5)}}")
    private Set<Computer> computers2;
    //给二维数组赋值
    @Value("#{{{'r1c1','r1c2'},{'r2c1','r2c2','r2c3'}}}")
    private String[][] address;
    //给嵌套集合赋值
    @Value("#{{{'aa','bb'},{'xx','yy','zz'}}}")
    private List<List<String>> address2;
}

获取user对象并打印,结果如下:

八月 01, 2019 3:10:05 下午 org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@7daf6ecc: startup date [Thu Aug 01 15:10:05 CST 2019]; root of context hierarchy
八月 01, 2019 3:10:06 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [applicationContext.xml]
八月 01, 2019 3:10:06 下午 org.springframework.context.support.PropertySourcesPlaceholderConfigurer loadProperties
信息: Loading properties file from class path resource [db1.properties]
八月 01, 2019 3:10:06 下午 org.springframework.beans.factory.config.PropertiesFactoryBean loadProperties
信息: Loading properties file from class path resource [db2.properties]
User(ids=[1, 2, 3, 4], hobbies=[a, b, c], chars=[a, b, c], ids2=[1, 2, 3, 4], hobbies2=[aa, b, c], computers=[Computer(color=白色, price=55.5), Computer(color=黑色, price=66.0), Computer(color=白色, price=55.5)], ids3=[1, 2, 4], hobbies3=[aa, b], computers2=[Computer(color=白色, price=55.5), Computer(color=黑色, price=66.0)], address=[[r1c1, r1c2], [r2c1, r2c2, r2c3]], address2=[[aa, bb], [xx, yy, zz]])

Process finished with exit code 0

内联Map给map赋值

使用类似JSON的语法,键和值之间用冒号隔开。

@Data
@Component
public class User {
    //给map集合赋值:格式#{{key:value,key2:value2}}
    @Value("#{{name:'lucy',age:18}}")
    private Map<String, String> map;
    //给map集合赋值:格式#{{key:value,key2:value2}}
    @Value("#{{person:{name:'lucy',age:18,address:'北京'}}}")
    private Map<String, Map<String, String>> map2;
}

获取user对象并打印,结果如下:

信息: Loading properties file from class path resource [db2.properties]
User(map={name=lucy, age=18}, map2={person={name=lucy, age=18, address=北京}})

Process finished with exit code 0

给数组赋值

可以使用内联的方式给数组赋值。我们还可以使用类似Java的语法给数组赋值,可以给出初始值,多维数组也受支持。

@Data
@Component
public class User {
    //直接new一个数组
    @Value("#{new int[5]}")
    private int[] ids;
    //new数组的同时还可以,指定初始值
    @Value("#{new int[3]{1,2,3}}")
    private int[] ids2;
    //还可以给多维数组初始化,二维数组使用new初始化的时候,不可以给初始值了,否则报错
    @Value("#{new int[3][2]}")
    private int[][] ids3;
}

获取user对象并打印,结果如下:

八月 01, 2019 3:38:23 下午 org.springframework.beans.factory.config.PropertiesFactoryBean loadProperties
信息: Loading properties file from class path resource [db2.properties]
User(ids=[0, 0, 0, 0, 0], ids2=[1, 2, 3], ids3=[[0, 0], [0, 0], [0, 0]])

Process finished with exit code 0

Elvis运算符

Spring EL表达式支持Elvis运算符,语法是变量?:默认值 意思是当某变量不为null不表达空的意思(如空字符串)的时候使用该变量,当该变量为null或表达空的意思的时候使用指定的默认值。

@Data
@Component
public class Computer {
    //string1赋值白色字符串
    @Value("#{'白色'}")
    private String string1;
    //string2赋值空字符串
    @Value("#{''}")
    private String string2;
    //string3不赋值,默认为null
    private String string3;
}
@Data
@Component
public class User {
    //如果Spring容器中id为computer对象的string1属性为空字符串或null则赋值为默认值哈哈,否则赋值为computer对象的string1属性
    @Value("#{computer.string1?:'哈哈'}")
    private String username1;
    @Value("#{computer.string2?:'哈哈2'}")
    private String username2;
    @Value("#{computer.string3?:'哈哈3'}")
    private String username3;
}

获取computer和user对象并打印,结果如下:

八月 01, 2019 3:47:52 下午 org.springframework.beans.factory.config.PropertiesFactoryBean loadProperties
信息: Loading properties file from class path resource [db2.properties]
Computer(string1=白色, string2=, string3=null)
User(username1=白色, username2=哈哈2, username3=哈哈3)

Process finished with exit code 0

#this和#root

#this#root代表了表达式上下文的对象,可以提前定义一个上下文根对象,这样就可以使用#root来引用这个根对象。而且#root被定义始终引用上下文根对象。通过跟队形可以向表达式公开一个全面的自定义量。#this则根据当前求值环境的不同而变化。#this来表示当前的对象. 常用于集合的过滤,下面的例子中,#this即每次循环的值。

@Data
@Component
public class Computer {
    private String brand;
    @Value("#{{1,2,3,4,5}}")
    private List<Integer> computerIds;
}
@Data
@Component
public class User {
    //#root用法演示
    static {
        //手动创建解析器来解析表达式
        //造一个电脑对象,用于将其存入上下文对象的根对象进行演示
        Computer computer = new Computer();
        computer.setBrand("华硕");
        //获取上下文对象
        StandardEvaluationContext context = new StandardEvaluationContext();
        //将computer存入上下文根对象,以在别的地方使用Spring EL表达式#root来获取在此存入的computer对象
        context.setRootObject(computer);
        //手动创建一个解析器
        ExpressionParser parser = new SpelExpressionParser();
        //Spring EL表达式,取出上下文根对象,由于根对象为我们手动存的电脑,所以可以获取其brand属性值
        String statement = "#root.brand";
        //解析表达式
        Expression expression = parser.parseExpression(statement);
        //获取解析后的结果
        String result = expression.getValue(context, String.class);
        //打印结果
        System.out.println("取出根对象computer的brand属性为:"+result);
    }
   /* #this用法演示
    * 集合.?[expression]: 是一种语法,本文后面即有介绍,目的是选择符合条件的元素#this即代表遍历集合时每次循环的值,
    * 此处意思是遍历容器中id为computer对象的computerIds属性(前提是此属一个集合),
    * 选出集合中大于2的所有元素生成一个新的集合,并将新的结合注入给user对象的userIds属性.
    */
    @Value("#{computer.computerIds.?[#this>2]}")
    private List<Integer> userIds;
}

获取computer和user对象并打印,结果如下:

八月 01, 2019 4:38:13 下午 org.springframework.beans.factory.config.PropertiesFactoryBean loadProperties
信息: Loading properties file from class path resource [db2.properties]
取出根对象computer的brand属性为:华硕
Computer(brand=null, computerIds=[1, 2, 3, 4, 5])
User(userIds=[3, 4, 5])

Process finished with exit code 0

操作符(运算符)

表达式中支持各种运算符,运算规则和Java规则类似。常用运算符如下:

  • 数学运算符: 加 (+),减 (-),乘 (*),除 (/),取模 (%),取幂(^)
  • 关系运算符: 等于 (==, eq),不等于 (!=, ne),小于 (<, lt),小于等于(<= , le),大于(>, gt),大于等于 (>=, ge) (注意:Xml配置,应该用“<"代替小于号“<”,用“le”代替小于等于“<=” )
  • 逻辑运算符: and(&&),or(||),and not(!)
  • 其他运算符: 三元操作符, instanceof, 正则匹配,between等
@Data
@Component
public class Computer {
    //注入字符串str1
    @Value("#{'str1'}")
    private String str1;
    //注入空字符串
    @Value("#{''}")
    private String str2;
    //不赋值默认为 null
    private String str3;
}
@Data
@Component
public class User {
    //加法
    @Value("#{3 + 2}")
    private int jia;    //5
    //减法
    @Value("#{3 - 2}")
    private int jian;   //1
    //乘法
    @Value("#{3 * 2}")
    private int cheng;  //6
    //除法
    @Value("#{3 / 2}")
    private int chu;    //1
    //取模
    @Value("#{3 % 2}")
    private int mo;     //1
    //取幂
    @Value("#{3 ^ 2}")
    private int mi;     //9
    //逻辑运算符,注意&&和||必须使用两个&或|才行,不能只使用一个
    //and
    @Value("#{true and false && false}")
    private boolean booleanAnd; //false
    //or
    @Value("#{true or false || false}")
    private boolean booleanOr;  //true
    //not
    @Value("#{not true}")
    private boolean booleanNot; //false
    @Value("#{! true}")
    private boolean booleanNot2;//false
    //关系运算符
    //等于
    @Value("#{1 == 1 or 1 eq 1}")
    private boolean dengYu;     //true
    //不等于
    @Value("#{1 != 1 or 1 ne 1}")
    private boolean buDengYu;   //false
    //小于
    @Value("#{1 < 1 or 1 lt 1}")
    private boolean xiaoYu;     //false
    //小于等于
    @Value("#{1 <= 1 or 1 le 1}")
    private boolean xiaoYuDengYu;//true
    //大于
    @Value("#{1 > 1 or 1 gt 1}")
    private boolean daYu;       //false
    //大于等于
    @Value("#{1 >= 1 or 1 ge 1}")
    private boolean daYuDengYu; //true
    //非空值val,那么表达式val > null恒为真
    @Value("#{'' > computer.str1}")
    private boolean valAndNull;     //false
    @Value("#{'' >= computer.str2}")
    private boolean valAndNull2;    //true
    @Value("#{'' > computer.str3}")
    private boolean valAndNull3;    //true
    //其他运算符
    //三元操作符/三目运算符
    @Value("#{3 > 2 ? 5 : 6}")
    private int age;                //5
    //instanceof使用
    @Value("#{1 instanceof T(Integer)}")
    private boolean instanceofTest; //true
    @Value("#{computer instanceof T(com.ls.entity.Computer)}")
    private boolean instanceofTest2; //true
    /*
     * between使用,判断一个数据是否在两个数据之间格式:
     * a between {m,n} m的值必须在n前面,也就是m必须比n小, m和n不能交换顺序,否则虽然不会报错但是无法获得正确的比较结果
     */
    @Value("#{3 between {2,5}}")
    private boolean betweenTest;     //true
    @Value("#{'ab' between {'aa','ac'}}")
    private boolean betweenTest2;     //true
    //正则表达式匹配,我们可以使用正则表达式和 matches关键字匹配数据,只有完全匹配的时候返回true,match前的字符串不能为null否则报错
    @Value("#{'35' matches '\\d+'}")
    private boolean regExp; //true
    @Value("#{computer.str1 matches '\\w+'}")
    private boolean regExp2; //true
}

获取computer和user对象并打印,结果如下:

八月 01, 2019 5:38:19 下午 org.springframework.beans.factory.config.PropertiesFactoryBean loadProperties
信息: Loading properties file from class path resource [db2.properties]
Computer(str1=str1, str2=, str3=null)
User(jia=5, jian=1, cheng=6, chu=1, mo=1, mi=9, booleanAnd=false, booleanOr=true, booleanNot=false, booleanNot2=false, dengYu=true, buDengYu=false, xiaoYu=false, xiaoYuDengYu=true, daYu=false, daYuDengYu=true, valAndNull=false, valAndNull2=true, valAndNull3=true, age=5, instanceofTest=true, instanceofTest2=true, betweenTest=true, betweenTest2=true, regExp=true, regExp2=true)

Process finished with exit code 0

注意:空值的处理,假设有非空值val,那么表达式 val > null 恒为真,这一点需要注意。

安全导航运算符

这是来自Groovy的一个功能,语法是?.,当然有些语言也提供了这个功能。当我们对对象的某个属性求值时,如果该对象本身为空,就会抛出空指针异常,如果使用安全导航运算符,空对象的属性就会简单的返回空。

@Data
@Component
public class Computer {
    //注入字符串str1
    @Value("#{'str1'}")
    private String str1;
    //注入空字符串
    @Value("#{''}")
    private String str2;
    //不赋值默认为 null
    private String str3;
}
@Data
@Component
public class User {
    //安全导航运算符 ?. 避免空指针异常,如果调用对象为null则直接返回null,避免了空指针异常
    @Value("#{computer.str1?.concat('abc')}")
    private String str1;
    @Value("#{computer.str2?.concat('abc')}")
    private String str2;
    @Value("#{computer.str3?.concat('abc')}")
    private String str3;
}

获取computer和user对象并打印,结果如下:

八月 01, 2019 5:48:49 下午 org.springframework.beans.factory.config.PropertiesFactoryBean loadProperties
信息: Loading properties file from class path resource [db2.properties]
Computer(str1=str1, str2=, str3=null)
User(str1=str1abc, str2=abc, str3=null)

Process finished with exit code 0

我们看到computer.str3为null并没有报空指针异常,而是直接给str3注入了null,另外两项不为null的都正常执行,后面拼接了字符串abc。

从数组、List、Map集合取值

我们可以使用Spring EL表达式从数组,list集合,set集合或map中取值。

  • 数组和列表可以使用方括号引用对应索引的元素,list[index]
  • Map类型可以使用方括号引用键对应的值,map[key]
@Data
@Component
public class Computer {
    //给数组赋值
    @Value("#{{1,2,3,4}}")
    private Integer[] ids;
    //给List集合赋值
    @Value("#{{'list1','list2','list3'}}")
    private List<String> hobbiesList;
    //给List集合赋值
    @Value("#{{'set1','set3','set2'}}")
    private Set<String> hobbiesSet;
    @Value("#{{'key1':'value1','key2':'value2'}}")
    private Map<String,String> map;
}
@Data
@Component
public class User {
    //取出数组0号索引的值
    @Value("#{computer.ids[0]}")
    private Integer id;
    //取出List集合中索引为0的值
    @Value("#{computer.hobbiesList[0]}")
    private String hobbyList;
    //取出Set集合中索引为0的值,set集合也可以通过索引取值,没想到吧
    @Value("#{computer.hobbiesSet[0]}")
    private String hobbySet;
    //取出map集合中key为key1的值
    @Value("#{computer.map['key1']}")
    private String MapStr;
}

获取computer和user对象并打印,结果如下:

八月 01, 2019 6:08:30 下午 org.springframework.beans.factory.config.PropertiesFactoryBean loadProperties
信息: Loading properties file from class path resource [db2.properties]
Computer(ids=[1, 2, 3, 4], hobbiesList=[list1, list2, list3], hobbiesSet=[set1, set3, set2], map={key1=value1, key2=value2})
User(id=1, hobbyList=list1, hobbySet=set1, MapStr=value1)

Process finished with exit code 0

注意:此种方式甚至可以从set集合中根据索引取值。

集合选择

我们可以在Spring EL表达式中对集合进行过滤选择。Spring会迭代集合对象的每一个元素,并使用选择表达式判断该元素是否满足条件,最后返回由满足条件的元素组成的新的集合。共有如下四种用法:

  • ? [expression]: 选择符合条件的元素
  • ^ [expression]: 选择符合条件的第一个元素
  • $ [expression]: 选择符合条件的最后一个元素
  • ! [expression]: 可对集合中的元素挨个进行处理

对于集合可以使用 #this 变量代表每次迭代出的元素进行过滤, 对于map, 可分别对keySet及valueSet分别使用key和value关键字,分别代表每次迭代出的键和值;

@Data
@Component
public class Computer {
    //给List集合赋值
    @Value("#{{'list1','list2','list3','list4'}}")
    private List<String> list;
    //给map集合赋值
    @Value("#{{'key1':'value1','key2':'value2','key3':'value3','key4':'value4'}}")
    private Map<String, String> map;
}
@Data
@Component
public class User {
    //筛选集合中所有大于list2的数据组成新集合并返回
    @Value("#{computer.list.?[#this > 'list2']}")
    private List<String> list1;
    //筛选集合中第一个大于list2的数据组成新集合并返回
    @Value("#{computer.list.^[#this > 'list2']}")
    // private List<String> list2;
    private String Str2;
    //筛选集合中最后一个大于list2的数据组成新集合并返回
    @Value("#{computer.list.$[#this > 'list2']}")
    private List<String> list3;
    //集合中所有元素加字符串abc
    @Value("#{computer.list.![#this + 'abc']}")
    private List<String> list4;
    //筛选map中所有key大于key2的数据组成新map并返回
    @Value("#{computer.map.?[key > 'key2']}")
    private Map<String, String> map1;
    //筛选map中第一个key大于key2的数据组成新map并返回
    @Value("#{computer.map.^[key > 'key2']}")
    private Map<String, String> map2;
    //筛选map中最后一个value大于value2的数据组成新map并返回
    @Value("#{computer.map.$[value > 'value2']}")
    private Map<String, String> map3;
    //筛选map中最后一个key小于key5并且value大于value2的数据组成新map并返回
    @Value("#{computer.map.$[key < 'key5' and value > 'value2']}")
    private Map<String, String> map4;
    //map中所有key加字符串abc,返回值为List集合而不再是map集合
    @Value("#{computer.map.![key + 'abc']}")
    private List<String> mapToList;
    //map中所有value加字符串abc,返回值为List集合而不再是map集合
    @Value("#{computer.map.![value + 'abc']}")
    private List<String> mapToList2;
}

获取computer和user对象并打印,结果如下:

八月 01, 2019 6:47:18 下午 org.springframework.beans.factory.config.PropertiesFactoryBean loadProperties
信息: Loading properties file from class path resource [db2.properties]
Computer(list=[list1, list2, list3, list4], map={key1=value1, key2=value2, key3=value3, key4=value4})
User(list1=[list3, list4], Str2=list3, list3=[list4], list4=[list1abc, list2abc, list3abc, list4abc], map1={key3=value3, key4=value4}, map2={key3=value3}, map3={key4=value4}, map4={key4=value4}, mapToList=[key1abc, key2abc, key3abc, key4abc], mapToList2=[value1abc, value2abc, value3abc, value4abc])

Process finished with exit code 0

注意:通过 和 $[expression] 筛选出来的结果一定最多只有一个,因此可以给非集合类型注入,如:如果筛选出来的是字符串则可以给String字符串类型注入。但是 ? [expression]或 ![expression] 返回的符合条件的结果哪怕实际真的只有一个也不能注入非集合类型。

集合投影

我们可以在Spring EL表达式中,将一个集合中所有元素的某属性抽取出来,组成一个新集合。语法是![投影表达式]。(其实在上面我们已经使用过了)

@Data
@Component
public class Computer {
    private int price;
    private String brand;
}
@Data
@Component
public class User {
    //new 三个Computer对象给集合赋值
    @Value("#{{new com.ls.entity.Computer(88,'联想'),new com.ls.entity.Computer(77,'弘基'),new com.ls.entity.Computer(99,'华硕')}}")
    private List<Computer> computers;
    //将computers集合中所有computer的brand属性投影到brands集合中,组成新集合
    @Value("#{user.computers.![#this.brand]}")
    private List<String> brands;
    //将computers集合中所有computer的price属性值乘以10投影到 prices集合中,组成新集合
    @Value("#{user.computers.![#this.price * 10]}")
    private List<String> prices;
}

获取computer和user对象并打印,结果如下:

八月 01, 2019 7:00:05 下午 org.springframework.beans.factory.config.PropertiesFactoryBean loadProperties
信息: Loading properties file from class path resource [db2.properties]
Computer(price=0, brand=null)
User(computers=[Computer(price=88, brand=联想), Computer(price=77, brand=弘基), Computer(price=99, brand=华硕)], brands=[联想, 弘基, 华硕], prices=[880, 770, 990])

Process finished with exit code 0

${}和#{}的区别

首先,${}是变量用来插值的一种表达式。#{}是SPEL表达式。他们的作用都是可以通过spring把值注入给某个属性。

  • ${key名称}
    • 用户获取外部文件中指定key的值
    • 可以出现在xml配置文件中,也可以出现在注解@Value中
    • 一般用户获取数据库配置文件的内容信息等
  • #{表达式}
    • 是Spring EL表达式的格式
    • 可以出现在xml配置文件中,也可以出现在注解@Value中
    • 可以任意表达式,支持运算符等

在使用的时候也允许 #{'$(key)'} 这样的格式使用.比如:@Value("#{'${jdbc.url}'}");

JAVA解析Spring EL表达式

文字表达式

支持的文字表达式类型有字符串、数值(int、real、hex)、布尔和null。字符串由单引号分隔。若要将单引号本身放在字符串中,请使用两个单引号字符。

通常来说,不会单纯的定义一个简单的文字表达式,而是通过方法调用等等复杂的操作,来完成一个功能:

// 定义Parser,可以定义全局的parser
ExpressionParser parser = new SpelExpressionParser();
// 获取字符串 "Hello World"
String helloWorld = (String) parser.parseExpression("'Hello World'").getValue();
// double类型 6.0221415E23
double avogadrosNumber = (Double) parser.parseExpression("6.0221415E+23").getValue();
// int类型 2147483647
int maxValue = (Integer) parser.parseExpression("0x7FFFFFFF").getValue();
// true
boolean trueValue = (Boolean) parser.parseExpression("true").getValue();
// null
Object nullValue = parser.parseExpression("null").getValue();

属性, 数组, List, Map,和 索引

属性操作

注意!属性名的第一个字母不区分大小写

// 定义Parser,可以定义全局的parser
ExpressionParser parser = new SpelExpressionParser();
// 注意!属性名的第一个字母不区分大小写。 birthdate.year等效于Birthdate.Year
// 取出Inventor 中,birthdate属性的year属性
Inventor zhangsan = new Inventor("zhangsan", new Date(), "China");
// 定义StandardEvaluationContext ,传入一个操作对象
StandardEvaluationContext zhangsanContext = new StandardEvaluationContext(zhangsan);
int year = (Integer) parser.parseExpression("birthdate.year + 1900").getValue(zhangsanContext);
System.out.println(year); // 2023
//取出Inventor的placeOfBirth的city属性
PlaceOfBirth placeOfBirth = new PlaceOfBirth("长沙", "中国");
zhangsan.setPlaceOfBirth(placeOfBirth);
String city = (String) parser.parseExpression("placeOfBirth.City").getValue(zhangsanContext);
System.out.println(city); // 长沙

数组和List

数组和List的内容是通过使用方括号符号获得的。

// 定义Parser,可以定义全局的parser
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
// 省略数据初始化
// 取出tesla对象的inventions 第四个数据
String invention = parser.parseExpression("inventions[3]").getValue(context, tesla, String.class);
// 取出ieee对象的第一个Member的name属性
String name = parser.parseExpression("Members[0].Name").getValue(context, ieee, String.class);
// 取出ieee对象的第一个Member中的第七个Inventions
String invention = parser.parseExpression("Members[0].Inventions[6]").getValue(context, ieee, String.class);

Map

Map操作是通过key来获取的

// 取出societyContext的Officers中的key为president的值
Inventor pupin = parser.parseExpression("Officers['president']").getValue(societyContext, Inventor.class);
String city = parser.parseExpression("Officers['president'].PlaceOfBirth.City").getValue(societyContext, String.class);
// Officers中key为advisors的值取第一个
parser.parseExpression("Officers['advisors'][0].PlaceOfBirth.Country").setValue(societyContext, "Croatia");

内嵌List

可以使用{}符号在表达式中直接表示List。{}本身意味着一个空列表。

// 定义Parser,可以定义全局的parser
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
// [1, 2, 3, 4]
List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue(context);
System.out.println(numbers);
// 嵌套: [[a, b], [x, y]]
List listOfLists = (List) parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context);
System.out.println(listOfLists);

内嵌Map

使用{key:value}符号在表达式中表示Map。{:}意味着空Map。

// 定义Parser,可以定义全局的parser
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
// {name=Nikola, dob=10-July-1856}
Map inventorInfo = (Map) parser.parseExpression("{name:'Nikola',dob:'10-July-1856'}").getValue(context);
System.out.println(inventorInfo);
// 嵌套:{name={first=Nikola, last=Tesla}, dob={day=10, month=July, year=1856}}
Map mapOfMaps = (Map) parser.parseExpression("{name:{first:'Nikola',last:'Tesla'},dob:{day:10,month:'July',year:1856}}").getValue(context);
System.out.println(mapOfMaps);
// List与Map可以嵌套使用,互相结合。
// 嵌套:[{name={first=Nikola, last=Tesla}}, {dob={day=10, month=July, year=1856}}]
List listOfMaps = (List) parser.parseExpression("{{name:{first:'Nikola',last:'Tesla'}},{dob:{day:10,month:'July',year:1856}}}").getValue(context);
System.out.println(listOfMaps);

构建数组

多维数组不提供初始化方式。

// 定义Parser,可以定义全局的parser
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue(context);
// 数组并初始化
int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue(context);
// 多维数组
int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue(context);

调用类的方法

ExpressionParser parser = new SpelExpressionParser();
// 调用substring方法
String bc = parser.parseExpression("'abc'.substring(1, 3)").getValue(String.class);
// 调用societyContext中对象的isMember方法,并传值。
StandardEvaluationContext societyContext = new StandardEvaluationContext(society);
boolean isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(societyContext, Boolean.class);

SpEL操作符

标准运算符

使用标准运算符表示法支持关系运算符(等于、不等于、小于、小于或等于、大于和大于或等于)。
null不被视为任何东西(即不为零)。因此,任何其他值总是大于null (X > null总是为真),并且没有任何其他值小于零(X < null总是为假)。

ExpressionParser parser = newSpelExpressionParser();
// evaluates to true
boolean trueValue = parser.parseExpression("2 == 2").getValue(Boolean.class);
// evaluates to false
boolean falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean.class);
// evaluates to true
boolean trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean.class);

instanceof 和 正则表达式的匹配操作符

使用基本类型时要小心,因为它们会立即被装箱为包装器类型,所以1 instanceof T(int)会计算为false,而1 instanceof T(Integer)会计算为true。

// evaluates to false
boolean falseValue = parser.parseExpression("'xyz' instanceof T(Integer)").getValue(Boolean.class);
// evaluates to true
boolean trueValue = parser.parseExpression("'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
//evaluates to false
boolean falseValue = parser.parseExpression("'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);

操作符的英文等价标识

每个符号操作符也可以被指定为纯字母的等价物。这避免了所使用的符号对于嵌入表达式的文档类型具有特殊含义的问题(例如在XML文档中)。所有文本操作符都不区分大小写。对应的文本是:
lt (<)
gt (>)
le (<=)
ge (>=)
eq (==)
ne (!=)
div (/)
mod (%)
not (!)

逻辑运算符

SpEL支持以下逻辑运算符:and、or、not

// 结果: false
boolean falseValue = parser.parseExpression("true and false").getValue(Boolean.class);
// 调用方法并根据方法返回值判断
String expression = "isMember('Nikola Tesla') and isMember('Mihajlo Pupin')";
boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
// -- OR --
boolean trueValue = parser.parseExpression("true or false").getValue(Boolean.class);
// 调用方法并根据方法返回值判断
String expression = "isMember('Nikola Tesla') or isMember('Albert Einstein')";
boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
// -- NOT --
// 取反
boolean falseValue = parser.parseExpression("!true").getValue(Boolean.class);
// -- AND and NOT --
String expression = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')";
boolean falseValue = parser.parseExpression(expression).getValue(societyContext, 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

赋值运算符

若要给对象设置属性,请使用赋值运算符(=)。这通常在对setValue的调用中完成,但也可以在对getValue的调用中完成。

// 定义Parser,可以定义全局的parser
ExpressionParser parser = new SpelExpressionParser();
Inventor inventor = new Inventor();
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
parser.parseExpression("Name").setValue(context, inventor, "Aleksandar Seovic");
System.out.println(inventor.getName()); // Aleksandar Seovic
// 或者这样赋值
String aleks = parser.parseExpression("Name = 'Aleksandar Seovic2'").getValue(context, inventor, String.class);
System.out.println(inventor.getName()); // Aleksandar Seovic2

获取类的类型

可以使用特殊的T运算符来指定java.lang.Class的实例(类型)。静态方法也是通过使用这个操作符来调用的。
StandardEvaluationContext使用TypeLocator来查找类型,StandardTypeLocator(可以替换)是基于对java.lang包的理解而构建的。所以java.lang中类型的T()引用不需要使用全限定名,但是其他包中的类,必须使用全限定名。

ExpressionParser parser = new SpelExpressionParser();
Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class);
Class stringClass = parser.parseExpression("T(String)").getValue(Class.class);
boolean trueValue = parser.parseExpression("T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR").getValue(Boolean.class);

调用类构造器

使用new运算符调用构造函数。除了基本类型(int、float等)和String之外,所有类型都应该使用完全限定的类名。

Inventor einstein = p.parseExpression("new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')").getValue(Inventor.class);
//创建一个新的Inventor,并且添加到members的list中
p.parseExpression("Members.add(new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German'))").getValue(societyContext);

SpEL变量

基本使用

可以使用#variableName语法在表达式中引用变量。通过在EvaluationContext实现上使用setVariable方法来设置变量

ExpressionParser parser = new SpelExpressionParser();
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
context.setVariable("newName", "Mike Tesla"); // 设置变量
// 获取变量newName,并将其赋值给name属性
parser.parseExpression("Name = #newName").getValue(context, tesla);
System.out.println(tesla.getName());  // "Mike Tesla"

#this#root变量

  • #this变量引用当前的评估对象(根据该评估对象解析非限定引用)。
  • #root变量总是被定义并引用根上下文对象。虽然#this可能会随着表达式的组成部分的计算而变化,但是#root总是指根。
// 创建一个Integer数组
List<Integer> primes = new ArrayList<Integer>();
primes.addAll(Arrays.asList(2,3,5,7,11,13,17));
// create parser and set variable 'primes' as the array of integers
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
context.setVariable("primes", primes);
//  numbers > 10 的 list 
// evaluates to [11, 13, 17]
List<Integer> primesGreaterThanTen = (List<Integer>) parser.parseExpression("#primes.?[#this>10]").getValue(context);
System.out.println(primesGreaterThanTen);

调用类静态方法

// 方法定义的方式
Method method = ...;
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
context.setVariable("myFunction", method);
// 准备一个要调用的目标方法
public class StringUtils {
    public static String reverseString(String input) {
        StringBuilder backwards = new StringBuilder(input.length());
        for (int i = 0; i < input.length(); i++) {
            backwards.append(input.charAt(input.length() - 1 - i));
        }
        return backwards.toString();
    }
}

// 调用目标静态方法
public static void main(String[] args) throws NoSuchMethodException {
    ExpressionParser parser = new SpelExpressionParser();
    EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
    // 获取要调用的方法
    context.setVariable("reverseString",StringUtils.class.getDeclaredMethod("reverseString", String.class));
    // 调用
    String helloWorldReversed = parser.parseExpression("#reverseString('hello')").getValue(context, String.class);
}

Bean引用

如果已经用bean解析器配置了评估上下文,则可以使用@符号从表达式中查找bean。

ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new MyBeanResolver());
// 将调用MyBeanResolver 的 resolve(context,"something")
Object bean = parser.parseExpression("@something").getValue(context);
// 注意!MyBeanResolver 可以使用系统自带的BeanFactoryResolver,写成:
context.setBeanResolver(new BeanFactoryResolver(applicationContext));
// BeanFactoryResolver的resolve方法,就是通过Bean的名称来获取Bean:
@Override
public Object resolve(EvaluationContext context, String beanName) throws AccessException {
    try {
        return this.beanFactory.getBean(beanName);
    }catch (BeansException ex) {
        throw new AccessException("Could not resolve bean reference against BeanFactory", ex);
    }
}

要访问工厂bean本身,应该在bean名称前加上&符号:

ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new MyBeanResolver());
// 将调用MyBeanResolver 的 resolve(context,"something")
Object bean = parser.parseExpression("&foo").getValue(context);

三元运算符(If-Then-Else)

// 使用示例
String falseString = parser.parseExpression("false ? 'trueExp' : 'falseExp'").getValue(String.class);
// name属性设置值
parser.parseExpression("Name").setValue(societyContext, "IEEE");
// 设置变量
societyContext.setVariable("queryName", "Nikola Tesla");
// 三元运算符
expression = "isMember(#queryName)? #queryName + ' is a member of the ' " +"+ Name + ' Society' : #queryName + ' is not a member of the ' + Name + ' Society'";
String queryResultString = parser.parseExpression(expression).getValue(societyContext, String.class);
// queryResultString = "Nikola Tesla is a member of the IEEE Society"

Elvis操作符

Elvis运算符是三元运算符语法的缩写,用于Groovy语言中。使用三元运算符语法时,通常需要将一个变量重复两次,如下例所示:

String name = "Elvis Presley";
String displayName = (name != null ? name : "Unknown");

可以使用Elvis运算符(因与Elvis的发型相似而得名)优化。以下示例显示了如何使用Elvis运算符:

ExpressionParser parser = new SpelExpressionParser();
String name = parser.parseExpression("name?:'Unknown'").getValue(String.class);
System.out.println(name);  // 'Unknown'

更复杂的实例:

ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
String name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, tesla, String.class);
System.out.println(name);  // Nikola Tesla
tesla.setName(null);
name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, tesla, String.class);
System.out.println(name);  // Elvis Presley

安全导航操作员

安全导航操作符用于避免NullPointerException,来自Groovy语言。通常,当引用一个对象时,可能需要在访问该对象的方法或属性之前验证它不为null。为了避免这种情况,安全导航运算符返回null,而不是引发异常。以下示例显示了如何使用安全导航运算符:

ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
tesla.setPlaceOfBirth(new PlaceOfBirth("Smiljan"));
String city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, tesla, String.class);
System.out.println(city);  // Smiljan
tesla.setPlaceOfBirth(null);
city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, tesla, String.class);
System.out.println(city);  // null - does not throw NullPointerException!!!

集合选择

// 语法.?[selectionExpression]
List<Inventor> list = (List<Inventor>) parser.parseExpression("Members.?[Nationality == 'Serbian']").getValue(societyContext);
// 返回value小于27的值
Map newMap = parser.parseExpression("map.?[value<27]").getValue();

除了返回所有选定的元素之外,还可以只检索第一个或最后一个值。要获得匹配选择的第一个条目,语法是。.^[selectionExpression].要获得最后一个匹配的选择,语法是。.$[选择表达式]

集合投影

// 语法:.![projectionExpression]
// returns ['Smiljan', 'Idvor' ]
List placesOfBirth = (List)parser.parseExpression("Members.![placeOfBirth.city]");

表达式模板

// 通常使用#{}作为模板,与字符串拼接起来
String randomPhrase = parser.parseExpression("random number is #{T(java.lang.Math).random()}",new TemplateParserContext()).getValue(String.class);
// evaluates to "random number is 0.7038186818312008"
// TemplateParserContext 的定义
public class TemplateParserContext implements ParserContext {
    public String getExpressionPrefix() {
        return "#{";
    }
    public String getExpressionSuffix() {
        return "}";
    }
    public boolean isTemplate() {
        return true;
    }
}

  1. expression ↩︎

posted @   明小子@  阅读(463)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· Deepseek官网太卡,教你白嫖阿里云的Deepseek-R1满血版
· 2分钟学会 DeepSeek API,竟然比官方更好用!
· .NET 使用 DeepSeek R1 开发智能 AI 客户端
· DeepSeek本地性能调优
· 一文掌握DeepSeek本地部署+Page Assist浏览器插件+C#接口调用+局域网访问!全攻略
点击右上角即可分享
微信分享提示