1. 基于环境配置 Profile

1.1 概念

地址:@Profile

Indicates that a component is eligible for registration when one or more specified profiles are active.

A profile is a named logical grouping that may be activated programmatically via ConfigurableEnvironment.setActiveProfiles(java.lang.String…) or declaratively by setting the spring.profiles.active property as a JVM system property, as an environment variable, or as a Servlet context parameter in web.xml for web applications. Profiles may also be activated declaratively in integration tests via the @ActiveProfiles annotation.

指示当一个或多个指定的概要文件处于活动状态时,组件有资格注册。

概要文件命名逻辑分组,可能被激活以编程方式通过ConfigurableEnvironment.setActiveProfiles声明方式(以…)或通过设置spring.profiles.active财产作为一个JVM系统属性,作为一个环境变量,或者作为web应用程序的web . xml中的Servlet上下文参数。概要文件也可以通过@ActiveProfiles注释在集成测试中声明性地激活。

其实听起来是有点蒙的,上文的意思就是说,当你用 @Profile 注解标注一个组件的时候,直到某个你配置好的属性把它激活了,这个属性可以通过(启动参数、环境、web.xml 等)

其实就可以理解为,根据运行时环境不同,动态的选择注册当前环境应该注入的组件。

1.2 使用

实体

  1. public class PartA {
  2. private String name;
  3. public PartA(String name) {
  4. this.name = name;
  5. }
  6. }

配置类

  1. @Configuration
  2. @Profile("dev")
  3. public class MyModuleConfiguration {
  4. @Bean
  5. public PartA zhangsan() {
  6. return new PartA("张三");
  7. }
  8. @Bean
  9. public PartA lisi() {
  10. return new PartA("李四");
  11. }
  12. }

测试类

  1. public class Test {
  2. public static void main(String[] args) {
  3. AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); Stream.of(context.getBeanDefinitionNames()).forEach(System.out::println);
  4. }
  5. }

输出

  1. org.springframework.context.annotation.internalConfigurationAnnotationProcessor
  2. org.springframework.context.annotation.internalAutowiredAnnotationProcessor
  3. org.springframework.context.annotation.internalCommonAnnotationProcessor
  4. org.springframework.context.event.internalEventListenerProcessor
  5. org.springframework.context.event.internalEventListenerFactory

可以看到 @Profile(“dev”) 标注在配置类上,但是输出结果却没有对应的配置类和张三李四这两个 bean。

如果想在其中使用它,就必须在 ApplicationContext 中进行配置,即你想获取哪些环境下的类

  1. public class Test {
  2. public static void main(String[] args) {
  3. AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
  4. context.getEnvironment().setActiveProfiles("dev");
  5. context.register(MyModuleConfiguration.class);
  6. context.refresh();
  7. Stream.of(context.getBeanDefinitionNames()).forEach(System.out::println);
  8. }
  9. }

输出

  1. org.springframework.context.annotation.internalConfigurationAnnotationProcessor
  2. org.springframework.context.annotation.internalAutowiredAnnotationProcessor
  3. org.springframework.context.annotation.internalCommonAnnotationProcessor
  4. org.springframework.context.event.internalEventListenerProcessor
  5. org.springframework.context.event.internalEventListenerFactory
  6. myModuleConfiguration
  7. zhangsan
  8. lisi

还有一种办法就是在 VM Options 中配置 -Dspring.profiles.active=dev这样的字样,也能达到效果。
所以通过这个注解,我们就能实现不同环境下的独有配置效果了,例如配置常见的 dev、pre、prod 等等。

2. 条件装配 conditional

2.1 概念

上面你的 profile 更适合对整个项目的运行环境进行管理,但是不能根据具体的一个 Bean 的一些条件因素决定是否装配,这也就是条件装配的来由。
地址: @Conditional

Indicates that a component is only eligible for registration when all specified conditions match. A condition is any state that can be determined programmatically before the bean definition is due to be registered (see Condition for details). The @Conditional annotation may be used in any of the following ways:

  • as a type-level annotation on any class directly or indirectly annotated with @Component, including @Configuration classes
  • as a meta-annotation, for the purpose of composing custom stereotype annotations
  • as a method-level annotation on any @Bean method

