环境与profile

配置profile bean

在3.1版本中,Spring引入了bean profile的功能。使用profile,首先将所有不同的bean定义整理到一个或多个profile之中,再将应用部署到每个环境时,并确保对应的profile处于激活(active)的状态

在Java配置中,可以使用@Profile注解指定某个bean属于哪一个profile。例如,在配置类中,嵌入式数据库的DataSource可能会配置成如下所示:

从Spring 3.2开始,可在方法级别上使用@Profile注解,与@Bean注解一同使用,如下所示:

//@Profile注解基于激活的profile实现bean的装配

package com.myapp;
import javax.activation.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.jndi.JndiObjectFactoryBean;  

@Configuration
public class DataSourceConfig
{
    @Bean(destroyMethod = "shutdown")
    @Profile("dev")                                //为dev profile装配的bean
    public DataSource embeddedDataSourece()
    {
        return new EmbeddedDataBuilder()
            .setType(EmbeddedDataSourceType.H2)
            .setScript("classpath:schema.sql")
            .setScript("classpath:test-data.sql")
            .build();
    }
    
    @Bean
    @Profile("prod")                                //为profile profile装配的bean
    public DataSource embeddedDataSourece()
    {
        JndiObjectFactoryBean jndiObjectFactoryBean = nre JndiObjectFactoryBean();
        jndiObjectFactoryBean.setJndiName("jdbc/myDS"); 
        jndiObjectFactoryBean.setResourceRef(true); 
        jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
        return (DataSource)jndiObjectFactoryBean.getObject(); 
    }
}  

注意:尽管每个DataSource bean都被声明在一个profile中,并且只有当规定的profile激活时,相应的bean才会被创建,但可能会有其他的bean并没有声明在一个给定的profile范围内。没有指定profile的bean始终都会被创建,与激活哪个profile没有关系

在XML中配置

可以在根<beans>元素中嵌套定义<beans>元素,而不为每个环境都创建一个profile XML文件。这能够将所有的profile bean定义放到同一个XML文件中,如下所示:

// 重复使用<beans>元素来指定多个profile
<?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:jdbc="http://www.springframework.org/schema/jdbc"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="
        http://www.springframework.org/schema/jee
        http://www.springframework.org/schema/jee/spring-beans.xsd
        http://www.springframework.org/schema/jdbc
        http://www.springframework.org/schema/jdbc/spring-beans.xsd
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd"
    
    <beans profile = "dev">                                //dev profile的bean 基于开发阶段
    <jdbc:embedded-database id="dataSource">
        <jdbc:script location = "classpath:schema.sql" />
        <jdbc:script location = "classpath:test-data.sql" />
    </jdbc:embedded-database>
    </beans>
                
    <beans profile = "qa">                                //qa profile的bean 基于连接池
        <bean id = "dataSource"
            class = "org.apache.commons.dbcp.BasicDataSource"
            destroy-method = "close"
            p:url = "jdbc:h2:tcp//dbserver/-/test"
            p:driverClassName = "org.h2.Driver
            p:username = "sa"
            p:password = "password"
            p:initialSize = "20"
            p:maxActive = "30" />
    </beans>

    <beans profile = "prod">                                //prod profile的bean 基于生产环境
    <jee:jndi-lookup id="dataSource"
        jndi-name = "jdbc/myDatabase"
        resource-ref = "true"
        proxy-interface = "javax.sql.DataSource" />
    </beans>        

</beans>      

所有bean都定义到同一个XML文件之中,这种配置方式与定义在单独的XML文件中的实际效果是一样的。这里有三个bean,类型都是javax.sql.DataSource,并且ID都是dataSource。但是在运行时,只会创建一个bean,这取决于处于激活状态的是哪个profile

激活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注解设置

// 在Web应用的web.xml文件中设置默认的profile
<?xml version="1.0" encoding="UTF-8"?>
<web-app version = "2.5"
    xmlns = "http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation = "http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
    
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value> /WEB-INF/spring/root-context.xml </param-value>
    </context-param>   

    <context-param>                                        // 为上下文设置默认的proflie
        <param-name>spring.profiles.default</param-name>
        <param-value> dev </param-value>
    </context-param>   
    
    <listener>
        <listener-class>
            org.springframework.web.context.ContextLoaderListener
        </listener-class>
    </listener>
    
    <servlet>
        <servlet-name>appServlet</servlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
        <init-param>
            <param-name>
                spring.profiles.default
            </param-name>
            <param-value> dev </param-value>
        </init-param>
        <load-on-startup> 1 </load-on-startup>
    </servlet>  
    
    <servlet-mapping>
        <servlet-name>appServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>        

使用profile进行测试

