Spring Boot 条件化注解总结

Spring 4.0 引入的条件化注解

假设你希望一个或多个 bean 只有在应用的类路径下包含特定的库时才创建。或者我们希望某个 bean 只有当另外某个特定的 bean 也声明了之后才会创建。我们还可能要求只有某个特定的环境变量设置之后,才会创建某个 bean。
在 Spring 4 之前,很难实现这种级别的条件化配置,但是 Spring 4 引入了一个新的 @Conditional 注解,它可以用到带有 @Bean 注解的方法上。如果给定的条件计算结果为 true,就会创建这个 bean,否则的话,这个 bean 会被忽略。

@Conditional 注解的源码如下:

1
2
3
4
5
6
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}

Condition 接口的源码如下:

1
2
3
public interface Condition {
boolean matches(ConditionContext var1, AnnotatedTypeMetadata var2);
}

其中,通过 Condition 接口的入参 ConditionContext,我们可以做到如下几点:

  • 借助 getRegistry() 返回的 BeanDefinitionRegistry 检查 bean 定义;
  • 借助 getBeanFactory() 返回的 ConfigurableListableBeanFactory 检查 bean 是否存在,甚至探查 bean 的属性;
  • 借助 getEnvironment() 返回的 Environment 检查环境变量是否存在以及它的值是什么;
  • 读取并探查 getResourceLoader() 返回的 ResourceLoader 所加载的资源;
  • 借助 getClassLoader() 返回的 ClassLoader 加载并检查类是否存在。

环境与 profile

在开发软件的时候,有一个很大的挑战就是将应用程序从一个环境迁移到另外一个环境。开发阶段中,某些环境相关做法可能并不适合迁移到生产环境中,甚至即便迁移过去也无法正常工作。跨环境部署时会发生变化的几个典型例子:

  • 数据库配置
  • 加密算法
  • 与外部系统的集成

解决办法:

  • 在单独的 Java Config(或 XML)中配置每个 bean,然后在构建时根据不同的环境分别打包,典型方法是采用 Maven profile。这种方式的问题在于要为每种环境重新构建应用。
  • 运行时指定不同的环境变量,典型方法是采用 Spring profile bean。这种方式的好处在于无需为每种环境重新构建应用。

Spring profile bean 的使用方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Configuration
@Profile("dev") // 类级别(Spring 3.1 引入)
public class DevProfileConfig() {

}

@Configuration
public class GlobalConfig() {

@Bean
@Profile("dev") // 方法级别(Spring 3.2 引入)
public DataSource embeddedDataSource() {
...
}

@Bean
@Profile("prod") // 方法级别(Spring 3.2 引入)
public DataSource jndiDataSource() {
...
}

}

激活方式,使用属性:spring.profiles.activespring.profiles.default。有多种方式来设置这两个属性:

  • 作为 DispatcherServlet 的初始化参数;
  • 作为 Web 应用的上下文参数;
  • 作为 JNDI 条目;
  • 作为环境变量;
  • 作为 JVM 的系统属性;
  • 在集成测试类上,使用 @ActiveProfiles 注解设置。

注意,Spring profile bean 注解 @Profile 底层其实也是基于 @ConditionalCondition 实现:

1
2
3
4
5
6
7
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({ProfileCondition.class})
public @interface Profile {
String[] value();
}

Spring Boot 的条件化注解

Spring Boot 没有引入任何形式的代码生成,而是利用了 Spring 4 的条件化 bean 配置特性,以及 Maven 和 Gradle 提供的传递依赖解析,以此实现 Spring 应用上下文里的自动配置。Spring Boot 实现的条件化注解如下:

Spring Boot 实现的条件化注解

Spring Boot 的 @Conditional 注解实现及相关支持类,详见文档:Package org.springframework.boot.autoconfigure.condition

依赖配置:

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>

通过运行 DEBUG=true mvn spring-boot:run,可以看到 DEBUG 级别的日志输出,从而观察自动配置的详情(AUTO-CONFIGURATION REPORT):

  • Positive matches
  • Negative matches
  • Exclusions
  • Unconditional classes
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
============================
CONDITIONS EVALUATION REPORT
============================

Positive matches:
-----------------

GenericCacheConfiguration matched:
- Cache org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration automatic cache type (CacheCondition)

JmxAutoConfiguration matched:
- @ConditionalOnClass found required class 'org.springframework.jmx.export.MBeanExporter' (OnClassCondition)
- @ConditionalOnProperty (spring.jmx.enabled=true) matched (OnPropertyCondition)

PropertyPlaceholderAutoConfiguration#propertySourcesPlaceholderConfigurer matched:
- @ConditionalOnMissingBean (types: org.springframework.context.support.PropertySourcesPlaceholderConfigurer; SearchStrategy: current) did not find any beans (OnBeanCondition)


Negative matches:
-----------------

ActiveMQAutoConfiguration:
Did not match:
- @ConditionalOnClass did not find required class 'javax.jms.ConnectionFactory' (OnClassCondition)

AopAutoConfiguration:
Did not match:
- @ConditionalOnClass did not find required class 'org.aspectj.lang.annotation.Aspect' (OnClassCondition)

Exclusions:
-----------

None

Unconditional classes:
----------------------

org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration

org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration

org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration

参考

Spring in Action, 4th

SpringBoot源码分析之条件注解的底层实现