02.高级装配

2020-04-08  本文已影响0人  apieceof2_d368

配置 profile 的原因

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

spring 在

环境与Profile

创建Profile

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

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

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

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

@Configuration
public class ProductionProfileConfig{
    @Bean
    @Profile("prod")
    public CDPlayer player(){
        return new CDPlayer(segPepper());
    }
}

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

激活Profile

激活 Profile 用的两个属性

这两个属性对于 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

首选的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 的一些重载

属性占位符

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

首先, 开启 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注解, 接受一个占位符, 格式为 ${...}, 大括号中占位符

上一篇下一篇

猜你喜欢

热点阅读