spring装配bean(五)profile注解和解决自动注入的歧义性_z1340954953的博客-爱代码爱编程
配置profile bean
Spring为环境相关的bean所提供的解决方案其实和构建时候的方案没有太大区别,Spring会根据环境决定该创建那个bean和
不创建那个bean。
Spring的bean profile的功能。要使用profile,首先将所有不同的bean定义到一个或者多个profile之中,在将应用部署到每个环境中,要确保对应的profile处于激活(active)的状态
* Java配置中,使用@Profile注解指定某个bean属于那个profile
首先创建一个bean,用在开发环境和生产环境
@Configuration
@Profile("dev")
public class DevConfiguration {
@Bean
public HelloWorld helloWorld(){
return new HelloWorld("dev environment .....");
}
}
@Configuration
@Profile("prod")
public class ProductConfiguration {
public HelloWorld helloWorld(){
return new HelloWorld("product environment .... ");
}
}
从Spring3.2开始,profile注解可以用在方法上,上面改为
@Configuration
public class HellloConfiguration {
@Bean
@Profile("dev")
public HelloWorld devhelloWorld(){
return new HelloWorld("dev environment .....");
}
@Bean
@Profile("prod")
public HelloWorld prodhelloWorld(){
return new HelloWorld("product environment .....");
}
}
只有规定的profile被激活,对应的bean才会被创建,没有指定profile的Bean始终都会创建
* XML中配置profile
* Beans标签使用profile属性表示当前的bean的profile
<?xml version="1.0" encoding="UTF-8"?>
<beans profile="dev" 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-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd
">
<bean id="helloWorld" class="com.erong.service.HelloWorld">
<property name="message" value="dev bean ....."></property>
</bean>
</beans>
需要注意的是xsi:schemaLocation中定义的beans的xsd文件必须是3.1之后的,只有profile被激活并且属性值相同的bean才能创建
* 重复使用元素来指定多个profile
-- profile_dev.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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd
">
<context:annotation-config></context:annotation-config>
<beans profile="dev">
<bean id="helloWorld" class="com.erong.service.HelloWorld">
<constructor-arg value="dev hello world bean ...."></constructor-arg>
</bean>
</beans>
<beans profile="business">
<bean id="helloWorld" class="com.erong.service.HelloWorld">
<property name="message" value="business bean ....."></property>
</bean>
</beans>
<beans profile="prod">
<bean id="helloWorld" class="com.erong.service.HelloWorld">
<property name="message" value="prod bean ....."></property>
</bean>
</beans>
</beans>
所有的bean定义到一个xml文件中,定义了三个bean,但是运行时候只会创建一个bean,取决于处于激活状态的是profile
* 激活profile
Spring在确定那个profile处于激活状态,需要依赖两个独立的属性:spring.profiles.active和spring.profiles.default
如果设置了spring.profiles.active属性的话,它的值就会用来确定那个profile是激活的。
如果没有设置,将使用spring.profiles.default的值,两个属性都没有设置将会不激活profile
设置方式:
1. 作为DispatcherServlet的初始化参数
2. 作为Web应用的上下文参数
3. 作为JNDI条目
4. 作为环境变量
5. 作为JVM系统属性
<!-- 为上下文设置默认的profile -->
<context-param>
<param-name>spring.profiles.default</param-name>
<param-value>dev</param-value>
</context-param>
<!-- 为servlet设置默认profile -->
<servlet>
<servlet-name>myServlet</servlet-name>
<servlet-class>com.bing.servlet.MyServlet</servlet-class>
<init-param>
<param-name>spring.profiles.default</param-name>
<param-value>dev</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>myServlet</servlet-name>
<url-pattern>/app</url-pattern>
</servlet-mapping>
注意到,profile使用的都是复数形式,可以同时激活多个profile,使用逗号隔开
* 使用profile进行测试
当运行集成测试时,如果配置中的bean定义在profile中,那么在运行测试中,需要有一种方式来启用合适的profile
Spring提供了@ActiveProfiles注解,我们可以使用它指定运行测试时要激活那个profile。
测试类:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(value="classpath:profile_dev.xml")
@ActiveProfiles(value={"dev"})
public class CDPlayerTest {
@Autowired
private HelloWorld helloWorld;
@Test
public void test(){
System.out.println(
helloWorld.getMessage());
}
}
最后应该输出:
Your Message: dev hello world bean ....
测试过程中发现的问题:
1. 创建bean的时候,使用property元素对Bean进行赋值操作,调用的无参的构造器,并且调用的是对应的setter方法进行设值
2. 如果使用constructor-arg元素,必须存在相应参数的构造器方法创建Bean
* 条件化的bean
假设希望一个或多个bean只有在应用的类路径下包含特定的库时才创建。或者希望某个bean在另外某个特定的bean声明之后才会创建。Spring4之后,引入一个全新的注解@Conditional注解,它可以用到带有@Bean注解的方法上。
如果给定的条件计算结果为true,就创建这个bean
1. JavaConfig配置类
@Configuration
public class CDConfig {
@Bean
@Conditional(MagicExistCondition.class)
public CompactDisc sgtPeppers(){
return new SgtPeppers();
}
}
@Conditional将会根据类MagicExistCondition的matches方法返回的结果判断是否创建Bean
-- MagicExistCondition 实现接口 org.springframework.context.annotation.Condition
public class MagicExistCondition implements Condition {
/**
* 检查环境变量是否存在magic属性
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata arg1) {
Environment env = context.getEnvironment();
return env.containsProperty("magic");
}
}
对于matches方法中的ConditionContext接口,提供的方法我们能够检查
public interface ConditionContext {
/**
* 根据返回的BeanDefinitionRegistry,检查bean的定义
*/
BeanDefinitionRegistry getRegistry();
/**
* 借助返回的ConfigurableListableBeanFactory,检查bean是否存在
*/
ConfigurableListableBeanFactory getBeanFactory();
/**
* 返回的Environment,来确定环境变量是否存在及值是什么
*/
Environment getEnvironment();
/**
* 获取加载的资源
*/
ResourceLoader getResourceLoader();
/**
* 检查类是否存在
*/
ClassLoader getClassLoader();
}
AnnotatedTypeMetadata这个接口,能够检查@Bean注解方法上还存在其他注解
Note:@Profile注解底层使用到了@Conditional注解,并且引用ProCondition作为Condition接口的实现
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {
/**
* The set of profiles for which the annotated component should be registered.
*/
String[] value();
}
class ProfileCondition implements Condition {
@Override
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;
}
}
从源码看
1. 获取Profile 注解的所有属性的键值对
2. 检查当前环境中生效的Profile和当前Bean的Profile的value的值是否相同,相同,返回true,创建这个bean
@Profile在使用时候也会通过Conditon接口的实现类,判断Bean的profile是否处在激活状态,从而判断是否创建Bean。
* 处理自动装配的歧义性
如果存在多个类实现同一个接口,每个类都创建一个bean,自动注入的时候会选择哪个Bean,Spring无法判断就会抛出异常
解决:
1. 使用@Primary注解或者xml中primary = true ,标识Bean是优先选择的注入的
@Bean
@Primary
@Conditional(MagicExistCondition.class)
public CompactDisc sgtPeppers(){
return new SgtPeppers();
}
<bean id="helloWorld" class="com.erong.service.HelloWorld" primary="true">
<constructor-arg value="dev hello world bean ...."></constructor-arg>
</bean>
2. 使用@Qualifier注解限定装配的Bean和@Autowire注解或者@Inject注解搭配使用,值为Bean的ID
@Autowired
@Qualifier("cdplayer")
private CDPlayer cdplayer;
* 创建自定义的限定符
可以为bean设置自己的限定符,而不是依赖于将bean ID 作为限定符
> bean的声明上添加@Qualifier注解,设置Bean自己的限定符号
@Component
@Qualifier("compactDisc")
public class SgtPeppers implements CompactDisc {
@Bean
@Qualifier("compactDisc")
public SgtPeppers getInstance(){
return new SgtPeppers();
}
这样,创建的Bean的限定符号就为compactDisc,自动注入的时候@Qualifier使用这个限定符注入
note:
1. @Bean注解放在方法上,如果方法存在形参,会自动扫描匹配的bean自动注入
2. @Bean注解创建的Bean的限定符,可以通过@Qualifier指定
3. @Bean创建的Bean,Bean ID可以同时是指定的限定符,也可以是方法名
* Bean的自定义限定符号相同,需要自定自己的限定符注解
@Target({java.lang.annotation.ElementType.FIELD,
java.lang.annotation.ElementType.METHOD,
java.lang.annotation.ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Cold {
}