Spring系列(二)Spring bean的高级装配详解

1 环境与profile

在开发软件的时候,有一个很大的挑战就是将应用程序从一个环境迁移到另一个环境。在开发阶段中,某些相关做法可能并不适合迁移到生产环境中,甚至迁移过去也无法正常工作。数据库配置就是比较常见的例子。

Spring为环境相关的bean所提供的解决方案其实与构建时的方案没什么差别,不过Spring并不是在构建时确定该创建哪个bean,而是在运行时决定。这样的结果就是同一个部署单元(可能会是war文件)能够适用于所有的环境,没有必要进行重新构建。

1.1 配置profile bean

在3.1版本,Spring引入了bean profile的功能。

要使用profile,首先需要将所有不同的bean定义整理到一个或多个profile之中,在应用部署到每个环境时,要确保对应的profile处于激活(active) 状态。

在Java配置中,可以使用@Profile注解指定某个bean属于哪一个profile。例如:

@Configuration
@Profile("dev")
public class TestConfig {

  @Bean
  public String test() {
    return null;
    }

}

@Profile应用在类级别上,它会告诉Spring这个配置类中的bean只有在dev profile激活时才能创建。如果dev profile没有激活,那么带有@bean注解的方法都会被忽略掉。

在Spring3.1只能在类级别上使用@Profile注解,不过从Spring3.2开始,也可以在方法上使用@Profile注解,与@Bean注解一起使用。如下所示:

@Configuration
public class TestConfig {

  @Bean
  @Profile("dev")
  public String test1() {
    return null;
    }

  @Bean
  @Profile("prod")
  public String test2() {
    return null;
    }
}

没有指定profile的bean始终都会创建,与激活哪个profile没有关系。

在XML中配置profile

可以通过元素的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"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans.xsd" 
    profile="dev">

</beans>

也可以使用元素中嵌套定义元素,同时创建多个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"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans.xsd" >

  <beans profile="dev">

  </beans>

  <beans profile="prod">

  </beans>

</beans>
1.2 激活profile bean

Spring在确定哪个profile处于激活状态时,需要依赖两个独立的属性:spring.profiles.active和spring.profiles.default

如果设置了spring.profiles.active属性的话,那么它的值就会用来确定哪个profile是激活的。但如果没有设置spring.profiles.active属性的话,那Spring将会查找spring.profiles.default的值。如果两者都没有设置,那就没有激活的profile,因此只会创建那些没有定义在profile中的bean。

有多种方式来设置这两个属性:

  • 作为DispatcherServlet的初始化参数
  • 作为Web应用的上下文参数
  • 作为JNDI条目
  • 作为环境变量
  • 作为JVM的系统属性
  • 在集成测试类上,使用@ActiveProflies注解设置

可以选择spring.profiles.active和spring.profiles.default的最佳组合方式以满足需求。

比如在Web应用的web.xml文件中设置默认的profile:

<context-param>
    <param-name>spring.profile.default</param-name>
    <param-value>dev</param-value>
<context-param>

当应用部署到其他环境的时候,可以设置spring.profiles.active即可,系统会优先执行spring.profiles.active。

使用profile进行测试:
Spring提供了@ActiveProfiles注解,可以用它来指定运行测试时要激活哪个profile。如下所示:

  @RunWith(SpringJUnit4ClassRunner.class)
  @ContextConfiguration(classes=DataSourceConfig.class)
  @ActiveProfiles("dev")
  public static class DevDataSourceTest {

  }
2 条件化的bean

Spring 4.0 引用了一个新的 @Conditional 注解,它可以用到带有@Bean注解的方法上。如果给定的条件计算结果为true,就会创建这个bean,否则,这个bean会被忽略。

例如,我们有一个MagicBean的类,我们希望只有设置了magic环境属性的时候,Spring才实例化这个类。如果环境中没有这个属性,那么MagicBean将会被忽略,如下使用了 @Conditional 注解条件化了MagicBean:

@Configuration
public class MagicConfig {

