Spring in Action --- 第三章 高级装配

Spring profile

使用Spring profile 可以设定在不同的环境中启用不同的bean.由于环境的不同,数据库配置,加密算法以及与外部系统的集成可能会有不同的表现,特别是数据库,一般开发环境,QA环境,预发布环境,生产环境会分开

Spring并不是在构建的时候做出这样的决策的,而是等到运行时再来确定

在java中

使用@Porfile注解指定某个bean属于哪一个profile,

@Profile("dev")

表示profile环境是dev.

只有相应的profile激活的时候,才会创建对应的bean,但是,没有指定profile的bean始终都会被创建,与激活哪个profile没有关系

在XML中

<beans xmlns="http://springframework.org/schema/beans"
		xmlns:...
 profile="dev">
 </beans>

创建不同的XML文件设置不同的profile,也可以在根<bean>元素中嵌套定义<beans>元素,而不是为每一个环境创建一个profile XML文件.

激活 profile

Spring在确定哪个profile处于激活状态时,需要依赖两个独立的属性:spring.profiles.activespring.profiles.default,如果设置了 spring.profiles.active 属性,那么它的值就会用来确定哪个profile是激活的,但是如果没有设置,那 Spring会查找spring.profiles.defalut的值.如果两者均为设置,那么就没有激活的profile,Spring只会创建哪些没有定义在profile中的bean.

设置属性的方式
  • 作为 DispatcherServlet 的初始化参数
  • 作为 Web 应用的上下文参数
  • 作为 JNDI 条目
  • 作为环境变量
  • 作为JVM的系统属性

示例:
在web.xml中为上下文设置默认的profile

<context-param>
	<param-name>spring.profiles.default</param-name>
	<param-value>dev</param-value>
</context-param>

在web.xml中为Servlet设置默认的profile

<servlet>
	<servlet-name>appServlet</servlet-name>
	<servlet-class>
		prg.springframework.web.servlet.DispatcherServlet
	</servlet-class>
	<init-param>
		<param-name>spring.profiles.default</param-name>
		<param-value>dev</param-value>
	</init-param>
</servlet>

使用profile进行测试

Spring提供了@ActiveProfiles注解指定运行测试时要激活哪个profile.

条件化的 bean

Spring4以后,引入了@Conditional,可以用到带有@Bean注解的方法上,用于计算给定的条件如果为true,就会创建这个bean,否则的话这个bean会被忽略
例如有一个Test的类,我们希望只有设置了 test 环境属性的时候,Spring才会实例化这个类,如果没有,就会被忽略.下面的配置展示了注解的使用方式

@Bean
@Conditional(TestExistsCondition.class)
public Test test() {
	return new Test();
}

@Condition中给定了一个Class,指明创建该bean的条件,设置给它的类可以是任意实现了Condition接口的类型.下面展现了TestExistsCondition类的实现

public class TestExistsCondition() {
	public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
	Environment env = context.getEnvironment();
	return env.containsProperty("test");
	}
}

其中的两个参数 ConditionContext, AnnotatedTypeMetadata 都是接口,具体请看P77

处理自动装配的歧义性

在使用自动装配时,如果一个接口有多个实现类并且都被定义为bean,当Spring试图装配的时候就会无法做出选择从而抛出 NoUniqueBeanDefinitionException, Spring提供了两种方式来解决这样的问题:

  • 将可选bean的某一个设置为首选的bean
  • 使用限定符(qualifier)来帮助Spring将可选的bean的范围缩小到只有一个bean

标示首选的bean

在 bean 上使用@Primary注解标示当前的bean是首选bean,如果你是用XML来实例化bean
,可以用下面的方式:

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

但是,如果你设置了两个或者更多的首选bean,那么它就无法正常工作了

限定自动装配的bean

@Qualifier注解是使用限定符的主要方式:

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

上面的代码片段是最简单的例子,为@Qualifier注解所设置的参数就是想要注入的bean的ID,所有的@Component注解创建的类的ID都是首字母变为小写的类名.需要注意的是,如果没有指定其他的限定符的话,所有的bean都会给定一个默认的限定符,这个限定符与bean的ID相同

这里有个问题就是,指定的限定符与要注入的bean的名称是紧耦合的,如果bean的名字修改了,那么就会无法注入,解决方案是在bean上使用@qualifier指定该bean的限定符

使用自定义的限定符注解

如果iceCream同时被使用在两个或更多的bean上,那怎么办呢?你可能会想到的是使用更多的限定符来缩小范围:

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

这里只有一个小问题,Java不允许在同一个条目上重复出现相同类型的多个注解.如果你这么做了,编译将会报错.但是,我们可以创建自定义的限定符注解:

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

上面的代码片段定义了一个自定义注解,它本身要使用@Qualifier来标注,这样就可以不断缩小范围,直到唯一.

bean 的作用域

在默认情况下,Spring应用上下文中所有的bean都是作为以单利的形式创建的,不过,Spring提供了多种作用域,可以基于这些作用域创建bean:

  • 单例:在整个应用中,只创建bean的一个实例
  • 原型:每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例
  • 会话:在Web应用中,为每个会话创建一个bean实例
  • 请求:在Web应用中,为每个请求创建一个bean实例

默认是单例模式,如果选择其他的作用域,要使用@Scope注解,例如,要将一个bean生命为原型,需要这样做:

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

使用组件扫描来发现和声明bean,只需要加上@Component注解替换@Bean,同样,如果使用XML来配置bean的话,可以使用scope属性来设置作用域:

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

对于会话作用域,在当前会话的相关操作中,bean实际上是单例的:

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

要注意的是,同时还有一个proxyMode属性被设置,请看下面的代码:

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

这个service是一个单例的bean,当它创建的时候,Spring会试图将ShoppingCart bean注入,但是ShoppingCart是会话作用域的,此时并不存在,知道某个用户进入到系统才会出现实例,另外,系统中将会有多个ShoppingCart实例被创建,我们希望注入的bean刚好是当前会话对应的那一个,因此,Spring会向StroeService中注入一个到ShoppingCart的代理.关于具体的描述,请看P87

运行时注入

在代码中,最好的方式是不要在代码中出现硬代码,因此我们需要借助Spring的一些配置来注入外部的值:

@Configuration
@PropertySource("")
public class ExpressiveConfig {
	@Autowired
	Environment env;
	
	@Bean
	public BlankDisc disc() {
		return new BlankDisc(env.getProperty("disc.title"));
	}
}

我们使用了@ PropertySource注解引用了外部的一个属性文件,其中有disc.title属性,会被读取并配置到bean中,如果这个属性没有被定义,获取到的值会是null,如果你希望这个属性必须被定义,可以使用getRequiredProperty()方法.

另外,可以使用Environment.containsProperty()方法来判断是否有某一个属性.最后,如果想将属性解析为类的话,可以使用getPropertyAdClass()方法.

如果我们依赖组件扫描和自动装配来创建和初始化应用组件的话,娜美可以使用@Value注解,它与@Autowired非常相似:

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

为了使用占位符,我们必须要配置一个PropertyPlaceHolderConfigurer bean或 PropertySourcePlaceHolderConfigurer bean,具体请看P92

注解

@Profile

Spring 3.2以后,注解支持在类和方法级别上使用
@Profile("...")括号中可以定义任意的字符串,但建议用有意义的字符串

@ActiveProfiles

@Conditional

@Primary

@Qualifier

@Scope

@PropertySource

posted @ 2017-01-17 18:58  吹哔战将  阅读(212)  评论(0编辑  收藏  举报