Spring提供了@ActiveProfiles注解,我们可以使用它来指定运行测试时要激活哪个profile。在集成测试时,通常想要激活的是开发环境的profile。如下的测试类片段展现了使用@ActiveProfiles激活dev profile:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {PersistenceTestConfig.class})
@ActionProfiles("dev")
public class PersistenceTest
{
    ...
}

条件化的bean

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

设有名为MagicBean的类,希望只设置magic环境属性时,Spring才会实例化这个类。如果环境中没有这个属性,那么MagicBean将会被忽略。在下述程序所展现的配置中,使用@Conditional注解条件化地配置了MagicBean:

// 条件化地配置bean
@Bean
@Conditional(MagicExistsCondition.class)                //条件化地创建bean
public MagicBean magicBean()
{
    return new MagicBean();
}

@Conditional给定一个Class,指明了条件——MagicExistsCondition。@Conditional将会通过Condition接口进行条件对比:

public interface Condition
{
    boolean matches(ConditionContext ctxt, AnnotationTypeMetadata metadata)
}

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

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

// 在Condition中检查是否存在magic属性
package com.habuma.restfun;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.ClassUtils;

public class MagicExistsCondition implements Condition
{
    public boolean matches(ConditionContext ctxt, AnnotationTypeMetadata metadata)
    {
        Environment env = context.getEnvironment();
        return env.containsProperty("magic");            // 检查magic属性
    }
}

ConditionContext的作用:

  • 借助getRegistry()返回的BeanDefinitionRegistry检查bean定义

  • 借助getBeanFactory()返回的ConfigurableListableBeanFactory检查bean是否存在,甚至探查bean的属性

  • 借助getEnvironment()返回的Environment检查环境变量是否存在以及它的值是什么

  • 读取并探查getResourceLoader()返回的ResourceLoader所加载的资源

  • 借助getClassLoader()返回的ClassLoader加载并检查类是否存在

AnnotatedTypeMetadata能够检查带有@Bean注解的方法上是否存在其他的注解及其属性

处理自动装配的歧义性

标示首选的bean

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

将@Component注解的IceCream bean声明为首选的bean:

@Component
@Primary
public class IceCream implements Dessert
{
    ...
}

通过Java配置显式地声明IceCream,那么@Bean方法如下所示:

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

通过XML配置bean,<bean>元素的primary属性指定首选bean:

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

限定自动装配的bean

Spring的限定符能够在所有可选的bean上进行缩小范围的操作,最终能够达到只有一个bean满足所规定的限制条件

@Qualifier注解是使用限定符的主要方式。它可以与@Autowired和@Inject协同使用,在注入的时候指定想要注入进去的是哪个bean。例如,我们想要确保要将IceCream注入到setDessert()之中:

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

创建自定义的限定符

bean可以设置自己的限定符,而不依赖bean ID作为限定符。在这里所需要做的就是在bean声明上添加@Qualifier注解。例如,它可以与@Component组合使用,如下所示:

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

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

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

通过Java配置显式定义bean时,@Qualifier可以与@Bean注解一起使用:

@beanlam[beanlam] 
@Qualifier("cold")
public Dessert iceCream()
{
    return new IceCream();
}

使用自定义的限定符注解

可以创建自定义的限定符注解,借助这样的注解来表达bean所希望限定的特性。这里所需要做的就是创建一个注解,它本身要使用@Qualifier注解来标注。这样将不再使用@Qualifier("cold"),而是使用自定义的@Cold注解,该注解的定义如下所示:

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

同样可创建一个新的@Creamy注解来代替@Qualifier("creamy"):

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

现在重新看一下IceCream,并为其添加@Cold和@Creamy注解,如下所示:

@Component
@Cold
@Creamy
public class IceCream implements Desser
{
    ...
}

bean的作用域

在默认情况下,Spring应用上下文中所有bean都是作为以单例(singleton)的形式创建的

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

  • 单例(Singleton):在整个应用中,只创建bean的一个实例

  • 原型(Prototype):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例

  • 会话(Session):在Web应用中,为每个会话创建一个bean实例

  • 请求(Rquest):在Web应用中,为每个请求创建一个bean实例

单例是默认的作用域,但是正如之前所述,对于易变的类型。如果选择其他的作用域,要使用@Scope注解,它可以与@Component或@Bean一起使用

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

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

使用Java配置将Notepad声明为原型bean,组合使用@Scope和@Bean来指定所需的作用域::

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

使用XML配置bean,scope属性设置作用域:

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

不管使用哪种方式来声明原型作用域,每次注入或从Spring应用上下文中检索该bean的时候,都会创建新的实例。这样所导致的结果就是每次操作都能得到自己的Notepad实例

使用回话和请求作用域