    @Bean
    @Conditional(MagicExistConditional.class)
    public MagicBean getBean(){
        return new MagicBean();
    }

}

可以看到,@Conditional中给定了一个Class,它指明了条件,即MagicExistConditional。@Conditional将会通过condition接口进行条件对比:

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

设置给@Conditional的类可以是任意实现了condition接口的类。这个实现很简单,只需提供matches()方法的实现即可。如果matches()方法返回true,那么就会创建带有@Conditional注解的bean,否则将不会创建这些bean。

在本例中,我们需要创建Condition的实现并根据环境中是否存在magic属性来做出决策:

public class MagicExistConditional implements Condition{

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        org.springframework.core.env.Environment env = context.getEnvironment();
        return env.containsProperty("magic");
    }
}

该实现通过给定的ConditionalContext对象得到Environment对象,并使用这个对象检查环境中是否存在名为magic的环境属性。

通过ConditionalContext,我们可以做到如下几点:

  • 借助getRegistry()返回的BeanDefinitionRegistry检查bean的定义;
  • 借助getBeanFactory()返回的ConfigurableListableBeanFactory检查bean是否存在,甚至探查bean的属性;
  • 借助getEnvironment()返回的Environment检查环境变量是否存在以及它的值是什么;
  • 读取并探查getResourceLoader()返回的ResourceLoader所加载的资源;
  • 借助getClassLoader()返回的ClassLoader加载并检查类是否存在。

AnnotatedTypeMetadata则能够让我们检查带有@Bean注解的方法上还有什么其他的注解。

3 处理自动装配的歧义性

自动装配能够提供很大的帮助,因为它会减少装配应用程序组件时所需要的显示配置的数量。

不过仅有一个bean匹配所需的结果时,自动装配才是有效的。如果不仅有一个bean能够匹配结果的话,这种歧义性会阻碍Spring自动专供诶属性、构造器参数或方法参数。

假设有一个甜点接口Dessert,分别有三个类Cake、Cookies、IceCream实现它,并且使用了@Component注解。如下:

public interface Dessert {

}
@Component
public class Cake implements Dessert {

}

@Component
public class Cookie implements Dessert {
}

@Component
public class IceCream implements Dessert {
}

此时,另一个类使用@Autowired注解标注了setDessert()方法:

public class People {
    private Dessert dessert;

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

因为这三个实现均使用了@Component注解,在组件扫描的时候,能够发现他们并将其创建为Spring应用上下文中的bean。然后当Spring试图自动装配setDessert()中的Dessert参数时,它并没有唯一、无歧义的可选值,因此Spring会抛出异常,即NoUniqueBeanDefinitionException。

Spring提供了多种解决方案来解决这样的问题。你可以将可选的bean中的某一个设置为首选(primary)的bean,或者使用限定符(qualifier)来帮助Spring将可选的范围缩小到只有一个bean。

3.1 标示首选的bean

在声明bean的时候,通过将其中一个可选的bean设置为首选(primary)bean能够避免自动装配时的歧义性。当遇到歧义性时,Spring将会使用首选的bean,而不是其他可选的bean。

@Primary能够与@Component组合用咋组件扫描的bean上,也可以与@Bean组合用在Java配置的bean声明中,如下所示将IceCream声明为首选的bean:

@Component
@Primary
public class IceCream implements Dessert {
}

或者通过Java配置显示声明:

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

或者通过XML声明:

<bean id="iceCream" class="com.zjx.dessert.IceCream" primary="true"/>

但是如果有两个首选的Dessert bean,就会带来新的歧义性问题,这时候就需要使用更强大的机制:限定自动装配的bean

3.2 限定自动装配的bean

Spring提供 @Qualifier注解来限定自动装配的bean。它可以与@Autowired和@Inject协同使用,在注入的时候指定想要注入进去的是哪个bean。比如我们想将IceCream注入到setDessert()中:

