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型別的集合中。