参考文献

@Conditional注解

1
2
3
4
5
6
7
8
9
10
11
12
13
// org.springframework.context.annotation.Conditional
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {

/**
* All {@link Condition} classes that must {@linkplain Condition#matches match}
* in order for the component to be registered.
*/
Class<? extends Condition>[] value();

}
  • @Conditional注解是从Spring 4.0才有的,可以在任何类型或方法上面标注.通过@Conditional注解可以配置一些条件判断,当所有条件都满足的时候被标注的对象才会被Spring容器处理.
  • @Conditional注解实现的原理是通过org.springframework.context.annotation.Condition这个接口判断是否应该执行操作.

Condition接口

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
package org.springframework.context.annotation;

import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.core.type.AnnotatedTypeMetadata;

/**
* 略...
* @author Phillip Webb
* @since 4.0
* @see ConfigurationCondition
* @see Conditional
* @see ConditionContext
*/
@FunctionalInterface
public interface Condition {

/**
* Determine if the condition matches.
* @param context the condition context
* @param metadata the metadata of the {@link org.springframework.core.type.AnnotationMetadata class}
* or {@link org.springframework.core.type.MethodMetadata method} being checked
* @return {@code true} if the condition matches and the component can be registered,
* or {@code false} to veto the annotated component's registration
*/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);

}
  • @Conditional注解判断条件与否取决于value属性指定的Condition实现,其中matches方法,返回true表示条件成立,反之不成立.

  • matches中的两个参数如下:

    • context: 条件上下文,ConditionContext接口类型的,可以用来获取容器中上下文信息.
    • metadata:用来获取被@Conditional标注的对象上的所有注解信息.

ConditionContext接口

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
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;
import org.springframework.lang.Nullable;

/**
* Context information for use by {@link Condition} implementations.
*
* @author Phillip Webb
* @author Juergen Hoeller
* @since 4.0
*/
public interface ConditionContext {

/**
* Return the {@link BeanDefinitionRegistry} that will hold the bean definition
* should the condition match.
* @throws IllegalStateException if no registry is available (which is unusual:
* only the case with a plain {@link ClassPathScanningCandidateComponentProvider})
* 返回Bean定义注册器,可以通过注册获取Bean定义的各种信息
*/
BeanDefinitionRegistry getRegistry();

/**
* Return the {@link ConfigurableListableBeanFactory} that will hold the bean
* definition should the condition match, or {@code null} if the bean factory is
* not available (or not downcastable to {@code ConfigurableListableBeanFactory}).
* 返回ConfigurableListableBeanFactory类型的Bean工厂,相当于IoC容器对象
*/
@Nullable
ConfigurableListableBeanFactory getBeanFactory();

/**
* Return the {@link Environment} for which the current application is running.
* 返回当前Spring容器的环境配置信息对象
*/
Environment getEnvironment();

/**
* Return the {@link ResourceLoader} currently being used.
* 返回资源加载器
*/
ResourceLoader getResourceLoader();

/**
* Return the {@link ClassLoader} that should be used to load additional classes
* (only {@code null} if even the system ClassLoader isn't accessible).
* @see org.springframework.util.ClassUtils#forName(String, ClassLoader)
* 返回类加载器
*/
@Nullable
ClassLoader getClassLoader();

}

Condition条件判断的时间点

  • 条件判断的执行分为两个阶段:
    • 配置类解析阶段(ConfigurationPhase.PARSE_CONFIGURATION): 在这个阶段会得到一批配置类的信息和一些需要注册的Bean.
      • 配置类:类上被标注@Component,@ComponentScan,@Import,@ImportResource,@Configuration以及类中方法有@Bean的方法.
        • 也可以通过org.springframework.context.annotation.ConfigurationClassUtils#isConfigurationCandidate来判断
    • Bean注册阶段(ConfigurationPhase.REGISTER_BEAN): 将配置类解析阶段得到的配置类和需要注册的Bean注入到容器中.
    • Condition默认会对2个过程都有效
  • 默认都是配置解析阶段,在SpringBoot中使用了ConfigurationCondition这个接口可以自定义执行阶段,比如@ConditionOnMissingBean都是在Bean注册阶段执行,因为需要从容器中判断Bean.
  • 两个阶段的区别:
    • 配置类解析阶段只是将需要加载的配置类和一些Bean(被@Conditional注解过滤掉之后)收集起来,
    • 而Bean注册阶段是将收集来的Bean和配置类注入到容器中,如果在配置类解析阶段执行Condition接口的matches方法区判断某些Bean是否存在IOC容器中,这个显然是不行的,因为这些Bean还没有注册到容器中.

