Spring——高級裝配

本文主要依據《Spring實戰》第三章內容進行總結

1、環境與profile

在不同的環境中配置某個bean的方式可能會有所不同,比如數據源DataSource,在開發、測試和生產環境中我們配置數據源的方式可能都不同,在開發環境中我們傾向於使用嵌入式數據庫,而在生產環境中我們也許會使用JNDI管理DataSource。我們需要有一種方法來配置DataSource,使其在每種環境下都會選擇最爲合適的配置。

其中一種方式就是在單獨的配置類(或XML文件)中配置每個bean,然後在構建階段確定要將哪一個配置編譯到可部署的應用中。這種方式的問題在於要爲每種環境重新構建應用,重新構建過程中可能會引入一些預料之外的bug。

1.1、配置profile bean

Spring也提供了一種解決方案,可以根據環境決定該創建哪個bean和不創建哪個bean,不過Spring並不是在構建的時候做出這樣的決策,而是等到運行時再來確定,這樣的話,同一個部署單元(可能會是WAR文件)能夠適用於所有的環境。

要使用profile,首先要將所有不同的bean定義整理到一個或多個profile之中,在將應用部署到每個環境時,要確保對應的profile處於激活的狀態。

在Java配置中,可以使用@Profile註解指定某個bean屬於哪一個profile,我們來看一下下面這個例子,首先定義一個簡單的類DataSource:

public class DataSource {
}

這個類中沒有定義任何的屬性和方法,只是一個很簡單的Java類,接着我們定義一個配置類ProfileConfig:

@Configuration
@Profile("dev")
public class ProfileConfig {
    @Bean
    public DataSource dataSource() {
        return new DataSource();
    }
}

在這個配置類中,我們將DataSource註冊爲Spring應用上下文中的bean,但是我們在類級別上使用了@Profile註解,它會告訴Spring這個配置類中的bean只有在dev profile激活時纔會創建,如果dev profile沒有激活的話,那麼帶有@Bean註解的方法都會被忽略。

從Spring 3.2開始,我們可以在方法級別上使用@Profile註解,與@Bean註解一同使用,例如我們再定義一個簡單的Java類FileSystem:

public class FileSystem {
}

然後我們修改ProfileConfig:

@Configuration
public class ProfileConfig {
    @Bean
    @Profile("dev")
    public DataSource dataSource() {
        return new DataSource();
    }

    @Bean
    @Profile("prod")
    public FileSystem fileSystem() {
        return new FileSystem();
    }   
}

可以看到,我們在方法上使用@Profile註解,併爲dev profile裝配DataSource,爲prod profile裝配FileSystem,這樣的話只有當dev profile被激活的時候纔會創建DataSource bean,當prod profile被激活的時候纔會創建FileSystem bean。對於沒有指定profile的bean始終都會被創建,與激活哪個profile沒有關係。

1.2、自動化裝配配置profile

如果Spring通過自動化的方式裝配bean,我們也可以使用@Profile註解配置profile,例如:

@Component
@Profile("dev")
public class DataSource {   
}

我們修改了DataSource的定義,添加了@Component註解,將其聲明爲一個組建類,Spring將爲這個類創建bean,同時,我們使用@Profile註解,這樣的話只有當dev profile被激活的時候纔會創建DataSource bean。

1.3、在XML中配置profile

我們也可以通過<beans>元素的profile屬性,在XML中配置profile bean,例如:

<?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"  
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
    profile="dev">   

    <bean id="dataSource" class="profile.DataSource" />

</beans>  

我們將profile屬性設置爲dev,這樣的話只有當dev profile被激活的時候,該XML文件中配置的bean纔會被創建。因爲<beans>元素爲XML文件的根元素,在<beans>元素中使用profile屬性的話,整個XML中定義的bean都只有相應的profile處於激活狀態纔會被創建,這樣就要爲每個profile都創建一個XML文件,使用起來不太方便。

我們可以在根<beans>元素中嵌套定義<beans>元素,這樣就能夠將所有的profile bean定義放到同一個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"  
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">   
    <beans profile="dev">
        <bean id="dataSource" class="profile.DataSource" />
    </beans>

    <beans profile="prod">
        <bean id="fileSystem" class="profile.FileSystem" />
    </beans>      
</beans>

1.4、激活profile