对于购物车bean,会话作用域最为合适,因为它与给定的用户关联性最大。要指定会话作用域,可以使用@Scope注解,它的使用方式与指定原型作用域是相同的:

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

将value设置成了WebApplicationContext中的SCOPE_SESSION常量(它的值是session)。Spring会为Web应用中的每个会话创建一个ShoppingCart。这会创建多个ShoppingCart bean的实例,但是对于给定的会话只会创建一个实例,在当前会话相关的操作中,这个bean实际上相当于单例的

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

若想将ShoppingCart bean注入到单例StoreService bean 的Setter方法中:

@Component
public class StoreService
{
    @Autowired
    public void setShoppingCart(ShoppingCart shoopingCart)
    {
        this.shoppingCart = shoppingCart;
    }
    ...
}

Spring并不会将实际的ShoppingCart bean注入到StoreService中,Spring会注入一个到ShoppingCart bean的代理。如下图所示,这个代理会暴露与ShoppingCart相同的方法,所以StoreService会认为它就是一个购物车。但是,当StoreService调用ShoppingCart的方法时,代理会对其进行懒解析并将调用委托给会话作用域内真正的ShoppingCart bean

图片描述

作用域代理能够延迟注入请求和会话作用域的bean

如配置所示,proxyMode属性被设置成了ScopedProxyMode.INTERFACES,这表明这个代理要实现ShoppingCart接口,并将调用委托给实现bean。如果ShoppingCart是接口,则是最理想的代理模式。但如果ShoppingCart是具体类,Spring就无法创建基于接口的代理。此时,它必须使用CGLib来生成基于类的代理。所以,如果bean类型是具体类的话,proxyMode属性将设置为ScopedProxyMode.TARGET_CLASS,以此来表明要以生成目标类扩展的方式创建代理

在XML中声明作用域代理

<aop:scoped-proxy>是与@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>

使用<aop:scoped-proxy>元素,必须在XML配置中声明Spring的aop命名空间:

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

运行时值注入

避免硬编码值,让这些值在运行时再确定。为了实现这些功能,Spring提供了两种在运行时求值的方式:

  • 属性占位符(Property placeholder)

  • Spring表达式语言(SpEL,Spring Expression Language)

注入外部的值

//使用@PropertySource注解和Environment

package soundsystem;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;  

@Configuration
@PropertySource("classpath:/com/soundsystem/app.properties")        // 声明属性源
public class ExpressiveConfig
{
    @Autowired
    Environment env;
    
    @Bean
    public BlankDisc disc()
    {
        return new BlankDisc(env.getProperty("disc.title"), env.getProperty("disc.artist"));        // 检索属性值
    }
}  

@PropertySource在上述程序中引用了类路径中的app.properties文件,该文件如下所示:

disc.title = Sgt.Peppers Lonely Hearts Club Band;
disc.artist = Beatles;

深入学习Spring的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)

如果使用getProperty()方法时没有指定默认值,且该属性无定义,那么获取的值为null。如希望这个属性必须要定义,那么可使用getRequiredProperty()方法,如下所示:

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

此时,若disc.title或disc.artist属性无定义,将会抛出IllegalStateException异常。
如想检查一下某个属性是否存在,可以调用Environment的containsProperty()方法:

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

将属性解析为类使用getPropertyAsClass()方法:

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

Environment检查profile处于激活状态的方法:

  • String[] getActiveProfiles():返回激活profile名称的数组

  • String[] getDefaultProfiles():返回默认profile名称的数组

  • boolean acceptsProfiles(String... profiles):如果environment支持给定profile的话,就返回true

解析属性占位符

Spring一直支持将属性定义到外部的属性的文件中,并使用占位符值将其插入到Spring bean中。在Spring装配中,占位符的形式为使用“${... }”包装的属性名称。作为样例,在XML中按照如下的方式解析BlankDisc构造器参数:

<bean id = "sgtPeppers"
    class = "soundsystem.BlankDisc"
    c:_title = "${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或PropertySourcesPlaceholderConfigurer bean。从Spring3.1开始,推荐使用PropertySourcesPlaceholderConfigurer,因为它能够基于Spring Environment及其属性源来解析占位符

如下@Bean方法,在Java中配置了
PropertySourcesPlaceholderConfigurer:

@Beanocean[beanocean] 
public static PropertySourcesPlaceholderConfigurer placeholderConfigurer()
{
    return new PropertySourcesPlaceholderConfigurer();
}

使用XML配置,Spring context命名空间中的<context:propertyplaceholder>元素将会生成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:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd"
    
    <context:property-placeholder />
</beans>

使用Spring表达式语言进行装配

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

SpEL拥有很多特性,包括:

  • 使用bean的ID来引用bean

  • 调用方法和访问对象的属性

  • 对值进行算术、关系和逻辑运算

  • 正则表达式匹配

  • 集合操作

