【0】README
0)本文部分文字描述转自:“Spring In Action(中/英文版)”,旨在review spring(3)高级装配 的相关知识;
【1】环境与profile(考虑数据库配置)
1)使用嵌入式数据库
@Bean(destroyMethod="shutdown") public DataSource dataSource() { return new EmbeddedDatabaseBuilder() .addScript("classpath:schema.sql") .addScript("classpath:test-data.sql") .build(); }
2)使用JNDI从容器中获取DataSource
@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(); }
3)选择不同的DataSource配置
@Bean(destroyMethod="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; }
Attention)我们必须要有一种方法来配置DataSource,使其在每种环境下都会选择最为合适的配置;其中一种方式是在单独的配置类(或XML 文件)中配置每个bean,然后在构建阶段(可能会使用Maven的profiles)确定要将哪一个配置编译到可部署的应用中;
problem+solution)
problem)从开发阶段迁移到QA 阶段,重新构建可能不会出大问题,但从QA阶段迁移到 生产阶段,重新构建可能会引入bug;
solution)Spring所提供 的 解决方案并不需要重新构建;
【1.1】配置 profile bean
1)intro:spring 在重新构建的过程中需要根据环境决定该创建那个 bean 和 不创建那个bean;不过spring 并不是在构建时做出决策,而是等待运行时再来确定;这样的结果就是同一个部署单元能够适用于所有的环境,没有必要进行重新构建;
2)spring 引入了bean profile:要使用profile,首先要将所有不同的 bean 定义整理到一个或多个profile中,在将应用部署到每个环境时,要确保对应的 profile 处于激活状态 ;
3)在javaConfig中,可以使用 @Profile 注解指定某个bean属于哪一个profile;
看个荔枝)注解@Profile(干货——注解@Profile的作用)
荔枝1)嵌入式数据库可能会配置为如下形式:
@Configuration @Profile("dev") public class DevelopmentProfileConfig { @Bean(destroyMethod="shutdown") public DataSource dataSource() { return new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.H2) .addScript("classpath:schema.sql") .addScript("classpath:test-data.sql") .build(); } }荔枝2)使用JNDI从容器中获取DataSource 的 Profile配置
@Configuration @Profile("prod") public class ProductionProfileConfig { @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(); } }
荔枝3)从spring3.2开始,@Profile注解既可以在方法级别上使用,也可以在类级别上使用;(还可以和 @Bean 注解一起使用)
Attention)
A1)没有定义在profile中的bean 都会被创建,无论profile 激活与否;
A2)而定义在 profile中的bean,当且仅当对应的 profile 被激活时才可以创建;
4)在XML中配置 profile
4.1)通过<beans>元素的profile属性,在XML 中配置profile bean;
4.2)还可以在根<beans>元素中嵌套定义 <beans> 元素,而不是为每个环境都创建一个 profile 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"> <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" 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"> <jee:jndi-lookup id="dataSource" jndi-name="jdbc/myDatabase" resource-ref="true" proxy-interface="javax.sql.DataSource" /> </beans> </beans>
【1.2】激活profile
1)spring在确定哪个 profile处于激活状态时,需要依赖两个独立的属性: spring.profiles.active and spring.profiles.default ; 如果设置了 active属性的话,相应的Profile被激活,否则,查看default属性的值,如果default的值
没有设置的话,那就会忽略掉 profile中的bean的创建;
2)多种方法设置上述两个属性;
method1)作为 DispatcherServlet 的初始化参数;
method2)作为web 应用的上下文参数;
method3)作为 JNDI条目;
method4)作为环境变量;
method5)作为JVM的 系统属性;
method6)在集成测试类上, 使用 @ActiveProfiles 注解设置;(干货——注解@ActiveProfile的作用)
原书作者的设置方法:喜欢用DispatcherServlet 的参数将spring.profiles.default属性设置为开发环境的profile,会在Servlet上下文中进行设置;
看个荔枝)在web.xml 中设置 spring.profiles.default 属性
Attention) spring.profiles.active and spring.profiles.default 中的profile都是 复数形式: 这意味着可以同时激活多个 profile,列出多个profile 名称,以逗号分隔来实现;
3)使用profile进行测试
3.1)intro:spring提供了 @ActiveProfiles注解,来指定测试时要激活哪个profile;
3.2)下面的荔枝展示了 使用 @ActiveProfiles注解 激活 dev profile;
【2】条件化bean
1)intro:当满足给定条件时,才装配相应的bean;组合@Conditional注解 和 @Bean注解;(干货——注解@Conditional的作用)
对以上代码的分析(Analysis):
A1)matches() 方法:会得到 ConditionContext 和 AnnotatedTypeMetadata 对象用来做出决策;
A2)ConditionContext是一个接口:
public interface ConditionContext { BeanDefinitionRegistry getRegistry(); ConfigurableListableBeanFactory getBeanFactory(); Environment getEnvironment(); ResourceLoader getResourceLoader(); ClassLoader getClassLoader(); }A3)通过ConditionContext,可以做到如下几点(works):
work1)借助getRegistry方法返回的BeanDefinitionRegistry 检查bean的定义;work2)借助getBeanFactory方法返回的ConfigurableListableBeanFactory 检查bean是否存在,查看bean的属性
work3)借助getEnvironment方法返回的Environment 检查环境变量是否存在以及它的值是什么;
work4)读取并检查getResourceLoader方法返回的ResourceLoader 所加载的资源;
work5)借助getRegistry方法返回的BeanDefinitionRegistry 检查bean的定义;
work6)借助getClassLoader方法返回的ClassLoader 加载并检查类是否存在;
A4)AnnotatedTypeMetadata 能够让我们检查带有 @Bean 注解的方法上还有什么其他的注解;
public interface AnnotatedTypeMetadata { boolean isAnnotated(String annotationName); // 判断带有@Bean注解的方法是不是还有其他的注解. Map<String, Object> getAnnotationAttributes(String annotationName); Map<String, Object> getAnnotationAttributes(String annotationName, boolean classValuesAsString); MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName); MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName, boolean classValuesAsString); }
2)从spring4 开始,@Profile注解进行了重构,使其基于@Conditional 和 Condition实现:@Profile注解如下所示:
对以上代码的分析(Analysis):
A1)ProfileCondition 通过 AnnotatedTypeMetadata 得到了用于 @Profile 注解的所有属性;
A2)根据 通过ConditionContext得到的 Environment 来检查(acceptProfiles()方法)该profile 是否处于激活状态;
【3】处理自动装配的歧义性
1)problem+solution
1.1)problem测试用例:(error info: No qualifying bean of type [com.spring.chapter3.Disc] is defined: expected single matching bean
but found 2: jayChou,leehom)
1.2)solution:spring 提供了多种可选方案来解决这样的问题。你可以将可选的bean 中的某个设为首选(primary)的bean,或者使用限定符来帮助 spring 将可选的bean的范围缩小到只有一个bean;
1.3)如果在XML 配置bean的话,设置primary属性为 true来指定;
Attention)如果标识了多个首选(Primary)bean的话,@Primary注解就无法正常工作了;
【3.2】限定自动装配的bean
1)problem+solution
1.1)problem:设置首选bean的 局限性: 在于@Primary 无法将可选方案的范围限定到唯一一个无歧义性的可选项中。当首选bean的数量超过一个四,我们并没有其他的方法来进一步缩小可选范围;
1.2)solution:spring的限定符能够在所有可选的bean上进行缩小范围的操作,最终能够达到一个bena 满足所规定的限制条件;(使用@Qualifier注解)
看个荔枝)确保将jaychou 注入到CDPlayer中
2)创建自定义限定符:我们可以为bean设置自己的限定符,而不是依赖于将bean ID 作为限定符;
@Component @Qualifier("cold") public class IceCream implements Dessert { ... }
3)@Qualifier注解也可以和 @Bean注解一起使用
@Bean @Qualifier("cold") public Dessert iceCream() { return new IceCream(); }
4)使用自定义的限定符注解(干货——开发人员自己创建限定符注解)
4.1)problem+solution
@Component @Qualifier("fashion") public class JayChou implements Disc { ... }
@Component @Qualifier("fashion") public class Leehom implements Disc { ... }
4.1.1)problem:现在我们有两个带有“fashion”限定符的唱片,在自动装配Disc bean的时候,我们再次遇到了歧义性问题;
4.1.2)solution:需要更多的限定符来将可选范围限定到只有一个bean;(多个 @Qualifier 注解)
@Component @Qualifier("fashion") @Qualifier("cool") public class JayChou implements Disc { ... }@Component @Qualifier("fashion") @Qualifier("handsome") public class Leehom implements Disc { ... }
5)problem+solution:
5.1)problem:java不允许在同一个条目上重复使用出现相同类型的多个注解;因为这样的话,编译器会报错;
5.2)solution:自定义限定符注解;(不能再干货——创建自定义的限定符注解)
6)how to build diy @Qualifier annotaion.
step1)不再使用 @Qualifier("fashion") 注解,使用自定义的 @Fashion这,该注解 的定义如下:
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface Fashion{ }Attention)
A1)自定义注解本身实际上就成为了 限定符注解;A2)通过声明自定义的限定符注解,我们可以同时使用多个限定符,不会再有java 编译器的限制或错误了;
7)所以,我们可以添加
@Component @Qualifier("fashion") @Qualifier("cool") public class JayChou implements Disc { ... } // 改为 @Component @Fashion // a qualifier annotaion. @Cool// a qualifier annotaion. public class JayChou implements Disc { ... }
(干货——说白了,自定义限定符注解 就是多个标识而已,用于区别不同bean 和 对bean 进行分组(因为他们都属于fashion 组))
【4】 bean的作用域
1)intro: default case下,spring应用上下文中所有 bean都是以单例的形式创建的;也就是说,不管给定一个bean被注入到其他bean中多少次,每次所注入的都是 同一个实例;
2)problem+solution:显然,让多个对象引用同一个bean 是不合理的,因为对象中的内容是易变的,一个对象对bean做了修改,这会波及到其他bean的;
3)spring定义了多种作用域,可以基于这些作用域创建bean,包括(scope):
scope1)单例(Singleton):在整个应用中,只创建bean的一个实例;(干货——默认情况下,bean以单例模式创建)
scope2)原型(Prototype):每次注入或者通过spring应用上下文获取的时候,都会创建一个新的 bean 实例;
scope3)会话(Session):在web 应用中,为每个会话创建一个 bean实例;
scope4)请求(Request):在web 应用中,为每个请求创建一个 bean实例;
Attention)
A1)default case下: 是单例作用域;
A2)如果选择其他作用域,要使用 @Scope注解,它可以和 @Component 或 @Bean 联用;(干货——@Scope注解的作用)
A3)不管用哪一种方式来声明原型作用域,每次注入或从 spring 应用上下文中检索该bean的时候,都会创建新的实例;
看个荔枝)将其声明为原型bean:
@Component @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class Notepad { ... } 用XML 来配置的话,代码形式如下: <bean id="notepad" class="com.myapp.Notepad" scope="prototype" />
【4.1】使用会话和请求作用域
1)指定会话作用域: 使用 @Scope 注解,它的使用方式与 指定原型作用域是相同的;
@Component @Scope( value=WebApplicationContext.SCOPE_SESSION, proxyMode=ScopedProxyMode.INTERFACES) public ShoppingCart cart() { ... }
对以上代码的分析(Analysis):
A1)这会创建多个 ShoppingCart bean的实例,但是对于给定的会话只会创建一个实例,在当前会话相关的操作中,这个bean 实际上相当于单例的;
A2)@Scope注解:同时还有一个 proxyMode 属性,它被设置成了 proxyMode=ScopedProxyMode.INTERFACES;这个属性解决了将会话或请求作用域的bean 注入到单例bean中所遇到的问题;
2)proxyMode 代理模式所要解决的问题:
2.1)假设我们要将 ShoppingCart bean在注入到 单例 StoreService bean 的Setter 方法中,如下所示:
@Component public class StoreService { @Autowired public void setShoppingCart(ShoppingCart shoppingCart) { this.shoppingCart = shoppingCart; } ... }
对以上代码的分析(Analysis):
A1)因为StoreService 是一个单例bean,会在spring 应用上下文加载的时候创建;
A2)当它创建的时候,spring会试图 将 ShoppingCart bean 注入到 setShoppingCart() 方法中;但是ShoppingCart bean 是会话作用域的,此时并不存在。直到某个client
进入系统,创建了会话后,才会出现 ShoppingCart 实例;(干货——我此时才体会到了为什么需要懒加载)A3)而且,系统中有多个 ShoppingCart 实例(多个购物车):每个用户一个。我们并不想让spring注入 到某个固定的 ShoppingCart 实例到 StoreService中。我们希望的是当StoreService 处理ShoppingCart 功能时,它所使用的 ShoppingCart 实例恰好是当前会话所对应的那一个;
A4)spring 并不会将实际的ShoppingCart bean 注入到 StoreService 中。spring 会注入一个到 ShoppingCart bean 的代理,如下图所示。这个代理会暴露与 ShoppingCart 相同的方法,所以 StoreService 会认为他是一个ShoppingCart。但是,当StoreService
调用 ShoppingCart 的方法时,代理会对其进行懒解析并将调用委托给会话作用域内真正的 ShoppingCart bean;(干货——懒加载的调用过程,引入了代理proxy)
3)如果 ShoppingCart 是接口不是类的话,这是可以的;但如果ShoppingCart 是具体的类,那么spring无法创建基于接口的代理了。这时,spring必须使用CGLIB 来生成基于类的代理;
3.1)所以,如果bean类型是具体的类的话,我们必须要将 proxyMode 属性设置为 ScopedProxy- Mode.TARGET_CLASS,以此来表明要以生成目标类扩展的方式创建代理;
【4.2】在XML 中声明作用域代理
1)要设置代理模式,需要使用 spring aop 命名空间的一个新元素(<aop:scoped-proxy /> ):
<bean id="cart" class="com.myapp.ShoppingCart" scope="session"> <aop:scoped-proxy /> </bean>
2)为了使用 <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>
【5】运行时值注入
1)problem+solution
1.1)problem:在实现的时候将值硬编码在配置类中。用XML 装配bean的话,同样值也会是硬编码的;
1.2)solution:为了避免硬编码,而是想让这些值在运行时再确定。spring 提供了两种在运行时求值的方式(ways):
way1)属性占位符(Property placeholder)way2)spring 表达式语言(SpEL)
【5.1】注入外部的值
对以上代码的分析(Analysis):这个属性文件(app.properties)会加载到 spring 的 Environment中,稍后可以从这里通过 getProperty()方法 检索属性。
2)深入学习Spring的Environment
2.1)getProperty()方法有4个重载的变种形式(variant):
v1)String getProperty(String key)v2)String getProperty(String key, String defaultValue)
v3)T getProperty(String key, Class<T> type)
v4)T getProperty(String key, Class<T> type, T defaultValue)
2.2)稍微修改下源码,当指定属性不存在时,使用一个default value:
2.3)如果我们从属性文件中得到的是一个String类型的值,那么在使用前还需要将其转换为 integer,但是使用 getProperty()方法的重载形式,就能便利地解决这个问题:(干货——getProperty()重载方法的作用)
int connectionCount = env.getProperty("db.connection.count", Integer.class, 30);
3)Environment方法概览(methods)
method1)getRequiredProperty()方法:如果你希望这个属性必须要定义;没有定义会抛出异常;
@Bean public Disc disc() { return new JayChou( env.getRequiredProperty("disc.title"), env.getRequiredProperty("disc.artist")); }method2)containsProperty()方法:检查该属性是否存在;
boolean titleExists = env.containsProperty("disc.title");method3)getPropertyAsClass()方法:如果想要吧属性解析为类的话;
Class<CompactDisc> cdClass = env.getPropertyAsClass("disc.class", CompactDisc.class);method4)String[] getActiveProfiles():返回激活的profile名称数组;
method5)String[] getDefaultProfiles():返回默认的 profile 名称 的数组;
method6)boolean acceptsProfiles(String... profiles):如果 environment 支持给定的 profile的话,返回ture;
4)解析属性占位符
4.1)intro:spring 一直支持将属性定义到 外部的属性文件中,并使用占位符将其插入到 spring bean中;
4.2)在spring装配中,占位符的形式为 使用 "${...}" 包装的属性名称;
<bean id="sgtPeppers" class="soundsystem.BlankDisc" c:_title="${disc.title}" c:_artist="${disc.artist}" />4.3)如果我们依赖于组件扫描和自动装配来创建和初始化应用组建的话,就没有XML配置文件或者类了。此时,可以使用 @Value 注解,如下所示:
public JayChou( @Value("${disc.title}") String title, @Value("${disc.artist}") String artist) { this.title = title; this.artist = artist; }4.4)为了使用占位符,必须要配置一个 PropertySourcesPlaceholderConfigurer(spring3.1推荐使用),因为它能够基于spring
Environment 及其属性源来解析占位符;如下的@Bean方法配置了 PropertySourcesPlaceholderConfigurer@Bean public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() { return new PropertySourcesPlaceholderConfigurer(); }4.5)如果使用了XML配置 PropertySourcesPlaceholderConfigurer,推荐使用新元素 <context:property-placeholder>:
<?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>
Attention)
A1)解析外部属性能够将值的处理推迟到 运行时,但是它的关注点在于根据名称解析来自于 spring Environment和属性源的属性;
A2)而 spring 表达式语言提供了一种更通用的方式在 运行时计算所要注入的值;
【5.2】 使用spring 表达式语言进行装配
1)intro to spring表达式语言==Spring Expression Language, SpEL;
2)SpEL 拥有很多特性(characters):
c1)使用bean的ID 来引用bean;
c2)调用方法和访问对象的属性;
c3)对值进行算术,关系和逻辑运算 ;
c4)正则表达式匹配;
c5)集合操作;
3)SpEL 荔枝
3.1)first task : 要知道将 SpEL 表达式放到 " #{...} "之中,这与属性占位符有些类似,属性占位符需要放到 " ${...} " 之中;
3.2)荔枝组团来袭
#{T(System).currentTimeMillis()} : 计算表达式的那一刻当前时间的毫秒数;T() 表达式会将java.lang.System 视为 java中对应的类型;因此可以调用其 static 修饰的currentTimeMillis方法; #{jaychou.artist}:得到id 为 jaychou 的bean 的artist属性; #{systemProperties['disc.title']}:引用系统属性;
4)装配bean的时候如何使用这些表达式
4.1)通过组件扫描 创建bean的话,在注入属性和构造器参数时,我们可以使用 @Value 注解,这与之前看到的属性占位符有点类似,但现在我们要使用 SpEL表达式;
看个荔枝):从系统属性中获取专辑名称和艺术家的名字;
public BlankDisc( @Value("#{systemProperties['disc.title']}") String title, @Value("#{systemProperties['disc.artist']}") String artist) { this.title = title; this.artist = artist; }4.2)在XML配置中,可以将SpEL 表达式传入 <property> or <constructor-arg> 的value属性中,或者将其作为
p-命名空间或c-命名空间条目的值;看个荔枝)构造器参数通过SePL 表达式设置;
<bean id="sgtPeppers" class="soundsystem.BlankDisc" c:_title="#{systemProperties['disc.title']}" c:_artist="#{systemProperties['disc.artist']}" />
5)SePL 表达式 表示字面量
组团荔枝)
#{3.14159} == 表示浮点值; #{9.87E4} ==98700 #{'Hello'} ==计算String类型的字面值; #{false} ==boolean类型的值;
6)引用bean,属性和方法(通过ID 引用其他bean)
组团荔枝)使用bean ID(jaychou) 作为SpEL 表达式;
#{jaychou} #{jaychou.artist} // 对属性(artist)的引用; #{jaychou.selectArtist()} // 调用bean上的方法; #{jaychou.selectArtist().toUpperCase()} // 对方法的连续调用; #{artistSelector.selectArtist()?.toUpperCase()} // 使用类型安全的运算符,当返回不为null时,才调用后面的方法;
7)在表达式中使用类型
7.1)T()运算符:如果要在 SpEL 中访问类作用域的方法和常量的话,要依赖T() 这个关键的运算符;(干货——T()
是 SpEL 中一个关键运算符)
组团荔枝,我们继续)
T(java.lang.Math) // 为了在SpEL 中表达java的Math类,可以像左侧这样使用 T() 运算符; T(java.lang.Math).PI // 将PI 值装配到bean的属性中; T(java.lang.Math).random() // 计算得到一个0~1 间的随机数;
8)SpEL 运算符
8.1)SpEL 提供了多个运算符,如下表所示:
组团荔枝来袭)
#{2 * T(java.lang.Math).PI * circle.radius}//计算圆周长; #{T(java.lang.Math).PI * circle.radius ^ 2}//计算面积; #{disc.title + ' by ' + disc.artist} // 连接字符串; #{counter.total == 100} or #{counter.total eq 100}// 数字比较; #{scoreboard.score > 1000 ? "Winner!" : "Loser"} //三元运算符的应用; #{disc.title ?: 'Rattle and Hum'}// Elvis运算符,表达式判断disc.title是否为null,若是null的话,计算结果是 后面的字符串;
9)计算正则表达式
9.1)intro:SpEL 通过matches 运算符支持表达式中的模式匹配, 且matches() 函数会返回一个boolean 类型的值;
#{admin.email matches '[a-zA-Z0-9._%+-][email protected][a-zA-Z0-9.-]+\\.com'} // 判断一个字符串是否包含有效的邮件地址;
10)计算集合
组团荔枝来袭)
#{jaychou.songs[4].title}//计算songs集合中第5个(基于零开始)元素的title属性;(干货——SpEL 计算集合时的start index等于0); #{jaychou.songs[T(java.lang.Math).random() * jaychou.songs.size()].title} // 随机取集合某个下标的song的title属性; #{'This is a test'[3]} //返回第4个字母(s); #{jaychou.songs.?[album eq '十二新作']}//SpEL 提供了查询运算符,用于对集合的过滤,得到集合的一个子集;(返回jaychou的十二新作专辑下的所有songs)
10.1)SpEL 提供了两个查询运算符: ".^[]", ".$[]";分别用于查询第一个匹配项和最后一个匹配项;
#{jaychou.songs.^[album eq '十二新作']}//查找列表中第一个属于十二新作专辑的歌曲;10.2) SpEL提供了投影运算符:(.![]),它会从集合中的每个成员中选择特定的属性放到另一个集合中;
#{jaychou.songs.![title]}//将title属性投影到一个新的String类型的集合中;10.3)投影操作可以和其它任意的 SpEL 运算符一起使用;
#{jaychou.songs.?[album eq '十二新作'].![title]} // 获得十二新作专辑下的所有歌曲名称;// 难道没有发现,上述表达式等同于 select ... where...