@Conditional注解及扩展注解
@Conditional注解@Conditional注解是从spring4.0才有的,可以用在任何类型或者方法上面,通过@Conditional注解可以配置一些条件判断,当所有条件都满足的时候,被@Conditional标注的目标才会被spring容器处理。可以通过@Conditional来控制bean是否需要注册,控制被@Configuration标注的配置类是需要需要被解析等。@Target(
文章目录
- 1.@Conditional注解
- 2.条件判断执行时机
- 3.@Conditional扩展注解
- 4.@ConditionalOnBean
- 5.@ConditionalOnMissingBean
- 6.@ConditionalOnClass
- 7.@ConditionalOnMissingClass
- 8.@ConditionalOnSingleCandidate
- 9.@ConditionalOnProperty
- 10.@ConditionalOnResource
- 11.@ConditionalOnWebApplication
- 12.@ConditionalOnNotWebApplication
- 13.@ConditionalOnJava
- 14.@ConditionalOnJndi
1.@Conditional注解
@Conditional注解是从spring4.0才有的,可以用在任何类型或者方法上面,通过@Conditional注解可以配置一些条件判断,当所有条件都满足的时候,被@Conditional标注的目标才会被spring容器处理。
可以通过@Conditional来控制bean是否需要注册,控制被@Configuration标注的配置类是需要需要被解析等。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}
注解只有一个value参数,Condition类型的数组,Condition是一个接口,表示一个条件判断,内部有个方法返回true或false,当所有Condition都成立的时候,@Conditional的结果才成立。
Condition接口
用来表示条件判断的接口
@FunctionalInterface
public interface Condition {
/**
* 判断条件是否匹配
* context:条件判断上下文
*/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
是一个函数式接口,内部只有一个matches方法,用来判断条件是否成立的,2个参数:
- context:条件上下文,ConditionContext接口类型的,可以用来获取容器中的个人信息
- metadata:用来获取被@Conditional标注的对象上的所有注解信息
ConditionContext接口
接口中提供了一些常用的方法,可以用来获取spring容器中的各种信息
public interface ConditionContext {
/**
* 返回bean定义注册器,可以通过注册器获取bean定义的各种配置信息
*/
BeanDefinitionRegistry getRegistry();
/**
* 返回ConfigurableListableBeanFactory类型的bean工厂,相当于一个ioc容器对象
*/
@Nullable
ConfigurableListableBeanFactory getBeanFactory();
/**
* 返回当前spring容器的环境配置信息对象
*/
Environment getEnvironment();
/**
* 返回资源加载器
*/
ResourceLoader getResourceLoader();
/**
* 返回类加载器
*/
@Nullable
ClassLoader getClassLoader();
}
如果将Condition接口的实现类作为配置类上@Conditional中,那么这个条件会对两个阶段都有效,此时通过Condition是无法精细的控制某个阶段的,如果想控制某个阶段,比如可以让他解析,但是不能让他注册,此时就就需要用到另外一个接口了:ConfigurationCondition
ConfigurationCondition接口
public interface ConfigurationCondition extends Condition {
/**
* 条件判断的阶段,是在解析配置类的时候过滤还是在创建bean的时候过滤
*/
ConfigurationPhase getConfigurationPhase();
/**
* 表示阶段的枚举:2个值
*/
enum ConfigurationPhase {
/**
* 配置类解析阶段,如果条件为false,配置类将不会被解析
*/
PARSE_CONFIGURATION,
/**
* bean注册阶段,如果为false,bean将不会被注册
*/
REGISTER_BEAN
}
}
ConfigurationCondition接口相对于Condition接口多了一个getConfigurationPhase方法,用来指定条件判断的阶段,是在解析配置类的时候过滤还是在创建bean的时候过滤
@Conditional使用步骤
- 自定义一个类,实现Condition或ConfigurationCondition接口,实现matches方法
- 在目标对象上使用@Conditional注解,并指定value的指为自定义的Condition类型
- 启动spring容器加载资源,此时@Conditional就会起作用了
2.条件判断执行时机
Spring对配置类的处理阶段
- 配置类解析阶段
会得到一批配置类的信息,和一些需要注册的bean - bean注册阶段
将配置类解析阶段得到的配置类和需要注册的bean注册到spring容器中
spring中处理这2个阶段会循环进行,直到完成所有配置类的解析及所有bean的注册。
配置类种类
配置类的种类:
- 类上有@Component注解
- 类上有@Configuration注解
- 类上有@CompontentScan注解
- 类上有@Import注解
- 类上有@ImportResource注解
- 类中有@Bean标注的方法
判断一个类是不是一个配置类,是否的是下面这个方法:
org.springframework.context.annotation.ConfigurationClassUtils#isConfigurationCandidate
Spring对配置类的处理过程
源码位置:org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions
大致过程如下:
- 通常我们会通过new AnnotationConfigApplicationContext()传入多个配置类来启动spring容器
- spring对传入的多个配置类进行解析
- 配置类解析阶段:这个过程就是处理配置类上面6中注解的过程,此过程中又会发现很多新的配置类,比如@Import导入的一批新的类刚好也符合配置类,而被@CompontentScan扫描到的一些类刚好也是配置类;此时会对这些新产生的配置类进行同样的过程解析
- bean注册阶段:配置类解析后,会得到一批配置类和一批需要注册的bean,此时spring容器会将这批配置类作为bean注册到spring容器,同样也会将这批需要注册的bean注册到spring容器
- 经过上面第3个阶段之后,spring容器中会注册很多新的bean,这些新的bean中可能又有很多新的配置类
- Spring从容器中将所有bean拿出来,遍历一下,会过滤得到一批未处理的新的配置类,继续交给第3步进行处理
- 3到6,这个过程会经历很多次,直到完成所有配置类的解析和bean的注册
过程分析:
- 可以在配置类上面加上@Conditional注解,来控制是否需要解析这个配置类,配置类如果不被解析,那么这个配置上面6种注解的解析都会被跳过
- 可以在被注册的bean上面加上@Conditional注解,来控制这个bean是否需要注册到spring容器中
- 如果配置类不会被注册到容器,那么这个配置类解析所产生的所有新的配置类及所产生的所有新的bean都不会被注册到容器
一个配置类被spring处理有2个阶段:配置类解析阶段、bean注册阶段(将配置类作为bean被注册到spring容器)。
3.@Conditional扩展注解
注解 | 描述 | 处理类 | 使用方法 |
---|---|---|---|
@ConditionalOnBean | 当容器中至少存在一个指定name或class的Bean时,进行实例化 | OnBeanCondition | @ConditionalOnBean(CacheManager.class) |
@ConditionalOnMissingBean | 当容器中指定name或class的Bean都不存在时,进行实例化 | OnBeanCondition | @ConditionalOnMissingBean(CacheManager.class) |
@ConditionalOnClass | 当类路径下至少存在一个指定的class时,进行实例化 | OnClassCondition | @ConditionalOnClass({Aspect.class, Advice.class }) |
@ConditionalOnMissingClass | 当容器中指定class都不存在时,进行实例化 | OnClassCondition | @ConditionalOnMissingClass(“org.thymeleaf.templatemode.TemplateMode”) |
@ConditionalOnSingleCandidate | 当指定的Bean在容器中只有一个,或者有多个但是指定了首选的Bean时触发实例化 | OnBeanCondition | @ConditionalOnSingleCandidate(DataSource.class) |
@ConditionalOnProperty | 当指定的属性有指定的值时进行实例化 | OnPropertyCondition | @ConditionalOnProperty(prefix = “spring.aop”, name = “auto”) |
@ConditionalOnResource | 当类路径下有指定的资源时触发实例化 | OnResourceCondition | @ConditionalOnResource(resources = “classpath:META-INF/build.properties”) |
@ConditionalOnExpression | 基于SpEL表达式的条件判断,当为true的时候进行实例化 | OnExpressionCondition | @ConditionalOnExpression(“true”) |
@ConditionalOnWebApplication | 当项目是一个Web项目时进行实例化 | OnWebApplicationCondition | @ConditionalOnWebApplication |
@ConditionalOnNotWebApplication | 当项目不是一个Web项目时进行实例化 | OnWebApplicationCondition | @ConditionalOnNotWebApplication |
@ConditionalOnJava | 当JVM版本为指定的版本范围时触发实例化 | OnJavaCondition | @ConditionalOnJava(ConditionalOnJava.JavaVersion.EIGHT) |
@ConditionalOnJndi | 在JNDI存在的条件下触发实例化 | OnJndiCondition | @ConditionalOnJndi({ “java:comp/TransactionManager”}) |
4.@ConditionalOnBean
@ConditionalOnBean源码
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnBean {
// bean的类型,当ApplicationContext包含给定类的bean时返回true
Class<?>[] value() default {};
// bean的类型名,当ApplicationContext包含给定的id时返回true
String[] type() default {};
// bean所声明的注解,当ApplicationContext中存在声明该注解的bean时返回true
Class<? extends Annotation>[] annotation() default {};
// bean的id,,当ApplicationContext中存在给定id的bean时返回true
String[] name() default {};
// 默认是所有上下文搜索
SearchStrategy search() default SearchStrategy.ALL;
}
SearchStrategy类源码
public enum SearchStrategy {
// 查询当前的context
CURRENT,
// 查询所有的祖先和父辈容器,但是不包含当前容器,从1.5开始废弃,推荐使用ANCESTORS
@Deprecated PARENTS,
// 搜索所有的祖先,不搜索当前的context
ANCESTORS,
// 搜索整个上下文
ALL
}
处理类OnBeanCondition类源码
public ConfigurationPhase getConfigurationPhase() {
return ConfigurationPhase.REGISTER_BEAN;
}
OnBeanCondition是@ConditionalOnBean,@ConditionalOnSingleCandidate,@ConditionalOnMissingBean三个注解的处理类
使用案例
在DataSourceAutoConfiguration中声明了如下方法:
@Bean
@ConditionalOnMissingBean
public DataSourceInitializer dataSourceInitializer(DataSourceProperties properties,
ApplicationContext applicationContext) {
return new DataSourceInitializer(properties, applicationContext);
}
表明当beanFactory中不存在DataSourceInitializer类型的bean时,才进行注册
5.@ConditionalOnMissingBean
@ConditionalOnMissingBean源码
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnMissingBean {
// bean的类型,当ApplicationContext不包含给定类的bean时返回true
Class<?>[] value() default {};
// bean的类型名,当ApplicationContext不包含给定的id时返回true
String[] type() default {};
// 给定的类型当进行匹配时进行忽略
Class<?>[] ignored() default {};
// 给定的类型名当进行匹配时进行忽略
String[] ignoredType() default {};
// bean所声明的注解,当ApplicationContext中不存在声明该注解的bean时返回true
Class<? extends Annotation>[] annotation() default {};
// bean的id,,当ApplicationContext中不存在给定id的bean时返回true
String[] name() default {};
// 默认是所有上下文搜索
SearchStrategy search() default SearchStrategy.ALL;
}
处理类OnBeanCondition源码
if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
// 3.1 实例化BeanSearchSpec
BeanSearchSpec spec = new BeanSearchSpec(context, metadata,
ConditionalOnMissingBean.class);
// 3.2 获得给定条件的beanNames
List<String> matching = getMatchingBeans(context, spec);
if (!matching.isEmpty()) {
// 3.3 如果不为空,返回不匹配,否则返回匹配
return ConditionOutcome.noMatch(ConditionMessage
.forCondition(ConditionalOnMissingBean.class, spec)
.found("bean", "beans").items(Style.QUOTE, matching));
}
matchMessage = matchMessage.andCondition(ConditionalOnMissingBean.class, spec)
.didNotFind("any beans").atAll();
}
return ConditionOutcome.match(matchMessage);
- 实例化BeanSearchSpec
- 获得给定条件的beanNames
- 如果不为空,返回不匹配,否则返回匹配
使用案例
在DataSourceAutoConfiguration中声明了如下方法:
@Bean
@ConditionalOnMissingBean
public DataSourceInitializer dataSourceInitializer(DataSourceProperties properties,
ApplicationContext applicationContext) {
return new DataSourceInitializer(properties, applicationContext);
}
表明当beanFactory中不存在DataSourceInitializer类型的bean时,才进行注册
6.@ConditionalOnClass
@ConditionalOnClass源码
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {
/**
*
* 给定的类必须存在
* @return the classes that must be present
*/
Class<?>[] value() default {};
/**
*
* 给定的类名,该类名必须存在
* @return the class names that must be present.
*/
String[] name() default {};
}
使用案例
AopAutoConfiguration声明了如下注解:
@ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class })
表明当在当前类路径存在EnableAspectJAutoProxy.class, Aspect.class, Advice.class时才对AopAutoConfiguration进行解析
7.@ConditionalOnMissingClass
@ConditionalOnMissingClass 源码
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnMissingClass {
// 给定的类名在当前类路径下不存在时返回true
String[] value() default {};
}
使用案例
Thymeleaf2Configuration 声明了如下注解:
@ConditionalOnMissingClass("org.thymeleaf.templatemode.TemplateMode")
表明当在当前类路径不存在org.thymeleaf.templatemode.TemplateMode时才对Thymeleaf2Configuration进行解析
8.@ConditionalOnSingleCandidate
@ConditionalOnSingleCandidate源码
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnSingleCandidate {
/**
*
* bean的类型,当ApplicationContext包含给定类的bean时并且如果有多个该类型的bean并且指定为primary的
* 存在则返回true.
*
* @return the class type of the bean to check
*/
Class<?> value() default Object.class;
/**
*
* bean的类型名,当ApplicationContext包含给定的id并且如果有多个该类型的bean并且指定为primary的
* 存在则返回true.
* @return the class type name of the bean to check
*/
String type() default "";
/**
*
* 默认是所有上下文搜索
* @return the search strategy
*/
SearchStrategy search() default SearchStrategy.ALL;
}
使用案例
在DataSourceTransactionManagerConfiguration 声明了如下注解:
@Configuration
@ConditionalOnSingleCandidate(DataSource.class)
static class DataSourceTransactionManagerConfiguration
标识:当DataSource类型的bean存在并且指定为Primary的DataSource存在时,加载DataSourceTransactionManagerConfiguration的配置
9.@ConditionalOnProperty
ConditionalOnProperty源码:
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnPropertyCondition.class)
public @interface ConditionalOnProperty {
// name属性的别名
String[] value() default {};
// 属性前缀,如果该前缀不是.结尾的,则会自动加上
String prefix() default "";
// 属性名,如果前缀被声明了,则会拼接为prefix+name 去查找.通过-进行分割单词,name需要为小写
String[] name() default {};
// 表明所期望的结果,如果没有指定该属性,则该属性所对应的值不为false时才匹配
String havingValue() default "";
// 表明配置的属性如果没有指定的话,是否匹配,默认不匹配
boolean matchIfMissing() default false;
// 是否支持relaxed(松散匹配). 默认支持
boolean relaxedNames() default true;
}
使用案例
在AopAutoConfiguration声明了如下注解:
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
表明: 如果配置了spring.aop.auto并且值为true时匹配,或者spring.aop.auto没配置时匹配
10.@ConditionalOnResource
@ConditionalOnResource源码:
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnResourceCondition.class)
public @interface ConditionalOnResource {
// 指定的资源必须存在,否则返回不匹配
String[] resources() default {};
}
OnResourceCondition
@ConditionalOnResource 所对应的处理类为OnResourceCondition
使用案例
在ProjectInfoAutoConfiguration中声明了如下方法:
@ConditionalOnResource(resources = "${spring.info.build.location:classpath:META-INF/build-info.properties}")
@ConditionalOnMissingBean
@Bean
public BuildProperties buildProperties() throws Exception {
return new BuildProperties(
loadFrom(this.properties.getBuild().getLocation(), "build"));
}
当spring.info.build.location配置的资源如果存在的话 或者 spring.info.build.location没配置的话并且classpath:META-INF/build-info.properties 存在的话,则 进行进一步的处理–> @ConditionalOnMissingBean 注解的处理
11.@ConditionalOnWebApplication
@ConditionalOnWebApplication源码
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnWebApplicationCondition.class)
public @interface ConditionalOnWebApplication {
}
使用案例
FreeMarkerWebConfiguration 声明了如下注解:
@ConditionalOnWebApplication
表明在web环境时加载该配置
12.@ConditionalOnNotWebApplication
@ConditionalOnNotWebApplication源码
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnWebApplicationCondition.class)
public @interface ConditionalOnNotWebApplication {
}
使用案例
FreeMarkerNonWebConfiguration声明了如下注解:
@ConditionalOnNotWebApplication
表明不在web环境时加载该配置
13.@ConditionalOnJava
@ConditionalOnJava源码
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnJavaCondition.class)
public @interface ConditionalOnJava {
/**
*
* 表明是大于等于配置的JavaVersion还是小于配置的JavaVersion
*/
Range range() default Range.EQUAL_OR_NEWER;
/**
*
* 配置要检查的java版本.使用range属性来表明大小关系
* @return the java version
*/
JavaVersion value();
使用案例
ThymeleafJava8TimeDialect声明了如下注解:
@ConditionalOnJava(ConditionalOnJava.JavaVersion.EIGHT)
表明只有在1.8及以上的java环境下才加载ThymeleafJava8TimeDialect的配置
14.@ConditionalOnJndi
@ConditionalOnJndi源码:
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnJndiCondition.class)
public @interface ConditionalOnJndi {
// 给定的jndi的Location 必须存在一个.否则,返回不匹配
String[] value() default {};
}
OnJndiCondition
@ConditionalOnJndi 对应的处理类为 OnJndiCondition
使用案例
JndiJtaConfiguration声明了如下注解
@ConditionalOnJndi({ JtaTransactionManager.DEFAULT_USER_TRANSACTION_NAME,
"java:comp/TransactionManager", "java:appserver/TransactionManager",
"java:pm/TransactionManager", "java:/TransactionManager" })
表明当jndi 在java:comp/UserTransaction,java:comp/TransactionManager,java:appserver/TransactionManager,java:pm/TransactionManager,java:/TransactionManager 路径上只要存在一个资源,则加载JndiJtaConfiguration的配置
更多推荐
所有评论(0)