@Conditional使用的3步骤

  • 自定义一个类,实现Condition或ConfigurationCondition接口,实现matches方法
  1. 在目标对象上使用@Conditional注解,并指定value的指为自定义的Condition类型
  2. 启动Spring容器加载资源,此时@Conditional就会起作用了

Spring对配置类处理过程

  • 通常我们会通过new AnnotationConfigApplicationContext()传入多个配置类来启动Spring容器

  • Spring对传入的多个配置类进行解析

  • 配置类解析阶段:这个过程就是处理配置类上面6种(@Component,@ComponentScan,@Import,@ImportResource,@Configuration以及类中方法有@Bean的方法)注解的过程,此过程中又会发现很多新的配置类,比如@Import导入的一批新的类刚好也符合配置类,而被@CompontentScan扫描到的一些类刚好也是配置类;此时会对这些新产生的配置类进行同样的过程解析

  • Bean注册阶段:配置类解析后,会得到一批配置类和一批需要注册的bean,此时Spring容器会将这批配置类作为bean注册到Spring容器,同样也会将这批需要注册的bean注册到Spring容器

  • 经过上面第3个阶段之后,Spring容器中会注册很多新的bean,这些新的bean中可能又有很多新的配置类

  • Spring从容器中将所有bean拿出来,遍历一下,会过滤得到一批未处理的新的配置类,继续交给第3步进行处理

  • Step3到Step6,这个过程会经历很多次,直到完成所有配置类的解析和bean的注册

  • 从上面过程中可以了解到:

    1. 可以在配置类上面加上@Conditional注解,来控制是否需要解析这个配置类,配置类如果不被解
      析,那么这个配置上面6种注解的解析都会被跳过

    2. 可以在被注册的bean上面加上@Conditional注解,来控制这个bean是否需要注册到Spring容器中

    3. 如果配置类不会被注册到容器,那么这个配置类解析所产生的所有新的配置类及所产生的所有新的
      bean都不会被注册到容器

源码

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
/**
* Build and validate a configuration model based on the registry of
* {@link Configuration} classes.
*/
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
String[] candidateNames = registry.getBeanDefinitionNames();

for (String beanName : candidateNames) {
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
if (logger.isDebugEnabled()) {
logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
}
}
else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
}

// Return immediately if no @Configuration classes were found
if (configCandidates.isEmpty()) {
return;
}

// Sort by previously determined @Order value, if applicable
configCandidates.sort((bd1, bd2) -> {
int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
return Integer.compare(i1, i2);
});

// Detect any custom bean name generation strategy supplied through the enclosing application context
SingletonBeanRegistry sbr = null;
if (registry instanceof SingletonBeanRegistry) {
sbr = (SingletonBeanRegistry) registry;
if (!this.localBeanNameGeneratorSet) {
BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
if (generator != null) {
this.componentScanBeanNameGenerator = generator;
this.importBeanNameGenerator = generator;
}
}
}

if (this.environment == null) {
this.environment = new StandardEnvironment();
}

// Parse each @Configuration class
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);

Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
do {
StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
// 配置类解析阶段
// 最终调用org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass 方法
parser.parse(candidates);
parser.validate();

Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);

// Read the model and create bean definitions based on its content
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.resourceLoader, this.environment,
this.importBeanNameGenerator, parser.getImportRegistry());
}
this.reader.loadBeanDefinitions(configClasses);
alreadyParsed.addAll(configClasses);
processConfig.tag("classCount", () -> String.valueOf(configClasses.size())).end();