Spring在確定哪個profile處於激活狀態時,需要依賴兩個獨立的屬性:spring.profiles.active和spring.profiles.default。如果設置了spring.profiles.active屬性的話,那麼它的值就會用來確定哪個profile是激活的。但是如果沒有設置spring.profiles.active屬性的話,那Spring將會查找spring.profiles.default的值。如果spring.profiles.active和spring.profiles.default均沒有設置的話,那就沒有激活的profile,因此只會創建那些沒有定義在profile中的bean。

有多種方式來設置這兩個屬性:

  • 作爲DispatcherServlet的初始化參數;

  • 作爲Web應用的上下文參數;

  • 作爲JNDI條目;

  • 作爲環境變量;

  • 作爲JVM的系統屬性;

  • 在集成測試類上,使用@ActiveProfiles註解設置。

在spring.profiles.active和spring.profiles.default中,profile使用的都是複數形式,這就意味着可以同時激活多個profile,這可以通過列出多個profile名稱,並以逗號分隔來實現。

Spring提供了@ActiveProfiles註解來指定運行測試時要激活哪個profile,例如:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:profile.xml")
@ActiveProfiles(profiles={"dev","prod"})
public class ProfileTest {
    @Autowired
    private DataSource d;

    @Autowired
    private FileSystem f;

    @Test
    public void testDataSource() {
        Assert.assertNotNull(d);
    }

    @Test
    public void testFileSystem() {
        Assert.assertNotNull(f);
    }   
}

2、條件化的bean

Spring 4引入了@Conditional註解,它可以用到帶有@Bean註解的方法上,如果給定的條件計算結果爲true,就會創建這個bean,否則的話,這個bean會被忽略。我們看一個例子,首先我們定義一個簡單的類MagicBean:

public class MagicBean {
}

然後使用Java Config的方式將MagicBean聲明爲Spring應用上下文中的bean:

@Configuration
@PropertySource("classpath:magic.properties")
public class ConditionalConfig {
    @Bean
    @Conditional(MagicExistsCondition.class)
    public MagicBean magicBean() {
        return new MagicBean();
    }
}

我們注意到這裏在類上使用了@PropertySource註解,這個註解用來聲明外部屬性源的,具體的使用方法在下文還會介紹到。另外,我們在magicBean()方法上使用了@Conditional註解,這個註解會通過Condition接口進行條件對比:

public interface Condition {
    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

設置給@Conditional的類可以是任意實現了Condition接口的類型,這個接口實現起來很簡單直接,只需提供matches()方法的實現即可,如果matches()方法返回true,那麼就會創建帶有@Conditional註解的bean,如果matches()方法返回false,將不會創建這些bean。在本例中,我們聲明瞭一個MagicExistsConditon:

public class MagicExistsCondition implements Condition {
    public boolean matches(ConditionContext context,
            AnnotatedTypeMetadata metadata) {
        Environment env = context.getEnvironment();

        return env.containsProperty("magic");
    }
}

這個Condition會根據環境中是否存在magic屬性來作出決策,如果存在magic屬性,那麼就會創建MagicBean實例,如果不存在magic屬性,就不會創建MagicBean實例。

3、處理自動裝配的歧義性

使用自動裝配可以讓Spring完全負責將bean引用注入到構造參數和屬性中。不過,僅有一個bean匹配所需的結果時,自動裝配纔是有效的,如果不僅有一個bean能夠匹配結果的話,這種歧義性會阻礙Spring自動裝配屬性、構造器參數或方法參數。

爲了闡述自動裝配的歧義性,假設我們使用@Autowired註解標註了setDessert()方法:

@Autowired
public void setDessert(Dessert d) {
    this.d = d;
}

在這裏,Dessert是一個接口:

public interface Dessert {
}

Dessert接口有三個實現類,分別爲Cake、Cookies和IceCream:

@Component
public class Cake implements Dessert {
}

@Component
public class Cookies implements Dessert {
}

@Component
public class IceCream implements Dessert{   
}

因爲這三個實現類均使用了@Component註解,在組件掃描的時候,Spring能夠發現它們並將其創建爲Spring應用上下文中的bean,然後,當Spring試圖自動裝配setDessert()中的Dessert參數時,它並沒有唯一、無歧義的可選值,因此Spring會拋出異常。

Spring提供了多種方案來處理自動裝配時出現的歧義性:我們可以將可選bean中某一個設爲首選的bean,或者使用限定符來幫助Spring將可選的bean的範圍縮小到只有一個bean。

3.1、標識首選的bean

在聲明bean的時候,通過將其中一個可選的bean設置爲首選bean能夠避免自動裝配時的歧義性。當遇到歧義性的時候,Spring會使用首選的bean,而不是其他可選的bean。

在使用組件掃描或者Java Config的方式聲明bean的時候,我們可以使用@Primary註解來表示首選bean,@Primary註解能夠與@Component組合用在組件掃描的bean上,也可以與@Bean組合用在Java配置的bean聲明中。例如:

@Component
@Primary
public class IceCream implements Dessert{
}
@Bean
@Primary
public Dessert iceCream() {
    return new IceCream();
}

以上兩個例子分別在組件掃描和Java配置中使用@Primary註解,將IceCream設置爲首選bean。

如果使用XML配置bean的話,<bean>元素有一個primary屬性用來指定首選的bean:

<bean id="iceCream" class="qualifier.IceCream" primary="true"/>

不管用什麼方式標示首選bean,效果都是一樣的,都是告訴Spring在遇到歧義性的時候要選擇首選的bean。但是,如果標示了兩個或多個首選bean,那麼又會帶來新的歧義性問題,Spring就無法正常工作了。

3.2、限定自動裝配的bean

設置首選bean的侷限性在於@Primary無法將可選方案的範圍限定到唯一一個無歧義性的選項中,它只能標示一個優先的可選方案,當首選bean的數量超過一個時,我們並沒有其他的方法進一步縮小可選範圍。

Spring限定符能夠在所有可選的bean上進行縮小範圍的操作,最終能夠達到只有一個bean滿足規定的限制條件。如果將所有的限定符都用上後依然存在歧義性,那麼可以繼續使用限定符來縮小選擇範圍。

@Qualifier註解是使用限定符的主要方式,它可以與@Autowired協同使用,在注入的時候指定想要注入進去的是哪個bean:

@Autowired
@Qualifier("iceCream")
public void setDessert(Dessert dessert) { 
}

在這個例子中,我們通過@Qualifier(“iceCream”)所引用的bean要具有String類型的”iceCream”作爲限定符。如果沒有指定其他的限定符的話,所有的bean都會給定一個默認的限定符,這個限定符與bean的ID相同,在這個例子中IceCream類沒有指定限定符,它的ID默認爲iceCream,所以它的默認限定符也是iceCream,因此@Qualifier註解引用的bean是IceCream類的實例。

基於默認的bean的ID作爲限定符是非常簡單的,但這有可能會引入一些問題,如果重構代碼改變了類的名稱,如IceCream類名改爲Gelato,bean的ID也發生了變化,那麼根據默認限定符去匹配時就會出錯。

3.2.1、創建自定義的限定符

我們可以爲bean設置自己的限定符,而不是依賴於將bean的ID作爲限定符,在這裏所需要做的就是在bean聲明上添加@Qualifier註解。例如,它可以與@Component組合使用:

@Component
@Qualifier("cold")
public class IceCream implements Dessert{   
}

在這裏,cold限定符分配給了IceCream bean,因爲它沒有耦合類名,因此可以隨意重構IceCream的類名,而不必擔心會破壞自動裝配。在注入的時候,只要引用cold限定符就可以了:

@Autowired
@Qualifier("cold")
public void setDessert(Dessert dessert) { 
}

在通過Java配置顯式定義bean的時候,@Qualifier也可以和@Bean註解一起使用:

@Bean
@Qualifier("cold")
public Dessert iceCream() {
    return new IceCream();
}

通過XML配置bean的話也可以配置限定符,只需要在<bean>元素內使用<qualifier>子元素即可:

<bean id="iceCream" class="qualifier.IceCream">
    <qualifier value="cold" />
</bean>

當使用自定義限定符的時候,儘量爲bean選擇特徵性或描述性的術語,例如此處的IceCream描述爲cold。

3.2.2、使用自定義的限定符註解

面向特性的限定符要比基於bean ID的限定符更好一些,但如果多個bean都具有相同的特性的話,這種做法也會出現問題,例如,如果引進一個新的Dessert bean:

@Component
@Qualifier("cold")
public class Popsicle implements Dessert {
}

可以看到,它和IceCream有相同的限定符”cold”,這樣在自動裝配Dessert bean的時候,就又會遇到歧義性的問題,這種情況下我們是否可以再次使用@Qualifier限定符來明確IceCream的定義?

@Component
@Qualifier("cold")
@Qualifier("creamy")
public class IceCream implements Dessert{   
}

在注入的時候使用這樣的限定符來進一步縮小範圍:

@Autowired
@Qualifier("cold")
@Qualifier("creamy")
public void setDessert(Dessert dessert) { 
}

在Java 8之前,同一個條目上是不允許重複出現相同類型的多個註解的,Java 8之後雖然允許出現重複的註解,但是註解本身定義的時候要帶有@Repeatable註解,這裏的@Qualifier註解在定義時沒有添加@Repeatable註解,所以使用多個@Qualifier註解的方式不能縮小可選bean的範圍。

對於使用XML配置的限定符,可以在<bean>元素中使用多個<qualifier>子元素,但是最後生效的只是最後一個<qualifier>子元素,例如:

<bean id="iceCream" class="qualifier.IceCream">
    <qualifier value="cold" />
    <qualifier value="creamy" />
</bean>

在這裏我們雖然使用了多個<qualifier>元素來描述限定符,但是實際生效並不是”cold”和”creamy”,而是隻有”creamy”限定符,也就是說如果同時配置了IceCream bean和Popsicle bean,使用@Qualifier(“cold”)限定符所引入的只是Popsicle bean,在這裏不會產生歧義性,但是如果有其他的bean也定義了”creamy”限定符,那麼仍然會出現歧義性。

使用@Qualifier註解和<qualifier>子元素沒有直接的辦法將自動裝配的可選bean縮小範圍至僅有一個可選的bean。

我們可以創建自定義的限定符註解,藉助這樣的註解來表達bean所希望限定的特性,所需要做的就是創建一個註解,它本身要使用@Qualifier註解來標註。在定義時添加@Qualifier註解,它們就具有了@Qualifier註解的特性,它們本身實際上就成了限定符註解。例如:

@Target({ElementType.CONSTRUCTOR, ElementType.FIELD
    ,ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Cold {
}

這就定義了一個@Cold註解,它本身就是一個限定符註解,同樣的我們也可以定義一個@Creamy限定符註解:

@Target({ElementType.CONSTRUCTOR, ElementType.FIELD
    ,ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Creamy {
}

現在,我們可以重新定義IceCream類,爲其添加@Cold和@Creamy註解,這樣它就同時有了”cold”和”creamy”兩種特性了:

@Component
@Cold
@Creamy
public class IceCream implements Dessert{
}

在注入點,我們也可以使用必要的限定符註解進行任意組合,從而將可選範圍縮小到只有一個bean滿足需求,例如,我們爲了得到IceCream bean,我們可以這樣改寫setDessert()方法:

@Autowired
@Cold
@Creamy
public void setDessert(Dessert dessert) { 
}

4、bean的作用域

在默認情況下,Spring應用上下文中所有的bean都是作爲以單例的形式創建的。Spring定義了多種作用域,可以基於這些作用域創建bean:

  • 單例(Singleton):在整個應用中,只創建bean的一個實例。
  • 原型(Prototype):每次注入或者通過Spring應用上下文獲取的時候,都會創建一個新的bean實例。
  • 會話(Session):在Web應用中,爲每個會話創建一個bean實例。
  • 請求(Request):在Web應用中,爲每個請求創建一個bean實例。

單例是默認的作用域,如果要選擇其他的作用域,要使用@Scope註解,它可以與@Component或@Bean一起使用,例如:

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class Notepad {
}

這就是用組件掃描的方式聲明瞭一個prototype作用域的Notepad bean,當然也可以使用Java配置的方式聲明同樣的bean:

@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Notepad notepad() {
    return new Notepad();
}

如果使用XML配置bean的話,可以使用<bean>元素的scope屬性來設置作用域:

<bean id="notepad" class="scope.Notepad" scope="prototype" />

5、運行時值注入

Spring提供了兩種在運行時求值的方式:

  • 屬性佔位符
  • Spring表達式語言(SpEL)

5.1、注入外部的值

在Spring中,處理外部值的最簡單方式就是聲明屬性源並通過Spring的Environment來檢索屬性,例如:

@Configuration
@PropertySource("classpath:config.properties")
public class EnvironmentConfig {

    @Autowired
    Environment env;

    @Bean
    public BlankDisc blankDisc() {
        return new BlankDisc(env.getProperty("title"), env.getProperty("artist"));
    }
}

這是一個基本的配置類,它使用外部屬性來裝配BlankDisc,使用@PropertySource引用config.properties文件,這個文件會加載到Spring的Environment中,稍後可以從這裏檢索屬性。

5.1.1、Spring的Environment

Environment的getProperty()方法不是獲取屬性值的唯一方法,它有四個重載的變種形式:

  • String getProperty(String key)
  • String getProperty(String key, String defaultValue)
  • T getProperty(String key, Class< T > type)
  • T getProperty(String key, Class< T > type, T defaultValue)

前兩種形式,方法都會返回String類型的值,第二個方法表示如果指定屬性不存在,會使用一個默認值。剩下兩種方法和前面兩種非常類似,但它們不會將所有的值都視爲String類型。

如果在使用getProperty()方法的時候沒有指定默認值,並且這個屬性沒有定義的話,獲取到的值是null,如果希望這個屬性必須要定義,那麼可以使用getRequiredProperty()方法,在這個方法中,如果屬性沒有定義的話,將會拋出IllegalStateException異常。

如果想檢查一下某個屬性是否存在的話,可以使用containsProperty()方法,如果想將屬性解析爲類的話,可以使用getPropertyAsClass()方法。

另外,Environment還提供了一些方法來檢查哪些profile處於激活狀態:

  • String[] getActiveProfiles():返回激活profile名稱的數組;
  • String[] getDefaultProfiles():返回默認profile名稱的數組;
  • boolean accpetsProfiles(String … profiles):如果Environment支持給定profile的話,就返回true。

5.1.2、解析屬性佔位符

Spring一直支持將屬性定義到外部的屬性文件中,並使用佔位符將值插入到Spring bean中,在Spring裝配中,佔位符的形式爲使用”${…}“包裝的屬性名稱。例如,我們可以在XML中按照如下的方式解析BlankDisc構造器參數:

<bean id="blankDisc" class="spel.BlankDisc" c:_0="${disc.title}" c:_1="${disc.artist}" />

可以看到title構造器參數所給定的值是從一個屬性中解析得到的,這個屬性的名稱爲disc.title,artist屬性的值也是如此。通過這種方式,XML配置沒有使用任何硬編碼的值,它的值是從配置文件以外的一個源中解析得到的。

如果我們依賴於組件掃描和自動裝配來創建和初始化應用組件的話,可以使用@Value註解來使用佔位符:

public BlankDisc(@Value("${disc.title}")String title, @Value("${disc.artist}")String artist) {
    this.title = title;
    this.artist = artist;
}

同樣的,對於Java Config配置的bean,也可以通過@Value註解來使用佔位符:

@Bean
public BlankDisc blanDisc(@Value("${disc.title}")String title, @Value("${disc.artist}")String artist) {
    return new BlankDisc(title, artist);
}

爲了使用佔位符,我們必須配置一個PropertySourcesPlaceholderConfigurer bean,因爲它能夠基於Spring Environment及其屬性源來解析佔位符。我們可以使用Java Config的方式和XML的方式來配置:

@Bean
public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
    return new PropertySourcesPlaceholderConfigurer();
}
<context:property-placeholder location="classpath:config.properties"/>

其中location屬性的的含義類似於@PropertySource註解,用於將屬性文件加載進來。

解析外部屬性的關注點在於根據名稱解析來自於Spring Environment和屬性源的屬性,外部值的來源還是比較單一的,SpEL是一種更爲通用的方法。

5.2、SpEL表達式

SpEL表達式擁有很多特性,包括:

  • 使用bean的ID來引用bean;
  • 調用方法和訪問對象的屬性;
  • 對值進行算數、關係和邏輯運算;
  • 正則表達式匹配;
  • 集合操作。

SpEL表達式要放在“#{…}”之中,這與屬性佔位符有些類似。那麼bean裝配的時候如何使用SpEL表達式呢?

如果通過組件掃描或Java配置創建bean的話,在注入屬性和構造器參數時,我們可以使用@Value註解,這與之前的屬性佔位符非常類似:

public BlankDisc(@Value("#{systemProperties['disc.title']}")String title,                                      @Value("#{systemProperties['disc.artist']}")String artist) {
    this.title = title;
    this.artist = artist;
}

在這個表達式裏,我們使用systemProperties對象引用系統屬性disc.title和disc.artist。

在XML配置中,我們可以將SpEL表達式傳入<property><constructor-arg>的value屬性中,或者將其作爲p-命名空間或c-命名空間條目的值:

<bean id="blankDisc" class="spel.BlankDisc" c:_0="#{systemProperties['disc.title']}" c:_1="#{systemProperties['disc.artist']}" />

5.2.1、表示字面值

SpEL表達式可以直接用來表示整數、浮點數、String值以及Boolean值,例如:#{1}、#{3.14159}、#{‘Hello’}、#{false}。字面值true和false的計算結果就是它們對應的Boolean類型的值。

5.2.2、引用bean、屬性和方法

SpEL可以通過ID引用其他的bean,例如我們使用#{sgtPeppers}將一個ID爲sgtPeppers的bean裝配到另外一個bean的屬性中。

如果我們想在一個表達式中引用sgtPeppers的artist屬性,我們可以使用#{sgtPeppers.artist},表達式主體的第一部分引用了一個ID爲sgtPeppers的bean,分隔符之後是對artist屬性的引用。

除了引用bean的屬性,我們還可以調用bean的方法,例如#{artistSelector.selectArtist()}表達式表示調用了artistSelector bean的selectArtist()方法。對於被調用方法的返回值來說,我們同樣可以調用它的方法,例如#{artistSelector.selectArtist().toUpperCase()},如果selectArtist()返回值是null的話,很可能出現空指針異常,我們可以使用類型安全運算符:{artistSelector.selectArtist()?.toUpperCase()},在這裏使用了?.運算符,這個運算符能夠在訪問它的右邊內容之前,確保它所對應的元素不是null,如果對應的元素爲null,那麼這個表達式最後的返回值是null。

5.2.3、在表達式中使用類型

在SpEL中訪問類作用域的方法或者常量的話,要依賴T()這個關鍵的運算符。T()運算符的結果會是一個Class對象,T()運算符的真正價值在於它能夠訪問目標類型的靜態方法和常量。例如:#{T(java.lang.Math).PI}、#{T(java.lang.Math).random()}。

5.2.4、SpEL運算符

SpEL提供了一下幾種類型的運算符:

運算符類型 運算符
算術運算 +、-、*、/、%、^
比較運算 >、<、==、<=、>=、lt、gt、eq、le、ge
邏輯運算 and、or、not、|
條件運算 ?:(ternary)、?:(Elvis)
正則表達式 matches

算術運算符與平時在Java中使用的算術運算符類似,其中如果使用String類型的值時,”+”運算符執行的是連接操作,與在Java中是一樣的。

SpEL提供的比較運算符有兩種形式:符號形式和文本形式,在大多數情況下,符號運算符與對應的文本運算符作用是相同的,使用哪一種形式均可以,例如要比較兩個數字是否相等,可以使用#{counter.total==100},也可以使用#{counter.total eq 100}。

SpEL還提供三元運算符,它與Java中的三元運算符非常類似,例如#{scoreboard.score > 1000 ? “Winner” : “Loser”},它會判斷scoreboard.score是否大於1000,如果大於1000,則計算結果爲Winner,否則爲Loser。三元運算符的一個常見場景就是檢查null值,並用一個默認值來替代null。例如#{disc.title ?: ‘Rattle and Hum’},它會判斷disc.title是不是null,如果是null,那麼表達式的計算結果就是Rattle and Hum。

SpEL通過matches運算符支持表達式中的模式匹配,matches的運算結果會返回一個Boolean類型的值,如果與正則表達式相匹配,則返回true,否則返回false。

5.2.5、計算集合

SpEL中可以通過”[]”運算符來從集合或者數組中按照索引獲取元素,例如:#{jukebox.songs[4].title}這個表達式會計算songs集合中第五個(索引從0開始)元素的title屬性,#{testMap[‘key’].value}這個表達式會獲取一個Map中鍵爲key的值的value屬性。它還可以從String中獲取一個字符,例如#{‘This is test’[3]},這個表達式會獲取String的第四個字符。

SpEL還提供了查詢運算符(.?[]),它會用來對集合進行過濾,得到集合的一個子集,例如#{jukebox.songs.?[artist eq ‘Aerosmith’]}這個表達式會得到jukebox中artist屬性爲Aerosmith的所有歌曲。這個表達式會對集合中的每個條目進行計算,如果表達式計算結果爲true,那麼條目會放到新的集合中,否則的話,它就不會放到新的集合中。

SpEL還提供了另外兩個查詢運算符:”.^[]”和”.$[]”,它們分別用來在集合中查詢第一個匹配項和最後一個匹配項。SpEL還提供了投影運算符(.![]),它會從集合的每個成員中選擇特定的屬性放到另外一個集合中,例如#{jukebox.songs.![title]}這個表達式會將title屬性投影到一個新的String類型的集合中。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章