If a @Configuration class is marked with @Conditional, all of the @Bean methods, @Import annotations, and @ComponentScan annotations associated with that class will be subject to the conditions.

表示组件只有在所有指定条件都匹配的情况下才有资格注册。 条件是可以在注册bean定义之前以编程方式确定的任何状态(有关详细信息,请参阅condition)。 @Conditional注释可以通过以下任何一种方式使用: 作为任何直接或间接用@Component注释的类的类型级注释,包括@Configuration类 作为元注释,用于编写定制的原型注释 作为任何@Bean方法的方法级注释 如果@Configuration类被标记为@Conditional,那么与该类关联的所有@Bean方法、@Import注释和@ComponentScan注释都将受到这些条件的约束。

2.2 基本使用

先创建一些基本的铺垫

  1. public class PartA {
  2. private String name;
  3. public PartA(String name) {
  4. this.name = name;
  5. }
  6. }
  7. public class PartB {
  8. private String name;
  9. public PartB(String name) {
  10. this.name = name;
  11. }
  12. }

配置类的 Bean 实例化上增加 @Conditional

  1. @Configuration
  2. public class MyModuleConfiguration {
  3. @Bean
  4. public PartB lisi() {
  5. return new PartB("李四");
  6. }
  7. @Bean
  8. @Conditional(MyCondition.class)
  9. public PartA zhangsan() {
  10. return new PartA("张三");
  11. }
  12. }

可以看到 PartA 上面增加了条件配置的注解,而且指定了规则类,我们来实现一下这个规则类

  • 其实前面几句都是我的测试代码,真正有用的只有 return 中依据,即比较当前的上下文工厂中是否已经存在了某个指定的内容。
  • 大家可以注意到,我在上面的 MyModuleConfiguration 配置类中,特别把 lisi 这个 Bean 放在了前面,大家可以自己调整位置,看看是否有效。

    1. public class MyCondition implements Condition {
    2. @Override
    3. public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    4. String[] beanDefinitionNames = context.getBeanFactory().getBeanDefinitionNames();
    5. for (String beanDefinitionName : beanDefinitionNames) {
    6. System.out.println(beanDefinitionName);
    7. }
    8. System.out.println(PartB.class.getName());
    9. // 重点就这一句,上面都是测试
    10. return Objects.requireNonNull(context.getBeanFactory()).containsBeanDefinition("lisi");
    11. }
    12. }

    测试

    public class Test {
      public static void main(String[] args) {
          AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyModuleConfiguration.class);
          Stream.of(context.getBeanDefinitionNames()).forEach(System.out::println);
      }
    }
    

    输出

    org.springframework.context.annotation.internalConfigurationAnnotationProcessor
    org.springframework.context.annotation.internalAutowiredAnnotationProcessor
    org.springframework.context.annotation.internalCommonAnnotationProcessor
    org.springframework.context.event.internalEventListenerProcessor
    org.springframework.context.event.internalEventListenerFactory
    myModuleConfiguration
    lisi
    zhangsan
    

    2.3 通用匹配方式

    如果一个组件依赖了很多个 Bean ,那么我们为每个条件都去写一个条件规则的类,这也太麻烦了,我们应该想想批量的方式

我们先自定义一个注解

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnBean {

    Class<?>[] value() default {};

    String[] beanNames() default {};
}

规则

public class OnBeanCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnBean.class.getName());
        // 匹配类型
        assert attributes != null;
        Class<?>[] classes = (Class<?>[]) attributes.get("value");
        for (Class<?> clazz : classes) {
            if (!Objects.requireNonNull(context.getBeanFactory()).containsBeanDefinition(clazz.getName())) {
                return false;
            }
        }
        // 匹配beanName
        String[] beanNames = (String[]) attributes.get("beanNames");
        for (String beanName : beanNames) {
            if (!Objects.requireNonNull(context.getBeanFactory()).containsBeanDefinition(beanName)) {
                return false;
            }
        }
        return true;
    }
}

配置类中就可以根据 beanNames 或者类的类型决定条件了。

@Configuration
public class MyModuleConfiguration {
    @Bean
    public PartB lisi() {
        return new PartB("李四");
    }

    @Bean
    @ConditionalOnBean(beanNames = "lisi")
    public PartA zhangsan() {
        return new PartA("张三");
    }
}