美文网首页
02.高级装配

02.高级装配

作者: apieceof2_d368 | 来源:发表于2020-04-08 11:23 被阅读0次

    配置 profile 的原因

    不同的环境有不同的配置, spring 需要根据需求的不同决定哪些需要创建哪些bean, 哪些不需要

    spring 在

    环境与Profile

    创建Profile

    不同的开发环境需要不同的配置, Spring 需要根据需求的不同决定哪些 Bean 需要创建, 哪些不需要. 所以需要配置文件, 决定配置.

    在 Spring 中使用 @Profile 指定某个 bean 属于哪一个 profile. 如果一个 bean 属于 dev profile, 那么只有在激活 dev profile 的时候, 这个 bean 才会创建.

    • 用 @Profile 修饰类
    @Configuration
    @Profile("dev")
    public class DevelopmentProfileConfig{
        @Bean
        public CDPlayer player(){
            return new CDPlayer(segPepper());
        }
        @Bean
        public SegPepper segPepper(){
            return new SegPepper();
        }
    }
    

    在上面这个配置文件中的 bean 只有在激活了 dev profile 的时候才会创建

    • 用 @Profile 修饰方法
    @Configuration
    public class ProductionProfileConfig{
        @Bean
        @Profile("prod")
        public CDPlayer player(){
            return new CDPlayer(segPepper());
        }
    }
    

    要注意没有创建 profile 的 bean 始终会被创建.

    激活Profile

    激活 Profile 用的两个属性

    • spring.profiles.active
    • spring.profiles.default

    这两个属性对于 profile 的影响是这样的

    1. 如果设置了 active属性, 那么 profile 由 active 决定
    2. 如果没有设置 active 属性, 那么 profile 由 default 决定
    3. 如果都没有, 那么生效的 bean 只有没有由 profile 管理的 bean

    条件化的Bean

    Spring4 支持 条件化创建 Bean. 比如我们希望某个 Bean 只有某个类存在的时候才创建, 或者要求只有某个变量存在的时候才创建某个 Bean

    @Bean
    @Conditional(MagicExistsCondition.class)
    public MagicBean magicBean(){
        return new MagicBean();
    }
    

    其中 MagicExistsCondition 实现了接口 Condition

    public class MagicExistsCondition implements Condition{
        public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata){
            Environment env = context.getEnvironment();
            return env.containsProperty("magic");
        }
    }
    

    这个 Condition 会检查是否存在属性 magic, 如果没有就不会创建 bean

    matches 有两个参数 ConditionContext contextAnnotatedTypeMetadata metadate

    • ConditionContext:
      • getRegistry() 返回BeanDefinitionRegistry检查 bean 定义
      • getBeanFactory() 返回ConfigurableListableBeanFactory检查 bean 是否存在, 探查 bean 的属性
      • getResourceLoader() 返回 ResourceLoader所加载的资源
      • getClassLoader() 返回的 ClassLoader加载并检查类是否存在
    • AnnotatedTypeMetadata: 能够检查带有 @Bean 注解的方法还有什么其他的注解, 也可以借助其中的 isAnnotated() 方法, 判断除了 @Bean 还有什么其他的注解

    首选的Bean

    如果在装配 Bean 的时候有两个或以上符合条件的 Bean, 那么会产生歧义, 报错. 可以在优先使用的bean 加上 @Primary 标签, 这样产生歧义的时候会优先使用这个组件

    // 可以用在组件类中
    @Component
    @Primary
    public class Component{}
    
    //也可以用在Bean上
    @Bean
    @Primary
    public Component component(){
        return new Component();
    }
    

    限定符修饰的Bean

    如前面所讲, 如果一个自动装配会试图尽量符合目标的注入. 如果没有符合的组件, 或者有多个符合的组件产生歧义就会报错.

    可以使用 @Qualifier 注解来给组件设定一个标识符

    @Autowired
    @Qualifier("code")
    public void setDessert(Dessert dessert){
        this.dessert = dessert;
    }
    

    这里, 会把一个标识符为 code 的组件注入到参数 dessert 中. 但是组件的默认标识符是它的 id, 可以用 @Qualifier 标签修改组件的标识符

    @Component
    // 也可以用在 @Bean 下面
    @Qualifier("code")
    public class IceCream implements Dessert{...}
    

    这里推荐标识符设置为组件的某个特性, 比如"冰激凌"是"code"的, 强调"一一对应"的注入关系不是什么好事情. 那么就又产出了一个新问题. 如果有多个组件有"code"特性呢? 这样不是又产出了歧义?

    也许可以给一个组件加上多个标识符

    @Component
    @Qualifier("code")
    @Qualifier("creamy")
    public class IceCream implements Dessert{...}
    

    但是 Java 不允许把一个注解多次使用在一个目标上. 解决方法是:

    @Targer({ElementType.CONSTRUCTOR, ElementType.Field,
            ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Qualifier
    public @interface Creamy{}
    

    这样我们就有了一个 @Creamy 注解, 同样声明一个 @Code 注解, 这样就可以同时使用两个标识符了

    @Component
    @Code
    @Creamy
    public class IceCream implements Dessert{...}
    
    @Autowired
    @Code
    @Creamy
    public void setDessert(Dessert dessert){
        this.dessert = dessert;
    }
    

    Bean 的作用域

    Spring 定义了多种定义域

    • 单例
    • 原型
    • 会话
    • 请求

    默认, 创建的组件都是单例, 如果要使用原型:

    @Component
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public class Notepad{...}
    
    //也可以使用@Scope("prototype"), 但是好像不如上面安全
    

    使用会话

    @Component
    @Scope(value=WebApplicationContext.SCOPE_SESSION, proxyMode=ScopeProxyMode.INTERFACES)
    public ShoppingCart cart(){...}
    

    第一个参数, 告诉 Spring 对于每一个 Web 会话, 都会创建一个 cart 会话. 第二个参数解决了单例和会话的配置的问题.

    假设现在有一个 StoreService 单例, 它负责结账购物车中的内容.

    @Component
    public class StoreService{
        @Autowired
        public void setShoppingCart(ShoppingCart cart){
            this.ShoppingCart = cart;
        }
    }
    

    但是单例在上下文加载的时候创建, 这个时候会话作用域的 cart 并不存在. 于是 cart 需要一个代理, 单例在注入 cart 的时候获得的是一个代理.

    运行时注入

    对于一般的组件, 我们可能会以这种方式装配

    @Bean
    public CompactDisc setPeppers(){
        return new BlankDisc(
            "set Pepper",
            "The Beatles"
        );
    }
    

    这里的问题就在于, 实例化 BlankDisc 过程的数据是硬编码在配置文件里的. 这也许不是很合适.

    这个时候就要通过注入外部的值尽量解决硬编码

    @Configuration
    @PropertySource("classpath:/com/soundsystem/app.properties")
    public class ExpressiveConfig {
        
        @Autowired
        Environment env;
        
        @Bean
        public BlankDisc disc() {
            return new BlankDisc(env.getProperty("disc.title"),
                                 env.getProperty("disc.artist"));
        }
    
    }
    

    在这里通过 @PropertySource() 标注导入了一个 app.properties 文件, 这个时候, 坏境会提供一个 Environment 组件. 将这个组件装配到 env 以后, 通过 getProperty 可以调用 app.properties 中的值

    disc.title="Set. Peppers"
    disc.artist="The Beaties"
    

    关于 getProperty 的一些重载

    • String getProperty(String key)

    • String getProperty(String key, String defaultValue): 如果没有 key , 那么设置为默认值

    • T getProperty(String key, Class<T> type) 如果希望得到的是数字:

      env.getProperty("db.connection.count", Integer.class);
      
    • T getProperty(String key, Class<T> type, 12) 相似, 不过有默认值

    属性占位符

    除去上面的方法, 还有一种使用属性占位符的方式配置方法.

    首先, 开启 PropertySourcePlaceholderConfigurer

        @Bean
        public
        static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
            return new PropertySourcesPlaceholderConfigurer();
        }
    

    然后, 组装 bean

        @Bean
        public BlankDisc disc(
                    @Value("${disc.title}") String title,
                    @Value("${disc.artist}")String artist
                ) {
            return new BlankDisc(title, artist);
        }
    

    这里用到了@Value注解, 接受一个占位符, 格式为 ${...}, 大括号中占位符

    相关文章

      网友评论

          本文标题:02.高级装配

          本文链接:https://www.haomeiwen.com/subject/vlgzphtx.html