    @Autowired
    @Qualifier("iceCream")
    public void setDessert(Dessert dessert){
        this.dessert = dessert;
    }

为@Qualifier注解设置的参数就是要注入的bean的ID。所使用的@Component注解声明的类都会创建为bean,并且bean的id为首字母变为小写的类名。因此@Qualifier(“iceCream”)指向的是组件扫描时所创建的bean,并且这个bean是IceCream类的实例。

创建自定义的限定符
我们可以为bean设置自己的限定符,而不是依赖于将bean ID作为限定符。在这里只需要再bean声明上添加@Qualifier注解。比如:

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

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

在这种情况下,cold限定符分配给了IceCreambean。因为它没有耦合类名,因此可以随意重构IceCream类名,而不必担心破坏自动装配。在注入的地方,只需要引入ice限定符就可以了:


@Autowired
@Qualifier("ice")
public void setDessert(Dessert dessert){
    this.dessert = dessert;
}

当通过Java配置显示定义bean的时候,@Qualifier也可以和@Bean注解一起使用:

@Bean
@Qualifier("ice")
public void setDessert(Dessert dessert){
    return new IceCream();
}

使用自定义的限定符注解
面向特性的限定符要比基于bean ID的限定符更好一些。但是如果多个bean都具有相同特性的话,这种做法也会出现问题,比如两个都带有“ice”的限定符,此时就需要使用自定义的限定符注解来解决。

需要创建一个新的注解,它本身使用@Qualifier注解来标注。这样我们将不再使用@Qualifier(“ice”),而是使用自定义的@Cold注解,该注解定义如下所示:

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

同时你也可以创建其他注解,比如@Creamy注解来代替@Qualifier(“creamy”):

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

现在我们可以同时在IceCream上使用这两个注解:

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

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

最终,在注入点,我们使用必要的限定符注解进行任意组合,从而将可选范围缩小到只有一个bean满足需求。为了得到IceCream bean,setDessert()方法可以这样使用注解:

@Bean
@Creamy
@Cold
public void setDessert(Dessert dessert){
    return new IceCream();
}

通过声明自定义的限定符注解,我们可以同时使用多个限定符,不会再有Java编译器的限制或错误。于此同时,相对使用原始的@Qualifier并借助String类型来指定限定符,自定义的注解也更为类型安全。

setDessert()方法以及它的注解并没有在任何地铁明确指定要将IceCream自动装配到该方法中。相反,我们使用所需的bean的特性来进行指定,即@Cold和@Creamy。因此,setDesert()方法能够与特定的Dessert实现保持解耦。

4 bean的作用域

在默认情况下,Spring应用上下文中所有bean都是以单例(singleton)的形式创建的。也就是说,不管给定的一个bean被注入到其他bean多少次,每次注入的都是同一个实例。
大多数情况使用单例是很合适的,但有时候可能会发现,你所使用的类是易变的(mutable),它们会保持一些状态,因此重用是不安全的。

Spring定义了多种作用域,可以基于这些作用域创建bean,包括:

