SpringBoot-注解@Conditional
参考文献
@Conditional
注解
1 | // org.springframework.context.annotation.Conditional |
@Conditional
注解是从Spring 4.0
才有的,可以在任何类型或方法上面标注.通过@Conditional
注解可以配置一些条件判断,当所有条件都满足的时候被标注的对象才会被Spring容器处理.@Conditional
注解实现的原理是通过org.springframework.context.annotation.Condition
这个接口判断是否应该执行操作.
Condition
接口
1 | package org.springframework.context.annotation; |
-
@Conditional
注解判断条件与否取决于value
属性指定的Condition
实现,其中matches
方法,返回true
表示条件成立,反之不成立. -
matches
中的两个参数如下:context
: 条件上下文,ConditionContext
接口类型的,可以用来获取容器中上下文信息.metadata
:用来获取被@Conditional
标注的对象上的所有注解信息.
ConditionContext
接口
1 | package org.springframework.context.annotation; |
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还没有注册到容器中.
- 配置类解析阶段只是将需要加载的配置类和一些Bean(被
@Conditional
使用的3步骤
- 自定义一个类,实现Condition或ConfigurationCondition接口,实现matches方法
- 在目标对象上使用@Conditional注解,并指定value的指为自定义的Condition类型
- 启动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的注册
-
从上面过程中可以了解到:
-
可以在配置类上面加上@Conditional注解,来控制是否需要解析这个配置类,配置类如果不被解
析,那么这个配置上面6种注解的解析都会被跳过 -
可以在被注册的bean上面加上@Conditional注解,来控制这个bean是否需要注册到Spring容器中
-
如果配置类不会被注册到容器,那么这个配置类解析所产生的所有新的配置类及所产生的所有新的
bean都不会被注册到容器
-
源码
1 | /** |
1 | protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException { |
1 | /** |
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时才会触发实例化 |