zoukankan      html  css  js  c++  java
  • Spring装配Bean的一些高级技巧

    一、使用@Profile注解来实现在不同环境下创建不同的Bean

    • 实现方式:将不同的Bean定义整理到对应环境的Profile中,当应用部署到不同的环境时(开发环境或者是QA环境或者是生产环境),激活对应的Profile,则相应环境的Bean就会在运行时被创建,非当前环境的Profile不会被创建,没有指定@Profile注解的Bean始终会被创建。
    • @Profile注解可以用在类级别上或者方法级别上。

    举例:

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Profile;
    import org.springframework.jdbc.datasource.DriverManagerDataSource;
    
    import javax.sql.DataSource;
    
    @Configuration
    public class DataSourceProfiles {
        @Bean
        @Profile("development")
        public DataSource deveDataSource(){
            DriverManagerDataSource dataSource = new DriverManagerDataSource();
            dataSource.setDriverClassName("com.mysql.jdbc.Driver");
            dataSource.setUrl("jdbc:mysql://11.11.11.11:3306/demodb");
            dataSource.setUsername("root");
            dataSource.setPassword("123456");
            return dataSource;
        }
    
        @Bean
        @Profile("qa")
        public DataSource qaDataSource(){
            DriverManagerDataSource dataSource = new DriverManagerDataSource();
            dataSource.setDriverClassName("com.mysql.jdbc.Driver");
            dataSource.setUrl("jdbc:mysql://11.11.11.11:3306/demodb");
            dataSource.setUsername("root");
            dataSource.setPassword("123456");
            return dataSource;
        }
    
        @Bean
        @Profile("product")
        public DataSource productDataSource(){
            DriverManagerDataSource dataSource = new DriverManagerDataSource();
            dataSource.setDriverClassName("com.mysql.jdbc.Driver");
            dataSource.setUrl("jdbc:mysql://11.11.11.11:3306/demodb");
            dataSource.setUsername("root");
            dataSource.setPassword("123456");
            return dataSource;
        }
        
        @Bean
        public SomeOtherBean getBean(){
            return new SomeOtherBean();
        }
    }
    View Code

    上面的例子中,“development”这个Profile激活时,会创建development环境的Bean和SomeOtherBean,其他两个Bean不会被创建。

    • 激活某个Profile的方式

    Spring有两个独立的属性来确定哪个Profile被激活:spring.profile.active和spring.profile.default,如果设置了spring.profile.active属性,则它的值用来确定激活的Profile,如果没有设置spring.profile.active,但是设置了spring.profile.default,则spring.profile.default的值用来确定激活的Profile,如果spring.profile.active和spring.profile.default均没有设置,则没有激活的Profile,此时只会创建哪些没有定义在Profile中的Bean。有多种方式来定义这两个属性:

    1. 作为DispatcherServlet的初始化参数;
    2. 作为Web应用上下文的参数;
    3. 作为环境变量;
    4. 作为JNDI条目;
    5. 作为JVM的系统属性;
    6. 在集成测试时,使用@ActiveProfile注解设置;

    二、条件化的装配Bean

    可以设置不同的条件来控制Bean的创建:

    • 只有在某个特定环境变量设置之后,才创建某个Bean;
    • 应用的类路径下包含特定的库才创建某个Bean(这也是SpringBoot的自动化装配的实现方式,当引入了某个特定依赖时,相应的Bean就会被自动创建);
    • 只有某个Bean被创建后,才会创建另一个Bean;

    举例:当环境变量中设置了magc属性时,才创建MagicExistCondition这个Bean,进一步的,只有MagicExistCondition创建后,才创建MagicBean这个Bean,具体实现方式如下:

    import org.springframework.context.annotation.Condition;
    import org.springframework.context.annotation.ConditionContext;
    import org.springframework.core.env.Environment;
    import org.springframework.core.type.AnnotatedTypeMetadata;
    
    public class MagicExistCondition implements Condition {
        public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata){
            Environment env = context.getEnvironment();
            return env.containsProperty("magic");
        }
    }
    View Code

    上述代码中MagicExistCondition实现了Condition接口,只有实现了这个接口的类,才可以作为条件类,用在@Conditional注解中。Condition这个接口很简单,如上面的例子,只需要实现matches方法即可。

    接下来,判断MagicExistCondition被创建后,才创建MagicBean这个Bean:

        @Bean
        @Conditional(MagicExistCondition.class)
        public MagicBean magicBean(){
            reutrn new MagicBean();
        }
    View Code

    再次看看Condition接口中的matche方法,这个方法有两个参数:ConditionContext和AnnotatedTypeMetadata。

    ConditionContext是一个接口:

    package org.springframework.context.annotation;
    
    import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
    import org.springframework.beans.factory.support.BeanDefinitionRegistry;
    import org.springframework.core.env.Environment;
    import org.springframework.core.io.ResourceLoader;
    
    public interface ConditionContext {
        BeanDefinitionRegistry getRegistry();
    
        ConfigurableListableBeanFactory getBeanFactory();
    
        Environment getEnvironment();
    
        ResourceLoader getResourceLoader();
    
        ClassLoader getClassLoader();
    }
    View Code
    • getRegistry()方法返回的BeanDefinitionRegistry可以检查Bean的定义
    • getBeanFactory()方法返回的ConfigurableListableBeanFactory可以检查Bean是否存在,甚至进一步探查Bean的属性
    • getEnvironment()方法返回的Environment检查环境变量是否存在以及它的值是什么
    • getResourceLoader()方法返回的ResourceLoader用于探查所加载的资源
    • getClassLoader()方法返回的ClassLoader用于加载并检查类是否存在

    AnnotatedTypeMetadata也是接口:

    import java.util.Map;
    import org.springframework.util.MultiValueMap;
    
    public interface AnnotatedTypeMetadata {
        boolean isAnnotated(String var1);
    
        Map<String, Object> getAnnotationAttributes(String var1);
    
        Map<String, Object> getAnnotationAttributes(String var1, boolean var2);
    
        MultiValueMap<String, Object> getAllAnnotationAttributes(String var1);
    
        MultiValueMap<String, Object> getAllAnnotationAttributes(String var1, boolean var2);
    }
    View Code

    该接口主要用来探查带有@bean注解的类上面是否还有其他的注解,并且检查那些注解的属性值。举个例子,回到@Profile这个注解,这个注解用于控制当前@bean注解的类在特定Profile激活时才被创建,那么,@Profile注解是如何实现这个功能的呢,这里就需要借助AnnotatedTypeMetadata这个接口了,从Spring 4开始,@Profile注解基于@Conditional和Condition来实现:

    首先看看@Condtion注解:

    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.TYPE, ElementType.METHOD})
    @Documented
    @Conditional({ProfileCondition.class})
    public @interface Profile {
        String[] value();
    }
    View Code

    其中的ProfileCondition类实现了Condition接口的matches方法:

    package org.springframework.context.annotation;
    
    import java.util.Iterator;
    import java.util.List;
    import org.springframework.core.type.AnnotatedTypeMetadata;
    import org.springframework.util.MultiValueMap;
    
    class ProfileCondition implements Condition {
        ProfileCondition() {
        }
    
        public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
            if (context.getEnvironment() != null) {
                MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
                if (attrs != null) {
                    Iterator var4 = ((List)attrs.get("value")).iterator();
    
                    Object value;
                    do {
                        if (!var4.hasNext()) {
                            return false;
                        }
    
                        value = var4.next();
                    } while(!context.getEnvironment().acceptsProfiles((String[])((String[])value)));
    
                    return true;
                }
            }
    
            return true;
        }
    }
    View Code

    可以看到:matches方法中,首先给metadata.getAllAnnotationAttributes()方法传递Profile.class.getName()这个Profile Bean的名字,得到所有注解的属性,并逐个遍历判断,借助acceptsProfiles方法来见擦好该Profile是否被激活!!!

    三、处理自动装配的歧义性

    当一个接口有多个实现类时,如果某个类需要注入接口类,此时会抛出NoUniqueBeanDefinitionException异常,解决方法是:

    1. 使用@Primary标注首选的Bean
    2. 使用@Qualifier注解@Qualifier(“specialBean”)限定符
    3. 创建自定义的限定符注解
  • 相关阅读:
    Codeforces 1009F Dominant Indices
    C++之++运算符重载问题
    Codeforces 1010D Mars rover
    这是一个开始
    MoreEffectiveC++Item35(异常)(条款9-15)
    C++隐式类类型转化
    MoreEffectiveC++Item35(操作符)(条款5-8)
    MoreEffectiveC++Item35(基础议题)(条款1-4)
    php+mysql网站无限级栏目分类-递归获取树形结构函数
    JavaScript简易动画
  • 原文地址:https://www.cnblogs.com/zheng-hong-bo/p/11061991.html
Copyright © 2011-2022 走看看