  • 单例(Singleton):在整个应用中,只创建bean的一个实例。
  • 原型(Prototype):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例。
  • 会话(session):在Web应用中,为每个会话创建一个bean实例。
  • 请求(Request):在Web应用中,为每个请求创建一个bean实例。

如果要使用其他的作用域,要使用 @Scope注解,它可以与@Component和@Bean一起使用。

例如,如果你使用组件扫描来发现和声明bean,那么可以在bean的类上使用@Scope注解,将其声明为原型bean:

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

这里使用了ConfigurableBeanFactory类的SCOPE_PROTOTYPE常量设置了原型作用域。当然也可以使用@Scope(“prototype”),但是使用SCOPE_PROTOTYPE常量更加安全并且不容易出错。

@Scope注解也可以在Java配置中和@Bean注解一起使用,也可以在xml中进行配置,可以使用元素的scope属性来设置作用域。

4.1 使用会话和请求作用域

在Web应用中,如果能够实例化在会话和请求范围内共享的bean,那将是非常有价值的事情。比如,在典型的电子商务中,可能会有一个bean代表用户的购物车。如果购物车是单例的话,会导致所有用户都像一个购物车中添加商品。另一方面,如果购物车是原型的,那么在应用中某一个地方往购物车添加商品,在应用的另一个地方可能就不可用了,因为在这里注入的是另外一个原型作用域的购物车。

就购物车bean来说,会话作用域是最合适的,指定会话作用域可以使用 @Scope注解

@Component
@Scope(value = WebApplicationContext.SCOPE_SESSION,proxyMode = ScopedProxyMode.INTERFACES)
public ShoppingCart cart {

}

这里,将value设置成了WebApplicaitonContext中的SCOPE_SESSION常量(它的值是session)。这会告诉Spring为Web应用中的每个会话创建一个shop。

@Scope中的proxyMode属性它被设置成了ScopedProxyMode.INTERFACES。这个属性解决了将会话或请求作用域注入到单例bean中所遇到的问题。

如果ShoppingCart bean是类而不是接口的话,我们必须将ProxyMode设置为ScopedProxyMode.TARGET_CLASS,以此表明要以生成目标类扩展的方式创建代理。

请求作用域的bean会面临相同的装配问题。因此,请求作用域的bean应该也以作用域代理的方式进行注入。

4.2 在XML中声明作用域代理

元素的scope属性可以设置作用域,但是指定代理模式需要使用Spring aop命名空间的一个新元素:

<bean id="cart"
    class="com.myapp.ShoppingCart"
    scope='session'>
    <aop:scoped-proxy />
</bean>

是与@Scope注解的proxyMode属性功能相同的Spring XML配置元素。它会告诉Spring为bean创建一个作用域代理。默认情况下,它会使用CGLib创建目标类的代理。但是,我们也可以将proxy-target-class属性设置为false,进而要求它生产基于接口的代理:

<bean id="cart"
    class="com.myapp.ShoppingCart"
    scope='session'>
    <aop:scoped-proxy proxy-target-class='false' />
</bean>
5 运行时值注入

bean装配的另外一个方面指的是将一个值注入到bean的属性或者构造器参数中。

可以通过硬编码实现,但我们想让这些值在运行时确定,为了实现这些功能,Spring提供了两种在运行时求值的方式:
- 属性占位符(Property placeholder)
- Spring表达式语言(SpEL)

5.1 注入外部的值

Spring处理外部值的最简单的方式就是声明属性源并通过Spring的Environment来检索属性

例如,使用@PropertySource注解和Environment来给BlankDisc bean注入值:
- 首先有一个名为disc.properties的配置文件:

disc.title=发如雪
disc.artist=周杰伦
  • 使用@PropertySource注解和Environment:
@Configuration
@PropertySource("classpath:disc.properties") //声明属性源
public class EnvironmentConfig {

    @Autowired
    Environment env;

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

BlankDisc类如下:

public class BlankDisc {

  private final String title;
  private final String artist;

  public BlankDisc(String title, String artist) {
    this.title = title;
    this.artist = artist;
  }

  public String getTitle() {
    return title;
  }

  public String getArtist() {
    return artist;
  }

}

通过 @PropertySource 引用了名为disc.properties的属性文件,该文件会加载到Spring的Environment中,稍后可以从这里检索属性。同时,disc()方法中会创建一个新的BlankDisc,它的构造器参数通过调用 getProperty() 方法从属性文件中获取。

深入学习Spring的Environment
getProperty()方法一共有四种重载的变种形式:

    String getProperty(String var1);

    String getProperty(String var1, String var2);

    <T> T getProperty(String var1, Class<T> var2);

