Spring in action 4 剑指Spring - (三)高级装配
高级装配
环境与Profile
在实际开发过程中,往往会将项目发布到不同的环境中,由于环境的不同,我们一般需要对在不同环境的数据和配置进行一些个性化的设定。比如,我们一般将开发到生产流程中的环境分为三种:开发(dev)、测试(test)和生产(prod)。而在这三种环境下,我们有些配置(如数据源)需要根据环境来切换。下面看看Spring中如何进行环境的个性化配置。
显示配置方式:
@Profile 来指定环境,可以修饰在类上和方法上。此处只演示修饰在类上的,表示当前类中所有的Bean都只能在dev环境下,才能实例化到IoC容器中。当修饰在方法上,与@Bean配合使用时,将用更加细粒度的控制某一个类在某种环境下才能创建出来
@Configuration
@Profile(value = "dev")
public class CompactConfig {
@Bean
public AdidasCompactDis adidasCompactDis(App app){
return new AdidasCompactDis();
}
@Bean
public App app(){
return new App();
}
@Bean
public AppUser appUser(){
return new AppUser(null);
}
}
@ActiveProfiles 注解来指定当前测试的环境。
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring.xml")
@ActiveProfiles(value = {"dev"})
public class AppTest {
@Autowired
private AdidasCompactDis adidasCompactDis;
@Test
public void shouldAnswerWithTrue() {
adidasCompactDis.palyCd();
}
}
xml配置方式:
profile=“dev” 设置Dev启动方式加载
<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"
xmlns:context="http://www.springframework.org/schema/context" xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd"
profile="dev">
<bean id="appTest" class="chendongdong.spring.test.bean.Test1.App"></bean>
<bean id="adidasCompactDis" class="chendongdong.spring.test.bean.Test2.AdidasCompactDis" ></bean>
</beans>
通过使用 标签中嵌套 标签,并且使用 标签的profile属性来指定环境。
<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"
xmlns:context="http://www.springframework.org/schema/context" xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<beans profile="dev">
<bean id="appTest" class="chendongdong.spring.test.bean.Test1.App"></bean>
<bean id="adidasCompactDis" class="chendongdong.spring.test.bean.Test2.AdidasCompactDis" ></bean>
</beans>
<beans profile="prod">
<bean id="appUser" class="chendongdong.spring.test.bean.Test1.AppUser">
<constructor-arg ref="appTest"></constructor-arg>
</bean>
<bean id="logger" class="chendongdong.spring.test.bean.Test1.XmlLoggerAspect"></bean>
</beans>
</beans>
**Profile环境的几种方式
Spring在确定哪个Profile处于**状态时,需要依赖两个独立的属性:spring.profiles.active 和 spring.profiles.default . 如果设置了spring.profiles.active 属性的话,那么它的值就会用来确定哪个profile是**的。但是如果没有设置spring.profiles.active的话,那spring将会查找 spring.profiles.default 的值,如果spring.profiles.default 和 spring.profiles.active 的值均没设定时,那么久无法**profile,只有那些没有定义在任何profile中的Bean才会会创建。
web.xml配置方式:
idea配置方式:
修改环境变量方式:
修改jvm系统参数方式:
获取参数值:
String property = System.getProperty("spring.profiles.active");
String getenv = System.getenv("spring.profiles.active");
System.out.println("ev = " + getenv + " -------- jvm = " + property);
ev = dev -------- jvm = prod
显示配置方式:
@ActiveProfiles
@RunWith(SpringJUnit4ClassRunner.class)
@ActiveProfiles(value = {"dev"})
public class AppTest {
@Autowired
private AdidasCompactDis adidasCompactDis;
@Test
public void shouldAnswerWithTrue() {
adidasCompactDis.palyCd();
}
}
条件化的Bean
场景:假设你希望一个或多个bean只有在应用的类路径下包含特定的库时才创建。或者我们希望某个bean只有当另外某个特定的bean也声明了之后才会创建。我们还可能要求只有某个特定的环境变量设置之后,才会创建某个bean。(手机和电池的关系,手机使用必须需要有电池。)
//手机
public class phone {
public void play(){
System.out.println("phone");
}
}
//电池
public class battery implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
try {
battery bean = conditionContext.getBeanFactory().getBean(battery.class);
if (bean == null){
return false;
}
return true;
} catch (Exception e){
return false;
}
}
public void play(){
System.out.println("battery");
}
}
配置类:
@Configuration
public class config {
@Bean
public battery battery(){
return new battery();
}
@Bean
@Conditional(battery.class)//判断battery的matches的返回值,如果是true则创建对象否则忽略不创建
public phone phone(){
return new phone();
}
}
@Conditional:
@Conditional 注解,他可以用到带有@Bean 注解的方法上,如果给定的条件计算结果为true,就会创建这个Bean,否则就会忽略这个Bean的创建。
Condition 接口:
public interface Condition {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
如果matches()方法返回true,那
么就会创建带有@Conditional注解的bean。如果matches()方法
返回false,将不会创建这些bean。
matches参数之ConditionContext
public interface ConditionContext {
BeanDefinitionRegistry getRegistry();
@Nullable
ConfigurableListableBeanFactory getBeanFactory();
Environment getEnvironment();
ResourceLoader getResourceLoader();
@Nullable
ClassLoader getClassLoader();
}
- 借助getRegistry()返回的BeanDefinitionRegistry检查bean定义;
- 借助getBeanFactory()返回的ConfigurableListableBeanFactory检查bean是否存在甚至探查bean的属性;
- 借助getEnvironment()返回的Environment检查环境变量是否存在以及它的值是什么;
- 读取并探查getResourceLoader()返回的ResourceLoader所加载的资源;
- 借助getClassLoader()返回的ClassLoader加载并检查类是否存在;
matches参数之AnnotatedTypeMetadata
public interface AnnotatedTypeMetadata {
boolean isAnnotated(String annotationName);
@Nullable
Map<String, Object> getAnnotationAttributes(String annotationName);
@Nullable
Map<String, Object> getAnnotationAttributes(String annotationName, boolean classValuesAsString);
@Nullable
MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName);
@Nullable
MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName, boolean classValuesAsString);
}
借助isAnnotated()方法,我们能够判断带有@Bean注解的方法是
不是还有其他特定的注解。借助其他的那些方法,我们能够检查
@Bean注解的方法上其他注解的属性。
@Profiles 注解也是使用了@Conditional 注解,并且引用ProfileCondition作为Condition实现,如下所示,ProfileCondition实现了Condition接口,并且在作出决策的过程中,考虑到了ConditionContext 和 AnnotatedTypeMetaData中的多个元素。
class ProfileCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) {
if (context.getEnvironment().acceptsProfiles(Profiles.of((String[]) value))) {
return true;
}
}
return false;
}
return true;
}
}
处理自动装配的歧义性:
-
@AutoWired 注解
默认是byType 进行装配的,如果在IoC容器中存在两个或两个以上的Bean类型一致时,Autowired会转变为byName进行装配,如果此时相同类型的Bean的ID存在0个或两个以两个以上与需要注入属性的名字一致,则会抛出
NoUniqueBeanDefinitionException
异常。如果此时需要装配Bean的ID与属性名不一致,可以配合@Qualifier
注解来标注。 -
@Resource 注解
@Resource其实与@Autowired的作用是一致的,都是用来装配Bean的,但是@Resource默认是byName来装配的,@Resource有两个比较 重要的属性,一个是name,一个是type,如果指定name,其实就是使用byName进行装配,如果指定type,则是使用byType进行装配。
装配顺序如下:
- 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常。
- 如果指定了name,则从上下文中查找名称(id)匹配的Bean进行装配,否则就抛出异常。
- 如果指定了type,则从上下文中查找类型匹配的唯一bean进行装配,找不到或者多个,都会抛出异常。
- 如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为类型匹配,如果匹配则自动装配。
-
@Qualifier注解
不仅可以与Autowired注解配置使用标注在属性,构造方法,set方法上,还可以标注在类上,如果一个类使用@Component注解标注,通过自动扫描产生的Bean的ID是类名的首字母小写的形式,而如果使用@Qualifier注解组合标注,只需要在注入的时候,选择合适的标注信息进行标注即可。
Bean的作用域
Bean的作用域分为四种:singleton, prototype, session, request.
- singleton:单例,默认,即每次从容器中获取的对象都是同一个
- prototype:多例(原型),即每次从容器中获取的对象都是不一样的。
- session:回话,用于web环境,一次session回话是同一个对象。
- request:请求,用于web环境,一次request请求是同一个对象。
java显示配置方式:
//singleton:单例
@Component
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
public class Cat implements Animal {
private String name;
private Integer age;
}
//prototype:多例
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class Dog implements Animal {
private String name;
private Integer age;
}
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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="com.ooyhao.spring.bean.Computer" scope="singleton"/>
<bean class="com.ooyhao.spring.bean.MobilePhone" scope="prototype"/>
</beans>