SpEL样例

SpEL表达式要放到“#{ ... }”之中

#{T(System).currentTimeMillis()}

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

SpEL表达式也可以引用其他的bean或其他bean的属性。如下表达式将计算得到ID为sgtPeppers的bean的artist属性:

#{sgtPeppers.artist}

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

#{systemProperties['disc.title']}

如通过组件扫描创建bean的话,在注入属性和构造器参数时,可以使用@Value注解。如下面样例展现BlankDisc从系统属性中获取专辑名称和艺术家的名字:

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

在XML配置中,可将SpEL表达式传入<property>或<constructor-arg>的value属性中,或者将其作为p-命名空间
或c-命名空间条目的值。如下BlankDisc bean的XML声明中,构造器参数就是通过SpEL表达式设置的:

<bean id = "sgtPeppers"
    class = "soundsystem.BlankDisc"
    c:_title = "#{systemProperties['disc.title']}"
    c:_artist = "#{systemProperties['disc.artist']}" />   

表示字面值

SpEL可以用来表示整数、浮点数、String值以及Boolean值

引用bean、属性的方法

SpEL所能做的另外一件基础的事情就是通过ID引用其他的bean

设有一个ID为artistSelector的bean,可在SpEL表达式中按照如下的方式来调用bean的selectArtist()方法:

#{artistSelector.selectArtist()}

对于被调用方法的返回值,同样可以调用它的方法

#{artistSelector.selectArtist().toUpperCase()}

为了避免出现NullPointerException,可以使用类型安全的运算符:“?.”。这个运算符能够在访问它右边的内容之前,确保它所对应的元素不是null。所以,如果selectArtist()的返回值是null的话,那么SpEL将不会调用toUpperCase()方法。表达式的返回值会是null:

#{artistSelector.selectArtist()?.toUpperCase()}    

在表达式中使用类型

如要在SpEL中访问类作用域的方法和常量,要依赖T()这个关键的运算符。如为了在SpEL中表达Java的Math类,需要按照如下的方式使用T()运算符:

T(java.lang.Math)

这里所示的T()运算符的结果会是一个Class对象,代表了java.lang.Math。如果需要的话,我们甚至可以将其装配到一个Class类型的bean属性中。但是T()运算符的真正价值在于它能够访问目标类型的静态方法和常量

如需要将PI值装配到bean属性中:

T(java.lang.Math).PI

调用T()运算符所得到类型的静态方法。计算得到一个0到1之间的随机数:

T(java.lang.Math).random()

SpEL运算符

运算符类型 运算符
算术运算 +、-、*、%、^
比较运算 <、>、==、<=、>=、lt、gt、eq、le、ge
逻辑运算 and、or、not、\
条件运算 ?:(ternary)、?:(Elvis)
正则表达式 mathes

计算正则表达式

计算集合

SpEL中最令人惊奇的一些技巧是与集合和数组相关的

下述表达式会计算songs集合中第五个(基于零开始)元素的title属性,这个集合来源于ID为musicBox bean:

#{musicBox.songs[4].title}

随机选择一首歌:

#{musicBox.songs[T(java.lang.Math).random()*timeless.songs.size()].title}   

“[]”运算符用来从集合或数组中按照索引获取元素,还可以从String中获取一个字符。如下表达式引用了String中的第9个字符,也就是"G":

#{'Nothing Gonna Change My Love For You'[8]}        

SpEL提供查询运算符(.?[]),它会用来对集合进行过滤,得到集合的一个子集。作为阐述的样例,假设你希望得到musicBox中artist属性为Khalil的所有歌曲。如下的表达式就使用查询运算符得到了Khalil的所有歌曲:

#{musicBox.songs.?[artist eq 'Khalil']}

SpEL还提供了另外两个查询运算符:“.^[]”和“.$[]”,它们分别用来在集合中查询第一个匹配项和最后一个匹配项。例如,考虑下面的表达式,它会查找列表中第一个artist属性为Khalil的歌曲:

#{musicBox.songs.^[artist eq 'Khalil']}

SpEL还提供了投影运算符(.![]),它会从集合的每个成员中选择特定的属性放到另外一个集合中。作为样例,假设我们不想要歌曲对象的集合,而是所有歌曲名称的集合。如下的表达式会将title属性投影到一个新的String类型的集合中:

#{musicBox.songs.![title]}     

投影操作可以与其他任意的SpEL运算符一起使用。如可使用如下表达式获得Khalil所有歌曲的名称列表:

#{musicBox.songs.?[artist eq 'Khalil'].![title]}  
 

布still
461 声望32 粉丝

数据挖掘、用户行为研究、用户画像