    <T> T getProperty(String var1, Class<T> var2, T var3);

前两种形式的getProperty()方法都会返回String类型的值,其中有两个参数的方法,第二个值是默认值,也就是在指定属性不存在的时候使用该默认值。

另外两种返回值由第二个值来确定,第三个值同样是默认值。

如果你希望获取的属性必须要定义,可以使用getRequiredProperty()方法,如果没定义将抛出IllegalStateException异常。

如果想检查方法是否存在,可以调用Environment的 containsProperty() 方法:

boolean exist = env.containsProperty("disc.title");

如果想将属性解析为类的话,可以使用 getPropertyAsClass() 方法:

Class<CompactDisc> cdClass = env.getPropertyAsClass("disc.class",CompactDisc.class);

除了属性相关的功能以外,Environment还提供了一些方法来检查哪些ProiFile处于激活状态:
- String[] getActiveProfiles():返回激活profile名称的数组;
- String[] getDefaultProfiles():返回默认profile名称的数组;
- boolean acceptsProfiles(String… profiles): 如果environment支持给定的profile的话,就会返回true。

Spring还提供了通过占位符装配属性的方法,这些占位符的值将会来源于一个属性源。

解析属性占位符
在Spring装配中,占位符的形式为使用 “${…}” 包装的属性名称。

比如我们可以在XML文件中如下的形式解析BlankDisc构造器参数:

<bean id="blankdisc" class="com.zjx.externals.BlankDisc"
c:_titile="${disc.title}"
c_artist="${disc.artist}"/>

可以看到,title构造器参数所给定的值是从一个属性中解析到的,这个属性的名称为disc.title。artist参数装配的是名为disc.artist的属性值。按照这种方式,XML配置没有任何硬编码的值,它的值是从配置文件以外的一个源中解析得到的。

如果我们依赖组件扫描和自动装配来创建和初始化应用组件的话,那么就没有指定占位符的配置文件或类了,在这种情况下,我们可以使用 @Value 注解,它的使用方式与@Autowired注解非常相似。比如,在BlankDisc类中,构造器可以如下所示:

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

为了使用占位符,我们必须要配置一个PropertyPlaceholderConfigurer bean或ProertySourcesPlaceholderConfigurer bean。从Spring 3.1开始,推荐使用前者,因为它能够基于Spring Environment及其属性源来解析占位符。

如下的@Bean方法在Java中配置了PropertySourcesPlaceholdersConfigurer:

    @Bean
    public static PropertySourcesPlaceholderConfigurer placeholderConfigurer(){
        return new PropertySourcesPlaceholderConfigurer();
    }

如果你想使用XML配置的话,Spring context命名空间中的 元素将会为你生成PropertySourcesPlaceholderConfigurer 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:c="http://www.springframework.org/schema/c"
  xmlns:context="http://www.springframework.org/schema/context"
  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-4.0.xsd">

  <context:property-placeholder
    location="com/soundsystem/app.properties" />

</beans>
5.2 使用Spring表达式语言进行装配

Spring 3引入了Spring表达式语言(Spring Expression Language,SpEL),它能够以一种强大和简洁的方式将值装配到bean属性和构造器参数中,在这个过程中所使用的表达式会在运行时计算得到的值。

SpEL拥有很多特性,包括:

  • 使用bean的ID来引用bean;
  • 调用方法和访问对象的属性;
  • 对值进行算术、关系和逻辑运算;
  • 正则表达式匹配;
  • 集合操作;

SpEL样例
SpEL需要放到“#{…}”之中,这与属性占位符类似,属性占位符需要放到“${…}”之中。

一个简单的SpEL:#{1}

#{T(System).currentTimeMillis()}

最终结果是计算表达式的那一刻当前时间的毫秒数 。T()表达式 会将Java.long.System视为Java中对应的类型。因此可以调用其static修饰的currentTimeMillis()方法。

可以通过SpEL表达式引用其他的bean或其他bean的属性,如下将会得到一个ID为dreamDisc的bean的title属性:

#{dreamDisc.title}

通过systemProperties对象引用系统属性:

#{systemProperties('disc.title')}

在bean装配时使用SpEL表达式:
- 通过组件扫描创建bean,在注入属性和构造器参数时,可以使用 @Value 注解。比如:

    @Bean
    public BlankDisc blankDisc(@Value("#{T(System).currentTimeMillis()}") String title, @Value("#{1}") String artist){
        return new BlankDisc(title,artist);
    }
  • 在XML配置中,可以将SpEL表达式传入或的value属性中,或者将其作为p-命名空间或c-命名空间的值。比如:
    <bean id="blandisc" class="com.zjx.externals.BlankDisc">
        <constructor-arg value="#{T(System).currentTimeMillis()}"></constructor-arg>
        <constructor-arg value="#{123}"></constructor-arg>
    </bean>
  • 表示字面值

SpEL可以用来表示浮点数、String值以及Boolean值。比如:#{3.1234}

数值还可以用科学计数法的方式进行表示,也可以用来计算String类型的字面值,如 #{‘hello’}

  • 引用bean、属性、方法

    1. SpEL还可以通过ID引用其他的bean,比如上面的例子:
#{dreamDisc}
  1. 还可以引用它的属性:
#{dreamDisc.artist}
  1. 还可以调用bean上的方法
#{dreamDisc.getTitle()}
  1. 调用方法返回值的方法
#{dreamDisc.getTitle().toUpperCase()}

toUpperCase()方法将大写字母转为小写字母

  1. 为了避免Null值可以使用类型安全的运算符:
#{dreamDisc.getTitle()?.toUpperCase()}

“?.”运算符 能够在访问它右边的内容之前,确保它所对应的元素不是null,如果为null则不访问右边的内容。

  • 在表达式中使用类型

使用 T() 这个关键的字符可以通过SpEL访问类作用域的方法和常量。

比如访问Java中的Math类:

T(java.lang.Math)

这里所示的T()运算符的结果会是一个Class对象,代表了Java.lang.Math。

可以访问类的静态方法和常量,比如得到0到1的随机数:

T(java.lang.Math).random()
  • SpEL运算符

SpEL提供多个运算符,这些运算符可以用在SpEL表达式的值上。

运算符类型运算符
算术运算+、-、*、/、%、^
比较运算<、>、==、<=、>=、lt、gt、eq、le、ge
逻辑运算and、or、not、”|”
条件运算?:(ternary)、?:(Elvis)
正则表达式matches
#{2 * T(java.lang.Math).PI * circle.radius}

在这里PI值乘以2,然后在乘以radius属性的值,这个属性来源于ID为circle的bean,它实际上计算了circle bean中所定义的圆的周长。

SpEL还提供了三元运算符(ternary),如下的表达式它会判断如果scoreboard.score>1000的话,计算结果为String类型的“Winner!”,否则为Loser:

#{scoreboard.score > 1000 ? 'Winner' : 'Loser'}

三元运算符常见的场景就是检查null值,并用一个默认值替代null。比如:

#{disc.title ?: 'Rattle and Hum'}

这种表达式通常称为Elvis运算符

  • 计算正则表达式

SpEL通过matches运算符支持表达式中的模式匹配。matches运算符对String类型的文本(作为左边参数)应用正则表达式(作为右边参数)。matches的运算结果会返回一个Boolean类型的值,如果与正则表达式相匹配,则返回true,否则返回false。

比如检验邮箱地址:

#{admin.email matches '[a-zA-Z0-9._+-%]+@[a-zA-Z0-9.-]+\\.com'}
  • 计算集合

最简单的集合,引用列表中的一个元素:

#{jukebox.songs[4].title}

“[]”运算符用来从集合或者数组中按照索引获取元素,它实际上还可以从String中取一个字符,比如:

#{'this is test'[3]}

结果是‘s’。

SpEL还提供了查询运算符(.?[]),它会用来对集合进行过滤,得到集合的一个子集:

#{jukebox.songs.?[artist eq 'zhangsan']}

这里会获取到所有artist为’zhangsan‘的歌曲。

SpEL还提供另外两个查询运算符:“.^[]”和“.$[]”,分别用来在集合中查询第一个匹配项和最后一个匹配项。

最后,SpEL还提供了投影运算符(.![]),它会从集合的每个成员中选择特定的属性放到另外一个集合中。比如:

#{jukebox.songs.![title]}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值