Spring——高级装配

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011024652/article/details/79794364

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

猜你喜欢

转载自blog.csdn.net/u011024652/article/details/79794364