Spring EL表示式使用詳解

明小子@發表於2024-08-30

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把值注入給某個屬性。

下面以註解的方式列舉一些Spring表示式的常用用法。xml配置檔案中也是同樣的用法。

注入字面值

表示式支援各種型別的字面值。字串或字元型別的字面值需要使用單引號包括,其他型別字面值直接寫就行。示例如下:
Computer

@Data
@Component
public class Computer {
    @Value("#{2}")
    private Integer id;
    @Value("#{'黑色'}")
    private String color;
    @Value("#{'聯想'}")
    private String brand;
}

User

@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表示式還可以獲取作業系統的屬性,我們可以注入到需要的變數中,示例如下:
User

@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:properties和context: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。

下面透過一個簡單的示例,來取出兩種載入方式載入的properties配置檔案的值。
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>

User

@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

可以看到,透過兩種方式載入的properties配置檔案我們可以對應透過兩種不同的寫法獲取到配置檔案中的值,併成功的注入到user的成員變數。

Bean屬性呼叫

我們還可以透過Spring EL表示式,呼叫容器中已有其他bean的屬性。只要使用點號引用屬性即可。屬性可直接使用屬性名,屬性名首字母大小寫均可(只有首字母可不區分大小寫);如,將容器中id為computer的物件的brand屬性取出,賦值為user的likeBrand變數,我們可以這樣寫。
Computer

@Data
@Component
public class Computer {
    @Value("#{'聯想'}")
    private String brand;
}

User

@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語法一樣。
Computer

@Data
@Component
public class Computer {
    public String getStr() {
        return "Hello";
    }
    public String getStr(String string, int i) {
        return "Hello " + string + i;
    }
}

User

@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運算子可以獲取表示式物件的型別, 可以呼叫表示式物件的靜態方法
Computer

@Data
@Component
public class Computer {

    public static String getStr() {
        return "Hello ";
    }

    public static String getStr(String string, double price) {
        return "Hello " + string + ",電腦價格為:" + price;
    }
}

User

@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包下的類,類名需要寫全限定名。
User

@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物件的集合變數賦值。這就是內聯。使用花括號語法。
Computer

@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
public class Computer {
    private String color;
    private Double price;
}

User

@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的語法,鍵和值之間用冒號隔開。
User

@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的語法給陣列賦值,可以給出初始值,多維陣列也受支援。
User

@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 或表達空的意思的時候使用指定的預設值。
Computer

@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
public class Computer {

    //string1賦值白色字串
    @Value("#{'白色'}")
    private String string1;

    //string2賦值空字串
    @Value("#{''}")
    private String string2;

    //string3不賦值,預設為null
    private String string3;
}

User

@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即每次迴圈的值。

Computer

@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
public class Computer {

    private String brand;

    @Value("#{{1,2,3,4,5}}")
    private List<Integer> computerIds;
}

User

@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等

下面演示Spring EL運算子
Computer

@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
public class Computer {
    //注入字串str1
    @Value("#{'str1'}")
    private String str1;
    //注入空字串
    @Value("#{''}")
    private String str2;
    //不賦值預設為 null
    private String str3;
}

User

@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的一個功能,語法是?.,當然有些語言也提供了這個功能。當我們對物件的某個屬性求值時,如果該物件本身為空,就會丟擲空指標異常,如果使用安全導航運算子,空物件的屬性就會簡單的返回空。
Computer

@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
public class Computer {
    //注入字串str1
    @Value("#{'str1'}")
    private String str1;
    //注入空字串
    @Value("#{''}")
    private String str2;
    //不賦值預設為 null
    private String str3;
}

User

@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]。

Computer

@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;
}

User

@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關鍵字,分別代表每次迭代出的鍵和值;

Computer

@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;
}

User

@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] 和 $ [expression] 篩選出來的結果一定最多隻有一個,因此可以給非集合型別注入,如:如果篩選出來的是字串則可以給String字串型別注入。但是 ? [expression]或 ! [expression] 返回的符合條件的結果哪怕實際真的只有一個也不能注入非集合型別。

集合投影

我們可以在Spring EL表示式中,將一個集合中所有元素的某屬性抽取出來,組成一個新集合。語法是![投影表示式]。(其實在上面我們已經使用過了)

Computer

@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
public class Computer {
    private int price;
    private String brand;
}

User

@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}’}");

相關文章