Spring之路----chapter03 高级装配
1. 环境与profile
在开发软件的时候,有一个很大的挑战就是将应用程序从一个环境迁移到另外一个环境。开发阶段中,某些环境相关做法可能并不适合迁移到生产环境中,甚至即便迁移过去也无法正常工作。数据库配置、加密算法以及与外部系统的集成是跨环境部署时会发生变化的几个典型例子。
在这个过程中需要根据环境决定该创建哪个bean和不创建哪个bean。
Spring引入了bean profile的功能。要使用profile,你首先要将所有不同的bean定义整理到一个或多个profile之中,在将应用部署到每个环境时,要确保对应的profile处于激活(active)的状态。
(1) 在JavaConfig中配置profile
在Java配置中,可以使用@Profile注解指定某个bean属于哪一个profile。只有在xxx profile激活的时候,才会创建对应的bean。
1) 在类上使用profile注解
2) 在方法上使用profile注解
@profile("prod"):在类或者方法中的bean只有在prod profile激活时才会创建
(2) 在XML中配置profile
1)单个profile
<beans..............
........................
profile="dev">
2)重复使用元素来指定多个profile
<beans..............
........................
profile="dev">
<beans..............
........................
profile="prod">
(3) 既然需要profile被激活的时候才能创建相应的bean,那profile什么时候被激活呢,其依赖于两个属性:
. spring.profiles.active
. spring.profiles.default
有多种方式来设置这两个属性:
作为DispatcherServlet的初始化参数;
作为Web应用的上下文参数;
作为JNDI条目;
作为环境变量;
作为JVM的系统属性;
在集成测试类上,使用@ActiveProfiles注解设置。
1) Web环境中使用spring.profiles.default在web.xml文件中进行指定
2) 当应用程序部署到QA、生产或其他环境之中时,负责部署的人根据情况使用系统属性、环境变量或JNDI设
置spring.profiles.active即可。当设置spring.profiles.active以后,至于spring.profiles.default置成什么值就已经无所谓了;系统会优先使用spring.profiles.active中所设置的profile。
Spring提供了@ActiveProfiles注解,我们可以使用它来指定运行测试时要激活哪个profile。
2. 条件化的bean
在条件化创建bean方面,Spring的profile机制是一种很棒的方法,这里的条件要基于哪个profile处于激活状态来判断。Spring 4.0中提供了一种更为通用的机制来实现条件化的bean定义,在这种机制之中,条件完全由你来确定。让我们看一下如何使用Spring 4和@Conditional注解定义条件化的bean。
@Conditional(MagicExistsCondition.class)
条件就是MagicExistsCondition,这个类要实现Condition接口才行,Condition接口中只有一个方法matches,所以实现了Condition接口的类只需要重新matches方法即可,matches方法返回True就可以创建带有@Conditional注解的bean,否者就不创建。
1 public interface Condition{ 2 boolean matches( ConditionContext ctxt, 3 AnnotatedTypeMetadata metadata); 4 }
Condtion接口中的matches方法的第一个参数为ConditionContext接口,通过ConditionContext,我们可以做到如下几点:
借助getRegistry()返回的BeanDefinitionRegistry检查bean定义;
借助getBeanFactory()返回的ConfigurableListableBeanFactory检查bean是否存在,甚至探查bean的属性;
借助getEnvironment()返回的Environment检查环境变量是否存在以及它的值是什么;读取并探查getResourceLoader()返回的ResourceLoader所加载的资源;
借助getClassLoader()返回的ClassLoader加载并检查类是否存在
Condtion接口中的matches方法的第二个参数为AnnotatedTypeMetadata接口,其能够让我们检查带有@Bean注解的方
法上还有什么其他的注解
3. 处理自动装配的歧义性
有多个类实现了同一个接口在注入的时候就会发生问题,有多个bean可以匹配。
【解决办法】:
(1) 标识首选的bean
1)JavaConfig注解----@Primary
@Primary能够与@Component组合用在组件扫描的bean上,也可以与@Bean组合用在Java配置的bean声明中。
2)XML的primary属性
1 <bean id="iceCream" 2 class="com.desserteater.IceCream" 3 primary="true" />
(2) 限定自动装配bean-------@Qualifier
设置首选bean的局限性在于@Primary无法将可选方案的范围限定到唯一一个无歧义性的选项中。
@Qualifier注解是使用限定符的主要方式。它可以与@Autowired和@Inject协同使用,在注入的时候指定想要注入进去的是哪个bean。为@Qualifier注解所设置的参数就是想要注入的bean的ID。 所有使用@Component注解声明的类都会创建为bean, 并且bean的ID为首字母变为小写的类名。 因此, @Qualifier("iceCream")指向的是组件扫描时所创建的bean, 并且这个bean是IceCream类的实例。
创建一个注解,它本身需要用@Qualifier注解来进行标注
自定义的@Cold注解
1 @Target{{ElementType.CONSTRUCTOR,ElementType.FIELD, ElementType.METHOD,ElementType.TYPE}} @Retention(RententionPolycy.RUNTIME) 2 @Qualifier 3 public @interface Cold{}
3. Bean的作用域
3.1 在默认情况下,Spring应用上下文中所有bean都是作为以单例(singleton)的形式创建的。
Spring定义了多种作用域, 可以基于这些作用域创建bean, 包括:
单例( Singleton) : 在整个应用中, 只创建bean的一个实例。
原型( Prototype) : 每次注入或者通过Spring应用上下文获取的时候, 都会创建一个新
的bean实例。
会话( Session) : 在Web应用中, 为每个会话创建一个bean实例。
请求( Rquest) : 在Web应用中, 为每个请求创建一个bean实例。
如果选择其他的作用域, 要使用@Scope注解, 它可以与@Component或@Bean一起使用。
(1) 自动装配
1 @Component 2 @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) 3 public class Notepad{ 4 .... 5 }
(2)Java代码装配
1 @Bean 2 @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) 3 public Notepad notepad(){ 4 return new Notepad(); 5 }
(3)XML配置
1 <bean id="notepad" class="com.myapp.Notepad" scope="protype"/>
3.2 使用会话和请求作用域
(1) 动态代理----生成基于接口的代理
(2)CGlib----声称基于类的代理
1)通过自动装配来声称类的代理
1 @Component 2 @Scope( 3 value= WebApplicationContext.SCOPE_SESSION, 4 proxyMode=ScopedProxyMode.INTERFACES) 5 public ShoppingCart cart(){...}
2)在XML中声明作用域代理
1 <bean id="cart" 2 class ="com.myapp.ShoppingCart" 3 scope="session"> 4 <aop:scoped-proxy/> 5 </bean>
生成基于接口的代理:
1 1 <bean id="cart" 2 class ="com.myapp.ShoppingCart" 3 scope="session"> 4 <aop:scoped-proxy proxy-target-class="false"/> 5 </bean>
为了使用<aop:scoped-proxy>元素,我们必须在XML配置中声明Spring的aop命名空间:也就是在.xml文件中药加入aop的命名空间。
3.3 运行时值注入---将一个值注入到bean的属性或者构造器参数中
(1) 传统方法是采用的硬编码
(2) 更好的办法是采用运行时值注入
两种方式:
1)属性占位符
2)Spring表达式语言(SpEL)
3.3.1 注入外部值
(1) 声明属性源并通过Spring的Enviroment来检索属性
(2) 解析属性占位符
1) XML
<bean id="sgtPeppers" class="soundsystem.BlankDisc" c:_titile="${disc.titile}" c:_artist="${disc.artist}"/>
为了使用占位符,我们必须要配置一个PropertyPlaceholderConfigurer bean或PropertySourcesPlaceholderConfigurer bean。从Spring3.1开始,推荐使用PropertySourcesPlaceholderConfigurer,因为它能够基于Spring Environment及其属性源来解析占位符。
相应的配置文件:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://www.springframework.org/schema/beans 5 http://www.springframework.org/schema/beans/spring-beans.xsd"> 6 7 <context:property-palceholder/> //该元素将会为你生成PropertySourcesPlaceholderConfiger bean 8 9 </beans>
2) 组件扫描和自动装配@value
public BlankDisc(
@value(“${disc.title}”)String title,
@value(“${disc.artist}”) String artist){
this.title = titile;
this.artist=artist;
}
相应的配置文件:
1 @Bean 2 public static PropertySourcesHolderConfigurer placeholderConfigurer(){
return new PropertySourcesPlaceholderConfigurer(); }
3.1.2 使用Spring表达式语言进行装配 #{...}
Spring 3引入了Spring表达式语言(Spring Expression Language,SpEL),它能够以一种强大和简洁的方式将值装配到bean属性和构造器参数中,在这个过程中所使用的表达式会在运行时计算得到值。
SpEL拥有很多特性,包括:
使用bean的ID来引用bean;
调用方法和访问对象的属性;
对值进行算术、关系和逻辑运算;
正则表达式匹配;
集合操作。
#{T(System).currentTimeMills() }:T()表达式会将java.lang.System视为Java中对应的类型,因此可以调用其static修饰的currentTimeMillis()方法。
SpEL表达式也可以引用其他的bean或其他bean的属性:#{sgtPeppers.artist}
1) 如果通过组件扫描创建bean的话,在注入属性和构造器参数时,我们可以使用@Value注解,这与之前看到的属性占位符非常类似。
1 public Blandisc{ 2 @Value("#{systemProperties['disc.title']}") String title 3 @Value("#{systemProperties['disc.artist']}") String title 4 5 this.title = title; 6 this.artist = artist; 7 8 }
2) 在XML配置中,你可以将SpEL表达式传入<property>或<constructor-arg>的value属性中,或者将其作为p-命名空间
或c-命名空间条目的值。
1 <bean id="sgtPeppers" 2 class="soundsystem.Blankdisc" 3 c:_titile="#{systemProperties['disc.title']}" 4 c:_artist="#{systemProperties['disc.artist']}" 5 >
spEL所支持的基础表达式:
1)表示字面值
2)引用bean、属性和方法
3)在表达式中使用类型 T()运算符
4)SpEL运算符
5)计算正则表达式
SpEL通过matches运算符支持表达式中的模式匹配。matches运算符对String类型的文本(作为左边参数)应用正则表达式(作为右边参数)。matches的运算结果会返回一个Boolean类型的值:如果与正则表达式相匹配,则返回true;否则返回false。
6)计算集合
7)查询运算符(.?[ ])
#{jukebox.songs.?[artist eq 'Aerosmith']}
.^[]:在集合中查询第一个匹配项
.$[]再结合中查询最后一个匹配项
.![] 投影运算符:从集合的每个成员中选择特定的属性放到另外一个集合中