欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring in action 4 剑指Spring - (三)高级装配

程序员文章站 2024-02-15 21:52:05
...

高级装配

环境与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配置方式:

Spring in action 4 剑指Spring - (三)高级装配

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

Spring in action 4 剑指Spring - (三)高级装配

修改jvm系统参数方式:

Spring in action 4 剑指Spring - (三)高级装配

获取参数值:

Spring in action 4 剑指Spring - (三)高级装配

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>