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.activespring.profiles.default . 如果设置了spring.profiles.active 属性的话,那么它的值就会用来确定哪个profile是激活的。但是如果没有设置spring.profiles.active的话,那spring将会查找 spring.profiles.default 的值如果spring.profiles.default 和 spring.profiles.active 的值均没设定时,那么久无法激活profile,只有那些没有定义在任何profile中的Bean才会会创建。

web.xml配置方式:

alt

idea配置方式:
修改环境变量方式:

alt

修改jvm系统参数方式:

alt

获取参数值:

alt

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进行装配。

装配顺序如下:

  1. 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常。
  2. 如果指定了name,则从上下文中查找名称(id)匹配的Bean进行装配,否则就抛出异常。
  3. 如果指定了type,则从上下文中查找类型匹配的唯一bean进行装配,找不到或者多个,都会抛出异常。
  4. 如果既没有指定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>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值