Spring装配Bean(五)profile注解和解决自动注入的歧义性

配置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

  
  
  
  	
  
  

需要注意的是xsi:schemaLocation中定义的beans的xsd文件必须是3.1之后的,只有profile被激活并且属性值相同的bean才能创建

* 重复使用元素来指定多个profile

-- profile_dev.xml



    
	
		
			
		
	
	
		
			
		
	
	
		
			
		
	
  

所有的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系统属性


	
		spring.profiles.default
		dev
	
	
	
		myServlet
		com.bing.servlet.MyServlet
		
			spring.profiles.default
			dev
		
	
	
		myServlet
		/app
	

注意到,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 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();
	} 

			
		

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 {

}


你可能感兴趣的:(Spring)