candidates.clear();
if (registry.getBeanDefinitionCount() > candidateNames.length) {
String[] newCandidateNames = registry.getBeanDefinitionNames();
Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
Set<String> alreadyParsedClasses = new HashSet<>();
for (ConfigurationClass configurationClass : alreadyParsed) {
alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
}
for (String candidateName : newCandidateNames) {
if (!oldCandidateNames.contains(candidateName)) {
BeanDefinition bd = registry.getBeanDefinition(candidateName);
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
!alreadyParsedClasses.contains(bd.getBeanClassName())) {
candidates.add(new BeanDefinitionHolder(bd, candidateName));
}
}
}
candidateNames = newCandidateNames;
}
}
while (!candidates.isEmpty());

// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
}

if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
// Clear cache in externally provided MetadataReaderFactory; this is a no-op
// for a shared cache since it'll be cleared by the ApplicationContext.
((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
}
}
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
protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
// 进行shouldSkip过滤
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return;
}

ConfigurationClass existingClass = this.configurationClasses.get(configClass);
if (existingClass != null) {
if (configClass.isImported()) {
if (existingClass.isImported()) {
existingClass.mergeImportedBy(configClass);
}
// Otherwise ignore new imported config class; existing non-imported class overrides it.
return;
}
else {
// Explicit bean definition found, probably replacing an import.
// Let's remove the old one and go with the new one.
this.configurationClasses.remove(configClass);
this.knownSuperclasses.values().removeIf(configClass::equals);
}
}

// Recursively process the configuration class and its superclass hierarchy.
SourceClass sourceClass = asSourceClass(configClass, filter);
do {
sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
}
while (sourceClass != null);

this.configurationClasses.put(configClass, configClass);
}
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
/**
* Determine if an item should be skipped based on {@code @Conditional} annotations.
* @param metadata the meta data
* @param phase the phase of the call
* @return if the item should be skipped
*/
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
return false;
}

if (phase == null) {
if (metadata instanceof AnnotationMetadata &&
ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
}
return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
}

List<Condition> conditions = new ArrayList<>();
for (String[] conditionClasses : getConditionClasses(metadata)) {
for (String conditionClass : conditionClasses) {
Condition condition = getCondition(conditionClass, this.context.getClassLoader());
conditions.add(condition);
}
}

AnnotationAwareOrderComparator.sort(conditions);

for (Condition condition : conditions) {
ConfigurationPhase requiredPhase = null;
if (condition instanceof ConfigurationCondition) {
requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
}
if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
return true;
}
}

return false;
}

ConfigurationCondition接口

  • 如果将Condition接口的实现类作为配置类上@Conditional中,那么这个条件会对两个阶段都有效,此时通过Condition是无法精细的控制某个阶段的,如果想控制某个阶段,比如可以让他解析,但是不能让他注册,此时就就需要用到另外一个接口了:ConfigurationCondition.
  • 判断bean存不存在的问题,通常会使用ConfigurationCondition这个接口,阶段:REGISTER_BEAN,这样可以确保条件判断是在bean注册阶段执行的。

@Conditional扩展注解

名称 说明
@ConditionalOnBean 仅仅在当前上下文中存在某个对象,才会实例化一个Bean
@ConditionalOnClass 某个Class位于类路径上,才会实例化一个Bean
@ConditianalOnExpression 当表达式为true的时候,才会实例化一个Bean
@ConditianalOnMissingBean 仅仅在当前上下文不存在某个对象时,才会实例化一个Bean
@ConditianalOnMissingClass 某个Class类路径上不存在的时候,才会实例化一个Bean
@ConditianalOnNotWebApplication 当前项目不是Web应用,才会实例化一个Bean
@ConditianalOnWebApplication 当前项目是一个Web应用,才会实例化一个Bean
@ConditianalOnProperty 当指定的属性有指定的值时,才会实例化一个Bean
@ConditianalOnJava 当JVM版本为指定版本范围是,才会实例化一个Bean
@ConditianalOnResource 当类路径下有指定的资源时,才会实例化一个Bean
@ConditianalOnJndi 在JNDI存在的条件下触发实例化
@ConditianalOnSingleCandidate 当指定的Bean在容器中只有一个,或者有多个但是指定了首先的Bean时才会触发实例化