高级装配(advanced wiring)
配置profile bean
使用profile,可以设置bean在什么环境下会创建。
使用注解@Profile。
语法:
- @Profile(“environment”)
@Profile是类级别和方法级别,就是可以在类上使用和方法上使用。在类使用,表示该类设置的bean都是这种环境下会创建;在方法上使用,只有这个方法设置的bean在某种环境下使用。
@Profile可以是方法级别的,意味着不同环境创建的bean可以放到同一个JavaConfig配置文件中。
如果没有用这个注解,表示bean在任何环境都会创建。
demo:
@Configuration
@Profile("dev")
public class DevelopConfig {
@Bean
public DataSource xxxDataSource() {
//创建XxxDataSource xxxDataSource的code
return xxxDataSource;
}
}
@Configuration
public class ProductConfig {
@Bean
@Profile("pro")
public DataSource yyyDataSource() {
//创建YyyDataSource yyyDataSource的code
return yyyDataSource;
}
}
以上代码表示,在开发环境dev中,使用XxxDataSource数据源,在生产环境pro中,使用YyyDataSource数据源。
在xml中配置profile
标签有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-->
</beans>
表示在开发环境dev激活时这个xml下的bean才会被创建。
可以在一个xml文件中定义不同环境激活的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">
<bean id="dataSource" class="xxx">
<!---->
</bean>
</beans>
<beans profile="prod">
<bean id="dataSource" class="yyy">
<!---->
</bean>
</beans>
</beans>
表示在开发环境,第一个数据源对象会创建,在生产环境,第二个数据源对象会创建。
激活profile
使用profile的步骤是:
- 定义profile
- 激活profile
定义profile
profile有2个属性:spring.profiles.active和spring.profile.default。
- 如果设置了spring.profiles.active属性,那么它的值就会用来确定是哪个profile是激活的;
- 如果spring.profiles.active没有配置,spring就会查找spring.profiles.default,使用它的值来确定哪个profile是激活的;
- 如果spring.profiles.active和spring.profiles.default都没有配置,那么,spring只会创建没有profile限制的bean。
在web.xml中设置profile
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>u/WEB-INF/spring/root-context.xml</param-value>
</context-param>
<!--为上下文配置默认的profile-->
<context-param>
<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>
<!--为servlet设置默认的profile-->
<param-name>spring.profiles.default</param-name>
<param-value>dev</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
激活profile
使用注解@ActiveProfile激活profile
语法:
@ActiveProfile(“environment”)
- @ActiveProfile是类级别的
demo:
@RunWith(Spring4JUnitClassRunner.class)
@ContextConfiguration(classes="{PresistenceTestConfig.class}")
@ActiveProfile("dev")
public class PersistenceTest {
//...
}
这是激活了dev profile。
条件化的bean
从spring4开始,可以使用注解@Conditional来设置是否创建bean的条件。
语法:
- @Conditional(xxxCondition.class)
- 注解用到带有@Bean注解的方法上
- 这个xxxCondition类要实现Condition接口,实现matches方法
public interface Condition {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
- 如果matches方法返回真,则创建带有@Conditional(xxxConditon.class)注解的bean
这里假设如果环境存在magic属性,则创建bean。
public class MagicExistsCondition implements Conditon {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment e = context.getEnvironment();
return e.containsProperty("magic");
}
}
@Bean
@Conditional(MagicExistsCondition.class)
public MagicBean magicBean() {
return new MagicBean();
}
matches方法的ConditionContext和AnnotatedTypeMetadata可以组合更多的条件。
ConditionContext的方法有:
- BeanDefinitionRegistry getRegistry():可以通过返回的BeanDefinitionRegistry检查bean的定义。
- ConfigurableListableBeanFactory getBeanFactory():通过获取的ConfigurableListableBeanFactory检查bean是否存在,还有探查bean的属性。
- Environment getEnvironment():获取环境对象,可以判断环境中是否有某种属性和值
- ResourceLoader getResourceLoader():通过返回的ResourceLoader读取和检查加载的资源内容你。
- ClassLoader getClassLoader():通过ClassLoader加载并检查类是否存在。
AnnotatedTypeMetadata的方法有:
- boolean isAnnotated(String annotationName):可以判断带有@Bean注解的方法是否还有特定的其他注解。
- Map<String, Object> getAnnotationAttributes(String annotationName):根据这个方法和下面的方法(这4个方法)可以检查@Bean注解的方法上其他注解的属性。
- Map<String, Object> getAnnotationAttributes(String annotationName, boolean classValuesAsString)
- MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName)
- MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName, boolean classValuesAsString)
处理自动装配的歧义性
如果用接口自动注入属性,而有1个以上的bean实现了这个接口,并且这些bean都放到了Spring的ApplicatonContext中,那么,到底注入哪个实现类bean,这里就会出现歧义,报错。
demo:
- 接口
public interface Dessert {}
- 实现类
@Component
public class Cake implements Dessert {}
@Component
public class Cookie implements Dessert {}
@Component
public class IceCream implements Dessert {}
- 测试类
@Component
public class DessertTest {
@Autowired
private Dessert dessert;
}
运行时,汇报一下的错误:
nested exception is
org.springframework.beans.factory.NoUniqueBeanDefinitionException:
No qualifying bean of type [xxx.Dessert] is defined:
expected single matching bean but found 3: Cake,Cookie,IceCream
但是通常只有一个实现类,所以没有这种歧义性。
但是如果出现歧义性时,有以下方法:
- 将可选bean的某一个bean设置为首选(primary)的。
- 使用限定符(qualifier)帮助Sprin将可选的bean的范围缩小到只有1个。
标示首选的bean
当自动装配出现歧义性时,我们可以设置某一个bean为首选(primary)的bean,在自动装配时,首先选择设置为首选(primary)的bean。我们可以使用注解@Primary来设置某个bean为首选bean。
语法:@Primary,有3种方式设置bean为首选。以下例子,设置Cake为首选bean。
- @Primary和@Component组合
@Component
@Primary
public class Cake implements Dessert {}
- @Bean和@primary组合
@Bean
@Primary
public Dessert cake() {
return new Cake();
}
- 在xml中设置
<bean id="cake" class="com.desserteater.Cake" primary="true"></bean>
注意:如果有2个或以上的bean被设置为首选,那么,首选设置(@Primary或primary=“true”)将会失效,还是会出现歧义性。
为了解决歧义性,限定符是更加强大的机制。
限定自动装配的bean
在自动装配时,我们可以使用注解@Qualifier来把多个可选bean限定为只有一个。
知道:@Component设置在class上时,bean的id是首字母小写的类名。
语法:
- @Qualifier(“限定符”)
- @Qualifier和@Autowired组合来限定用某个bean
以下例子都把IceCream作为限定的一个bean。
关于限定符,我们可以使用bean的id。
@Autowired
@Qualifier("iceCream")
public void setDessert(Dessert dessert) {
this.dessert = dessert;
}
但是,这里有个问题,如果我们重构了IceCream类,把类名改为了Gelato,那么,此时bean的id是gelato,不再是iceCream,上面的setter方法没有找到bean id为iceCream的bean。
对此,我们可以创建自定义的限定符,而不是使用bean的id作为限定符,将@Component和@Qualifier组合。
@Component
@Qualifier("cold")
public class IceCream implements Dessert {}
@Autowired
@Qualifier("cold")
public void setDessert(Dessert dessert) {
this.dessert = dessert;
}
这里创建了限定符cold,在setter方法中使用的是cold限定符,不是bean的id,所以,就算我们改变了类名,@Qualifier仍然生效。
通常,我们是使用特征或描述性的术语来作为限定符,如果有2个或以上的bean,他们的特征相同,那么,就会出现问题。
@Component
@Qualifier("cold")
public class Popsicle implements Dessert {}
我们定义了一个bean,它的限定符也是cold,此时,setter方法就会出现歧义性。
这时,我们的思路是再定义一个限定符来区分这2个bean。
@Component
@Qualifier("cold")
@Qualifier("creamy")
public class IceCream implements Dessert {}
@Component
@Qualifier("cold")
@Qualifier("fruity")
public class Popsicle implements Dessert {}
@Autowired
@Qualifier("cold")
@Qualifier("creamy")
public void setDessert(Dessert dessert) {
this.dessert = dessert;
}
但是,注意:java不允许在同一个条目中使用相同类型的注解。从Java8开始,在同一个条目中可以使用相同类型的注解,但是这个注解定义时要带有@Repeatable注解。但是@Qualifier注解定义中没有带有@Repeatable注解,所以不能使用2次@Qualifier注解。
对此,我们可以创建自定义的限定符注解,如下:
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Cold {}
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Creamy {}
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Fruity {}
限定范围
@Autowired
@Cold
@Creamy
public void setDessert(Dessert dessert) {
this.dessert = dessert;
}
Bean的作用域
所谓bean的作用域,就是bean的作用范围。
spring的作用域有以下几种:
- 单例(singleton):在整个application中只有一个bean实例。
- 原型(prototype):从application context中获取bean或者依赖注入的时候,都会创建一个新的bean实例。
- 请求(request):在web应用中,每次请求,都会创建一个新的bean实例返回。
- 回话(session):在web应用中,对每个会话,都创建一个新的bean实例返回。
spring的bean默认的作用域是单例(singleton)。
通常情况下,单例模式的一种理想的方案,但是有例外,例如,如果每次获取bean对象,都需要这个bean对象是初始化那时的状态,那么如果使用单例模式的话,bean对象的状态被改变了(污染了)。
在3种配置方式中(自动配置、Java显式配置和xml显式配置),我们都可以设置bean的作用域。
自动配置
在自动配置中使用注解@Scope和@Component组合使用
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class Notepad {
}
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)可以写成@Scope(“prototype”),但是使用 ConfigurableBeanFactory.SCOPE_PROTOTYPE
的方式会更安全和不容易出错。
Java显式配置
在Java显式配置中将@Scope和@Bean组合使用。
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Notepad notepad() {
return new Notepad();
}
xml显式配置
在标签中使用scope属性。
<bean id="notepad" class="com.myapp.Notepad" scope="prototype" />
request和session作用域
在web应用中,通常我们在每个会话或请求中都创建一个bean实例是比较好的。
例如:在电商系统的购物车功能中,如果购物车bean是单例的话,那么每个用户的购物车bean是共享的,不是自己加入到购物车的物品也出现在自己的购物车中,这就乱套了。如果购物车bean是原型模式,如果在一个地方放入物品到购物车,再到另一个地方时,原来放入购物车的物品没有了,因为这里购物车bean使用原型模式,每次从应用上下文中获取或注入时都会创建一个新的购物车bean实例返回。
所以,在这里,使用session作用域是一个好的方案,一个会话中只有一个购物车bean实例,一个用户只有一个购物车。在一个会话或请求中,会话bean或请求bean,其实在这个会话或请求中就是一个单例的bean。
@Component
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.INTERFACES)
public ShoppingCart cart() {
}
现在要说说@Scope中的proxyMode属性,proxyMode = ScopedProxyMode.INTERFACES
解决了将会话/请求作用域的bean注入到单例的bean所产生的问题。
例如我们要把购物车bean注入到storeService中,让StoreService来处理购物车。
@Component
public class StoreService {
private ShoppingCart shoppingCart;
@Autowired
public void setShoppingCart(ShoppingCart shoppingCart) {
this.shoppingCart = shoppingCart;
}
//或
//@Autowired
//private ShoppingCart shoppingCart;
}
StoreService是单例模式,在spring加载应用上下文时创建StoreService bean,那么就需要注入ShoppingCart bean,而ShoppingCart bean还没有创建,ShoppingCart是会话作用域,只有当某个用户登入进系统创建了会话时,才会创建ShoppingCart bean。
而且,ShoppingCart bean会有多个,注入固定的ShoppingCart bean到StoreService不合适,我们想要的效果是StoreService bean注入相应用户的ShoppingCart bean。
这时,就是proxyMode = ScopedProxyMode.INTERFACES
起作用了,proxyMode = ScopedProxyMode.INTERFACES
作用是创建一个这些ShoppingCart bean的代理,将这个代理注入到StoreService bean中,当StoreService要调用购物车时,代理会委托相应用户的购物车bean来执行功能。如下图。
注意:proxyMode = ScopedProxyMode.INTERFACES
需要ShoppingCart是接口而不是实现类。如果ShoppingCart是实现类,要使用proxyMode = ScopedProxyMode.TARGET_CLASS,表明代理是目标类的扩展生成的。
将request作用域的bean注入到单例bean中也一样有这些问题,所以也要使用proxyMode属性创建代理来解决上面所说的问题。
xml的方式设置代理
使用aop命名空间实现设置代理。
- <aop:scoped-proxy />
<bean id="cart" class="com.myapp.ShoppingCart" scope="session">
<aop:scoped-proxy />
</bean>
<aop:scoped-proxy />默认情况是使用CGLib创建目标类的代理。如果想设置基于接口的代理,将<aop:scoped-proxy />的target-proxy-class属性设置为false
<bean id="cart" class="com.myapp.ShoppingCart" scope="session">
<aop:scoped-proxy target-proxy-class="false" />
</bean>
使用aop,要在xml中声明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>
运行时值注入
依赖注入,除了注入一个对象,或者说注入引用外,还有一种重要的方式,就是注入值,除了使用硬编码的方式注入值,还有在运行时才注入值的方式。
运行时注入值有2种方式:
- 属性占位符(property placeholder)
- Spring表达式语言(SpEL)
Java显示配置硬编码注入值
@Bean
public BlankDisc disc {
new BlankDisc("Field of hope", "seed");
}
注入外部的值
我们可以使用注解@PropertySource和Environment来获取外部的值,外部值是存放到属性源(属性文件)中。
首先在内路径中需要有一个属性文件,我们命名为app.properties
disc.title=filed of hope
disc.artist=seed
在spring中获取属性文件的属性
@Configuration
@PropertySource("classpath:soundsystem/app.properties")
public class SourceConfig {
@Autowired
private Environment env;
@Bean
public BlankDisc disc() {
new BlankDisc(env.getProperty("disc.title"), env.getProperty("disc.artist"));
}
}
@PropertySource语法
- @PropertySource(“classpath:属性文件的类路径”)
Environment的方法
获取属性的方法
- String getProperty(String propertyName):根据属性名获取属性值(字符串类型),如果没有这个属性,会返回null
- String getProperty(String propertyName, String defaultValue):根据属性名获取属性值(字符串类型),如果没有这个属性,会返回defaultValue
- T getProperty(String propertyName, Class type):根据属性名获取属性值,返回值类型是由参数type决定,如果没有这个属性,会返回null
- T getProperty(String propertyName, Class type, T defaultValue):根据属性名获取属性值,返回值类型是由参数type决定,如果没有这个属性,会返回defaultValue
当getProperty(String propertyName)中属性名不存在时,会返回null,如果想返回一个默认值的话,可以使用getProperty(String propertyName, String defaultValue)。
demo:
@Bean
public BlankDisc disc() {
new BlankDisc(env.getProperty("disc.title", "love story"), env.getProperty("disc.artist", "tailor"));
}
上面后2个方法和前2个方法类似,不同的就只有后2个方法设置了返回值的类型。例如如果在属性文件中属性值是连接池中数据源的数量,如果我们使用前2个方法获取属性值,那么就返回的是字符串类型,我们还要转成整型,使用后2个方法,就可以直接获取整型了。
demo:
db.connection.count=30
int count = env.getProperty("db.connection.count", Interger.class, 20);
如果不想返回null或者默认值,而是想要这个属性一定要定义,可以使用getRequiredProperty方法。
- getRequiredProperty(String propertyName) throw IllegalStateException
@Bean
public BlankDisc disc() {
new BlankDisc(env.getRequiredProperty("disc.title"), env.getRequiredProperty("disc.artist"));
}
如果属性文件中没有定义这个属性,会抛出IllegalStateException异常。
也可以使用containsProperty方法检查属性文件中是否有某个属性
- boolean containsProperty(String propertyName);存在返回true
除了可以获取上面的这些属性类型外,还可以使用getPropertyAsClass方法解析属性成Class对象。
- Class getPropertyAsClass(String propertyName, Class type)
Class<CompactDisc> clazz = env.getPropertyAsClass("disc.class", CompactDisc.class)
除了处理属性的方法外,Environment还有检查profile是否处于激活状态的方法。
- String[] getActiveProfiles():返回激活状态的profile的名字的数组
- String[] getDefaultProfiles():获取默认的profile的名字的数组
- boolean acceptProfile(String… profiles):如果environment支持给定的profile(s),那么返回true
虽然Environment不是经常使用,但是知道Environment的方法对我们也有不少好处。
属性占位符
使用属性占位符可以把值的注入延迟到运行时。
使用属性占位符的步骤:
- 配置PropertyPlaceholderConfigurer bean或PropertySourcesPlaceholderConfigurer bean
- 使用属性占位符
配置PropertyPlaceholderConfigurer bean或PropertySourcesPlaceholderConfigurer bean
从Spirng3.1开始,建议使用PropertySourcesPlaceholderConfigurer。
配置有2种方式:
- Java显式配置
- xml显式配置
Java显式配置
在Java配置类中,配置PropertySourcesPlaceholderConfigurer bean。
@Bean
public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
xml显式配置
使用标签<context:property-placeholder /> 配置PropertySourcesPlaceholderConfigurer bean。要使用context命名空间。
<?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 />
</beans>
使用属性占位符
使用属性占位符可以在xml或自动配置中使用。
语法:
使用${…}包裹属性名
在xml中使用
<bean id="compactDisc" class="soundsystem.BlankDisc" c:_title="${disc.title}" c:_artist="${disc.artist}" />
在自动配置中使用
使用注解@Value
public BlankDisc(
@Value("${disc.title}") String title,
@Value("${disc.artist}") String artist) {
this.title = title;
this.artist = artist;
}
Spring表达式语言
Spring表达式语言(Spring Expression Language, SpEL)
语法:使用**#{…}**包裹Spring表达式体
- #{表达式体}
Spring表达式语言可以在xml和自动配置中使用。
xml
在xml中,SpEL可以在、、p命名空间和c命名空间中使用。
<bean id="sgtPeppers" class="soundsystem.BlankDisc"
c:_title="#{compactDisc.title}" c:_artist="#{compactDisc.artist}" />
把bean id为compactDisc的bean的title和artist属性注入到sgtPeppers中
如果SpEL的值是字符串,可以使用toUpperCase()方法转成大写。例如上面的例子中将歌手名字转成大写
#{compactDisc.artist.toUpperCase()}
但是,歌手名可能是null,这时调用toUpperCase()方法会抛出NullPointerException异常。为了解决这个问题,可以使用 ?. 的方式。
#{compactDisc.artist?.toUpperCase()}
如果compactDisc.artist为null,就不会执行toUpperCase()方法,直接返回null。
自动配置
在自动配置中,使用注解@Value
public BlankDisc(
@Value("#{compactDisc.title}") String title,
@Value("#{compactDisc.artist}") String artist) {
this.title = title;
this.artist = artist;
}
Spring表达式可以注入的值类型有:
- 字面量
- bean、属性和方法
- 类、静态属性和静态方法
- 系统属性(属性文件里面的属性)
- 运算符
- 集合
字面量
在#{}里面放入字面量值。
字面量可以是:
- 整数
- 浮点数
- 字符串
- boolean值
支持科学表示法
demo:
#{1}:代表整数1
#{3.14159}
#{1.23E4}:代表整数1230
#{'abc'}
#{false}
其实,Spring表达式体单纯只有一个字面量意义不大,常用是和其他类型组合使用,例如做运算。
bean、属性和方法
Spring表达式体里面可以是bean、属性和方法。使用bean是用bean的id。
- #{compactDisc}:返回id为compactDisc bean,可以把这个值注入到类属性的类型是CompactDisc的属性中
- #{compactDisc.title}:获取id为compactDisc bean的title属性值
- #{artistSelector.selectArtist()}:把selectArtist方法的返回值作为这个SpEL的值
类、静态属性和静态方法
在SpEL中使用类、静态属性和静态方法,要使用操作符T()。
语法:
T(类):获取这个类的Class对象
- #{T(java.lang.Math)}:得到Math类的Class对象,可以注入到属性类型是Class类型的属性中。
- #{T(java.lang.Math).PI}:获取Math的PI
- #{T(java.lang.Math).random()}:调用Math的random方法,得到0(include) - 1(exclude)的随机数
系统属性(属性文件里面的属性)
SpEL可以读取属性文件里面的属性。
语法:
#{systemProperties[‘属性名’]}
- #{systemProperties[‘disc.title’]}:获取属性名是disc.title的值
运算符
SpEL支持的运算符如下
运算符类型 | 运算符 |
---|---|
数学运算符 | +,-,*,/,%,^ |
关系运算符 | >,gt,<,lt,== eq,>=,ge,<=,le |
逻辑运算符 | and,or,not |
关系运算符 | ?:(ternary),?:(Elvis) |
正则表达式 | matches |
数学运算符
^:乘方,2 ^ 4:2的4次方
关系运算符
>和gt,符号运算符和字母运算符作用是一样的
关系运算符
- ?:(ternary):三元表达式,通常在用在如果为null,返回一个默认值
#{scoreBoard.score > 1000 ? "winner" : "loser"}
如果bean id是scoreBoard的score属性值大于1000,返回"winner",否则返回"loser"
三元表达式通常在用在如果为null,返回一个默认值。
demo:如果歌名为null,返回tailor
#{disc.title ?: 'tailor'}
这个表达式通常被称作Elvis表达式
正则表达式
matches的使用方式是在matches左边放字符串,右边放正则表达式
语法:
- 字符串 matches 正则表达式
demo:检查某个字符串是否是邮箱地址
#{admin.email matches '[A-Za-z0-9-._]+@[A-Za-z0-9-]+(\.[A-Za-z0-9]+)*(\.[A-Za-z]{2,6})'}
这个正则表达式不一定完全满足邮箱地址的左右情况,这里只是用来举例。
集合
使用’[索引]'的方式得到集合或数组中的某个元素
获取某个元素
- []
#{jukeBox.songs[3]}
获取bean id是jukeBox bean的songs集合的第4个元素(索引从0开始)
#{jukeBox[3].artist}
获取bean id是jukeBox bean的songs集合的第4个元素的歌手名
[]也可以获取字符串某个索引的字符(索引从0开始)。
#{'hello world'[3]}
获取第四个字符l
获得子集
- .?[]
#{jukeBox.songs.?[artist eq 'tailor']}
获取歌手名称是tailor的元素,组成一个新的集合
获取第一个或最后一个符合条件的元素
-
.^[]
-
.$[]
-
使用 .^[] :获取第一个符合条件的元素
-
使用 .$[] :获取最后一个符合条件的元素
#{jukeBox.songs.^[artist eq 'tailor']}
获取第一个歌手名称的tailor的元素
投影元素的某个属性
语法
- .![属性名]
SpEL可以使用投影操作符 .![] 将集合元素的某个属性投影出来,形成新的集合(就是取出所有元素的某个属性值,放到一个新的集合中)。
demo:取出所有的歌名
#{jukeBox.songs.![title]}
.![]投影可以和其他组合使用
demo:取出tailor的所有歌名
#{jukeBox.songs.?[artist == 'tailor'].![title]}
注意:虽然SpEL很强大,但是如果写的SpEL太过智能,那么测试这个SpEL也更加重要,所以,尽量写简单。