本文主要依据《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类型的集合中。