一、高级装配
1、环境与profile
当程序从一个环境移动到另一个环境,往往配置会不同,比如数据库配置、加密算法等配置。比如:数据库配置:
开发环境dev的数据库配置可能是这样的:
@Bean(destoryMethod="shutdown")
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.addScript("classpath:schema.sql")
.addScript("classpath:test-data.sql")
.build();
}
生产环境prod的数据库配置可能是这样的:
@Bean
public DataSource dataSource() {
JndiObjectFactoryBean jndiObjectFactoryBean =
new JndiObjectFactoryBean();
jndiObjectFactoryBean.setJndiName("jdbc/myDS");
jndiObjectFactoryBean.setResourceRef(true);
jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
return (DataSource) jndiObjectFactoryBean.getObject();
}
而,QA环境中数据库配置可能是这样的:
@Bean(destoryMethod="close")
public DataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setUrl("jdbc:h2:tcp://dbserver/~/test");
dataSource.setDriverClassName("org.h2.Driver");
dataSource.setUsername("sa");
dataSource.setPassword("password");
dataSource.setInitialSize(20);
dataSource.setMaxActive(30);
return dataSource;
}
(1)、配置 profile bean
在3.1版本中,Spring引入了bean profile的功能。
使用@Profile注解指定某个bean属于某个profile,例如:指定dev环境的数据库配置bean:
@Configuration
@Profile("dev")
public class DevelopmentProfileConfig {
@Bean(destoryMethod="shutdown")
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.addScript("classpath:schema.sql")
.addScript("classpath:test-data.sql")
.build();
}
}
类似的,可以指定属于prod环境的数据库配置,在Spring3.1中,只能在类级别上使用@Profile注解。不过,从Spring3.2开始,也可以在方法级别上使用@Profile注解,因此,可以将属于不同profile的bean配置在同一个配置类中,例如:
@Configuration
public class DataSourceConfig {
@Bean(destoryMethod="shutdown")
@Profile("dev")
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.addScript("classpath:schema.sql")
.addScript("classpath:test-data.sql")
.build();
}
@Bean
@Profile("prod")
public DataSource dataSource() {
JndiObjectFactoryBean jndiObjectFactoryBean =
new JndiObjectFactoryBean();
jndiObjectFactoryBean.setJndiName("jdbc/myDS");
jndiObjectFactoryBean.setResourceRef(true);
jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
return (DataSource) jndiObjectFactoryBean.getObject();
}
}
注意:没有指定profile的bean始终都会被创建,与激活哪个profile没有关系
(2)、如何在XML中配置profile
我们可以通过<beans>元素的profile属性,在XML中配置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:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd"
profile="dev">
<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:schema.sql" />
<jdbc:script location="classpath:test-data.sql" />
</jdbc:embedded-database>
</beans>
类似地,也可以将profile设置为prod,创建适用于生产环境的配置。
另外,可以在跟元素<beans>元素中嵌套定义<beans>元素,而不是为每个环境都创建一个profile XML文件,这样可以将属于不同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: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-jee.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd"
profile="dev">
<beans profile="dev">
<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">
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destory-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">
<jee:jndi-lookup id="dataSource"
jndi-name="jdbc/myDatabase"
resource-ref="true"
proxy-interface="javax.sql.DataSource" />
</beans>
</beans>
另外,Springi提供了@ActiveProfiles注解,可以用来指定运行测试时要激活哪个profile。例如:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={PersistenceTestConfig.class})
@ActiveProfiles("dev")
public class PersistenceTest {
...
}
2、条件化的bean
条件化bean是满足特定的条件后bean才会被创建。
在Spring 4引入了一个新的@Conditional注解,它可以用到带有@Bean注解的方法上。如果给定的条件计算结果为true,就会创建这个bean,否则的话,这个bean会被忽略。
例如,我们再JavaConfig中配置一个条件化的bean:
@Bean
@Conditional(MagicExistsCondition.class)
public MagicBean magicBean() {
return new MagicBean();
}
上述代码中,@Conditional注解中给定了一个class,它指明了条件。上例中的MagicExistsConditon类实现了Condition接口,@Conditional将会通过Condition接口进行条件对比,Condition接口的定义如下:
public interface Condition {
boolean matches(ConditionContext ctxt, AnnotatedTypeMetadata metadata);
}
MagicExistsConditon类实现了Condition接口,实现如下:
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment env = context.getEnvironment();
return env.containsProperty("magic");
}
上述代码,matches方法检查环境变量中是否存在magic属性,如果存在返回true,不存在就返回false。因此在bean配置中的意思就是,如果MagicExistsCondition类的matches方法返回true,那么该标记的bean就会被创建,否则,该bean不被创建。
(1)、Condition接口中的matches()方法参数的含义
matches()方法有两个参数:ConditionContext、AnnotatedTypeMetadata,这两个参数提供可能的条件信息,来辅助做出决策。
ConditionContext是一个接口,大致如下:
public interface ConditionContext {
BeanDefinitionRegistry getRegistry();
ConfigurableListableBeanFactory getBeanFactory();
Environment getEnvironment();
ResourceLoader getResourceLoader();
ClassLoader getClassLoader();
}
注:getRegistry()返回的BeanDefinitionRegistry检查bean定义;
getBeanFactory()返回的ConfigurableListableBeanFactory检查bean是否存在,甚至探查bean的属性;
借助getEnvironment()返回的Enrionment检查环境变量是否存在以及它的值是什么;
读取并探查getResourceLoader()返回的ResourceLoader所加载的资源;
借助getClassLoader()返回的ClassLoader加载并检查类是否存在。
AnnotatedTypeMetadata也是一个接口,大致如下:
public interface AnnotatedTypeMetadata {
boolean isAnnotated(String annotationType);
Map<String, Object> getAnnotationAttributes(String annotationType);
Map<String, Object> getAnnotationAttributes(String annotationType, boolean classValuesAsString);
MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType);
MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType, boolean classValuesAsString);
}
注:该接口能够让我们检查带有@Bean注解的方法上还有什么其他注解等信息。
(2)、Spring 4中, @Profile注解的实现
从 Spring 4开始, @Profile注解进行了重构,使其基于@Conditional 和 Condition实现。如下:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@Conditional(ProfileConditon.class)
public @interface Profile {
String[] value();
}
class ProfileCondition implement Condition {
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
if (context.getEnvironment() != null) {
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) {
if (context.getEnvironment().acceptsProfiles(((String[])value))) {
return true;
}
}
return false;
}
}
return true;
}
}
注:ProfileCondition通过AnnotatedTypeMetadata得到了用于@Profile注解的所有属性,借助该信息,它会明确地检查value属性,该属性包含了bean的profile名称,然后,它根据通过ConditionContext得到的Environment来检查(借助acceptsProfiles()方法)该Profile是否处于激活状态。
3、处理自动装配的歧义性
当Spring试图装配属性时,发现没有唯一、无歧义的可选值(通常是由于存在多个实现类的情况),Spring会抛出NoUniqueBeanDefinitionException。
当确实发生歧义性时,Spring提供了多种可选方案来解决这个问题。
(1)、标示首选的bean
在声明bean的时候,将其中一个bean设置为首选(primary)bean能够避免自动装配的歧义性,当遇到歧义性的时候,Spring将会使用首选的bean,而不是其他可选的bean。
标示首选bean的方法有多种:
第一种:与@Component组合用在组件扫描的bean上,例如:
@Component
@Primary
public class IceCream implements Dessert {
...
}
第二种:与@Bean组合使用用在Java配置类的bean上,例如:
@Bean
@Primary
public Dessert iceCream() {
return new IceCream();
}
第三种,如果使用xml配置bean的话,可以使用<bean>元素的primary属性,例如:
<bean id="iceCream"
class="com.desserteater.IceCream"
primary="true" />
(2)、限定自动装配的bean
设置首选bean的局限性在于@Primary无法将可选方案的范围限定到唯一一个无歧义性的选项中。它只能标示一个优先的可选方案,当首选bean的数量超过一个时,我们并没有其他的方法进一步缩小可选范围。
Spring的限定符能够在所有可选的bean上进行缩小范围的操作,最终能够达到只有一个bean满足所规定的限定条件。如果将所有的限定符都用上后依然存在歧义性,那么你可以继续使用更多的限定符来缩小选择范围。
@Qualifier注解是使用限定符的主要方式。它可以与@Autowired和@Inject协同使用,在注入的时候指定想要注入进去的是哪个bean。例如:
@Autowired
@Qualifier("iceCream")
public void setDessert(Dessert dessert) {
this.dessert = dessert;
}
注:上例中,为Qualifier注解所设置的参数就是想要注入的bean的ID。
上述方法也存在问题:例如:当IceCream类被重命名为Gelato的话,bean的ID和默认的限定符会变为gelato,就无法匹配setDessert()方法中的限定符,自动装配就会失败。原因是:setDessert()方法上所指定的限定符与要注入的bean的名称是紧耦合的。
(3)、创建自定义的限定符
鉴于上述方法的缺点,我们可以为bean设置自己的限定符,例如:
@Component
@Qualifier("cold")
public class IceCream implements Dessert {...}
注:上述代码中的,“cold”限定符是我们自定义的,这样就可以不与类名紧耦合了,在注入的地方,如下使用:
@Autowired
@Qualifier("cold")
public void setDessert(Dessert dessert) {
this.dessert = dessert;
}
@Qualifier也可以与@Bean一起使用,例如:
@Bean
@Qualifier("cold")
public Dessert iceCream() {
return new IceCream();
}
(4)、使用自定义的限定符注解
上述方法有时会出现问题,比如,当我们存在多个bean都带有“cold”自定义的限定符,那么在自动装配时Spring还是无法解决歧义性的问题。而且Java不允许在同一个条目上重复出现相同类型的多个注解,因此,不能通过再增加自定义限定符的方式解决歧义性问题。
但是,Spring提供了另外的方法,那就是自定义的限定符注解。借助这样的注解来表达bean所希望限定的特性。及创建自己的注解。
例如:创建@Cold注解
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD,
ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Cold {}
例如:创建@Creamy注解
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD,
ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Creamy {}
这样,在组件扫描时,可以为组件添加上自己的注解,例如:
@Component
@Cold
@Creamy
public class IceCream implements Dessert {....}
@Component
@Cold
@Fruity
public class Popsicle implements Dessert {....}
在注入点,可以使用必要的限定符注解进行任意组合,从而将可选的范围缩小到只有一个bean满足需求。例如:
@Autowired
@Cold
@Creamy
public void setDessert(Dessert dessert) {
this.dessert = dessert;
}
4、bean的作用域
Spring定义了多种作用域,可以基于这些作用域创建bean,包括:
【】单例(Singleton): 在整个应用中,只创建bean的一个实例。
【】原型(Prototype):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例。
【】会话(Session):在Web应用中,为每个会话创建一个bean实例。
【】请求(Rquest):在Web应用中,为每个请求创建一个bean实例。
注:单例是默认的作用域,如果需要显式地指定作用域,要使用@Scope注解,它可以和@Component或@Bean一起使用。
例如:如果使用组件扫描来发现和声明bean,可以在bean的类上使用@Scope注解:
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class Notepad {...}
注:上述代码使用ConfigurableBeanFactory类的SCOPE_PROTOTYPE常量设置了原型作用域,与@Scope("prototype")效果相同,但是使用SCOPE_PROTOTYPE常量更加安全并且不易出错。
如果想在Java配置中将Notepad声明为原型bean,那么可以组合使用@Scope和@Bean来指定所需的作用域:
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Notepad notepad() {
return new Notepad();
}
如果使用XML来配置bean的话,可以使用<bean>元素的scope属性来设置作用域:
<bean id="notepad"
class="com.myapp.Notepad"
scope="prototype" />
(1)、使用会话和请求作用域
在Web应用中,常常需要实例化在会话和请求范围内共享的bean。为bean指定会话作用域的方式跟指定原型作用域相同,例如:
@Component
@Scope (
value=WebApplicationContext.SCOPE_SESSION,
proxyMode=ScopedProxyMode.INTERFACES)
public ShoppingCart cart() {...}
注:上例中,通过WebApplicationContext.SCOPE_SESSION的设置,Spring会为Web应用中的每个会话创建一个ShoppingCart。另外proxyMode属性,用来解决将会话或请求作用域的bean注入到单例bean中所遇到的问题。
会话或请求作用域bean注入到单例bean中时遇到的问题:
假设,我们要将ShoppingCart bean注入到单例StoreService bean的Setter方法中,因为StoreService是一个单例的bean,会在Spring应用上下文加载的时候创建,当它创建的时候,Spring会试图将ShoppingCart bean注入到setShoppingCart()方法中。但是ShoppingCart bean是会话作用于的,此时并不存在。直到某个用户进入系统,创建了会话之后,才会出现ShoppingCart实例。这是问题中的一部分。
另外,系统中可能存在多个ShoppingCart实例,每一个用户一个,但是我们并不希望Spring注入某个固定的ShoppingCart实例到StoreService中,而是注入当前会话的ShoppingCart。
解决该问题的方法:
Spring引入了作用域代理的方法,来解决该问题。代理会暴露与ShoppingCart相同的方法,当StoreService调用ShoppingCart的方法时,代理会对其进行懒解析并将调用委托给会话作用域内的真正的ShoppingCart bean。(即作用域代理能够延迟注入请求和会话作用域的bean)
如果,proxyMode属性值为ScopeProxyMode.INTERFACES,则表明这个代理要实现ShoppingCart接口,并将调用委托给实现bean;
如果,proxyMode属性值为ScopeProxyMode.TARGET_CLASS,则这个代理要以生成目标类扩展的方式创建。
(2)、在XML中声明作用域代理
如果使用XML来声明会话或请求作用域的bean,除了需要使用<bean>元素的scope属性设置bean的作用域外,还要使用Spring aop命名空间的<aop:scoped-proxy>元素。
<aop:scoped-proxy>是与@Scope注解的proxyMode属性功能相同的SpringXML配置元素。它会告诉Spring为bean创建一个作用域代理。默认情况下,它会使用CGLib创建目标类的代理。(可以通过将proxy-target-class属性设置为false,进而要求生成基于接口的代理),例如
<bean id="cart"
class="com.myapp.ShoppingCart"
scope="session">
<aop:scoped-proxy/>
</bean>
注:上述代码,声明作用域为会话的bean,同时指定代理模式为创建目标类的方式。
<bean id="cart"
class="com.myapp.ShoppingCart"
scope="session">
<aop:scoped-proxy proxy-target-class="false" />
</bean>
注:上述代码,声明作用域为会话的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">
...
</bean>
5、运行时值注入
运行时值注入,是指将一个值注入到bean的属性或者构造器的参数中。
为了实现这些功能,Spring提供了两种在运行时求值的方式:
【】:属性占位符
【】:Spring表达式语言(SpEL)
(1)、注入外部的值 (即直接从Environment中检索属性)
处理外部值的最简单方法就是声明属性源并通过Spring的Environment来检索属性。
例如:下列程序使用外部属性来装配BlankDisc bean:
@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=The Beatles
该属性文件会加载到Spring的Environment中,稍后可以从这里检索属性。
*关于Spring的Environment
Environment提供了getProperty()方法的四个重载形式:
【】String getProperty(String key) // 返回String类型的值
【】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()方法与getProperty()方法类似,但是getRequiredProperty()方法要求获取的属性必须定义,如果没有定义将会抛出IllegalStateException异常。
containsProperty()方法:检查某个属性是否存在。
getPropertyAsClass()方法:将属性解析为类。例如:
Class<CompactDisc> cdClass = env.getPropertyAsClass("disc.class", CompactDisc.class);
除了属性相关的功能外,Environment还提供了一些方法来检查哪些profile处于激活状态:
【】String[] getActiveProfiles() : 返回激活profile名称的数组
【】String[] getDefaultProfiles() : 返回默认profile名称的数组
【】boolean acceptsProfiles(String... profiles) : 如果environmet支持给定profile的话,返回true;
(2)、解析属性占位符
直接从Environment中检索属性是非常方便的,尤其是在Java配置类中装配bean的时候,但是,Spring也提供了通过占位符装配属性的方法,这些占位符的值会来源于一个属性源。
在Spring装配中,占位符的形式为使用“${...}”包装的属性名称。
如果要在XML中解析构造参数,可以如下所示:
<bean id="sgtPeppers"
class="soundsystem.BlankDisc"
c:_title="${disc.title}"
c:_title="${disc.artist}" />
如果是依赖组件扫描和自动装配来创建和初始化组件的话,可以使用@Value注解,例如:在BlankDisc类中,可以如下初始化:
public BlankDisc(
@Value("${disc.title}") String title,
@Value("${disc.artist}") String artist) {
this.title = title;
this.artist = artist;
}
此外,为了使用占位符,必须配置一个PropertyPlaceholderConfigurer bean 或PropertySourcesPlaceholderConfigurer bean。(从Spring 3.1开始,推荐使用PropertySourcesPlaceholderConfigurer, 因为它能够基于Spring Environment及其属性源来解析占位符)。
配置方法,如果使用java配置类,可以如下,例如:
@Bean
public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
如果使用XML配置,Spring Context命名空间中的<context:property-placeholder>元素将会为你生成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/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder />
</bean>
(3)、使用Spring表达式语言进行装配
Spring 3引入了Spring表达式语言(Spring Expression Language, SpEL), 它能够以一种强大和简洁的方式将值装配到bean属性和构造器参数值中。
SpEL表达式要放到“#{...}”之中,去除“#{...}”标记之后,剩下的就是SpEL表达式体。
例如,在Java类中使用SpEL注入值:
public BlankDisc(
@Value("#{systemProperties['disc.title']}") String title,
@Value("#{systemProperties['disc.artist']}") String artist) {
this.title = title;
this.artist = artist;
}
上述代码,展示了SpEL从系统属性中获取属性值。
例如:
<bean id="sgtPeppers"
class="soundsystem.BlankDisc"
c:_title="#{systemProperties['disc.title']}"
c:_title="#{systemProperties['disc.artist']}" />
上述代码,展示了SpEL在xml声明中的使用方法。
【】使用SpEL表示字面值:
#{3.14159} // 表示浮点值
#{9.87E4} // 使用科学记数法的方式进行表示
#{‘hello’} // 表示String类型的字面值
#{false} // 表示Boolean类型的值
【】引用bean、属性和方法
SpEL可以通过ID引用其他的bean。
例如:#{sgtPeppers} // 引用bean sgtPeppers
#{sgtPeppers.artist} // 引用sgtPeppers的artist属性
#{sgtPeppers.selectArtist()} // 调用sgtPeppers bean的selectArtist()方法,获得返回值
#{sgtPeppers.selectArtist().toUpperCase()} // 调用selectArtist()方法,再对返回的字符串调用toUpperCase()方法
#{sgtPeppers.selectArtist()?.toUpperCase()} // 与上句作用一样,不同点是如果selectArtist()方法返回null,不会抛出NullException异常,而是SpEL表达式的返回值为null。
【】在表达式中使用类型
在SpEL中访问类的静态方法和常量时,要依赖T()运算符,例如:
T(java.lang.Math) // 这里T()运算符的结果是一个class对象,代表了java.lang.Math。
T(java.lang.Math).PI // 访问类的常量
T(java.lang.Math).random() // 访问类的静态方法
【】SpEL运算符
算术运算符:+ 、-、*、/、%、^
比较运算符:<、>、==、<=、>=、lt、gt、eq、le、ge
逻辑运算符:and、or、not、|
条件运算符:?:(ternary)、?:(Elvis)
正则表达式:matches
例如:#{2 * T(java.lang.Math).PI * circle.radius}
#{T(java.lang.Math).PI * circle.radius ^ 2}
#{disc.title + ' by ' + disc.artist}
#{counter.total == 100} 等价于 #{counter.total eq 100}
#{scoreboard.score > 1000 ? "Winnder!" : "Loser"}
#{disc.title ?: 'Rattle and Hum'} // 如果disc.title的值不是null, 表达式的结果就是disc.title的值, 如果disc.title的值为null,表达式的值就是'Rattle and Hum', // 注:这种表达式通常称为Elvis运算符。
【】计算正则表达式
SpEL通过matches运算符支持表达式中的模式匹配,如果与正则表达式匹配,则返回true,否则返回false。例如:
#{admin.email matches '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.com'}
【】计算集合
#{jukebox.songs[4].title} // 这个表达式会计算songs集合中下标为4的元素的title属性。
#{jukebox.songs[T(java.lang.Math).random() * jukebox.songs.size()].title} // 从jukebox中随机选择一首歌
#{‘This is a test’[3]} // 该表达式引用String中的第四个字符,也就是‘s’。
#{jukebox.songs.?[artist eq 'Aerosmith']} // 查询运算符(.?[])会用来对集合进行过滤,得到集合的一个子集,SpEL在迭代歌曲列表songs的时候,会对歌曲集合中的每一个条目计算[]内部的表达式,当[]内部表达式值为ture时,那么相应条目将会被放到新的集合中,否则不会,本例中,[]内部表达式检查歌曲的artist属性是不是等于Aerosmith。
注:“.^[]” 运算符,用来在集合中查询第一个匹配项; “.$[]”运算符,用来在集合中查询最后一个匹配项。
#{jukebox.songs.^[artist eq 'Aerosmith']} // 用来查找列表中第一个artist属性为Aerosmith的歌曲;
注:“.![]” 运算符,投影运算符,它会从集合的每个成员中选择特定的属性放到另外一个集合中,例如:
#{jukebox.songs.![title]} // 该表达式获得songs集合中所有歌曲title属性值,组成的名称列表。
#{jukebox.songs.?[artist eq 'Aerosmith'].![title]} // 该表达式获得艺术家 Aerosmith的所有歌曲的名称列表。