Spring源码学习笔记(5)——@Conditonal注解
Spring源码学习笔记(5)——@Conditonal注解
一. @Conditonal注解基本使用
- 简介 @Conditonal是Spring中常用的一个注解,标记了该注解后,只有在满足@Conditonal中指定的所有条件后,才可以向容器中注入组件。
- 接口信息 @Conditonal只有一个属性,即一个Condition接口的数组,表示该@Conditonal注解需要满足的所有条件,只有当所有的Condition的匹配时,才向IoC容器中注册组件。源码如下:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Conditional {
//只有当所有的Condition的匹配时,才向IoC容器中注册组件
Class<? extends Condition>[] value();
}
- Condition接口只有一个方法,判断是否满足当前条件,源码如下:
public interface Condition {
/**
* 判断是否满足当前条件
* @param context 当前条件所处的上下文环境
* @param metadata @Conditional注解所描述的类型的元信息
* @return 返回true时,表示满足条件,组件可以被注册
*/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
- 示例程序 下面简单演示@Conditional注解的使用,根据当前操作系统的类型,注册不同的Service。 首先,定义Service接口和不同操作系统的实现:
/**
* @Auther: ZhangShenao
* @Date: 2018/9/25 13:39
* @Description:操作系统Service接口
*/
public interface OSService {
String showOSInfo();
}
/**
* @Auther: ZhangShenao
* @Date: 2018/9/25 13:40
* @Description:Windows操作系统实现
*/
public class WindowsOSServiceImpl implements OSService{
@Override
public String showOSInfo() {
return "Windows 操作系统";
}
}
/**
* @Auther: ZhangShenao
* @Date: 2018/9/25 13:41
* @Description:Mac操作系统实现
*/
public class MacOSServiceImpl implements OSService{
@Override
public String showOSInfo() {
return "Mac 操作系统";
}
}
- 下面,定义基于操作系统的Condition类,根据当前环境中的操作系统名称进行匹配:
/**
* @Auther: ZhangShenao
* @Date: 2018/9/25 13:44
* @Description:Windows操作系统Condition
*/
public class WindowsCondition implements Condition{
private static final String WINDOWS_OS_NAME = "windows";
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment environment = context.getEnvironment();
String osName = environment.getProperty("os.name");
return (!StringUtils.isEmpty(osName) && osName.contains(WINDOWS_OS_NAME));
}
}
/**
* @Auther: ZhangShenao
* @Date: 2018/9/25 13:48
* @Description:Mac操作系统Condition
*/
public class MacCondition implements Condition{
private static final String MAC_OS_NAME = "Mac";
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment environment = context.getEnvironment();
String osName = environment.getProperty("os.name");
return (!StringUtils.isEmpty(osName) && osName.contains(MAC_OS_NAME));
}
}
- 接下来,使用@Conditional注解注册Bean,并指定自定义的Condition
/**
* @Auther: ZhangShenao
* @Date: 2018/9/21 10:15
* @Description:Spring配置类
*/
@Configuration
@ComponentScan
public class MainConfig {
@Bean
@Conditional(MacCondition.class)
public OSService macOSService(){
return new MacOSServiceImpl();
}
@Bean
@Conditional(WindowsCondition.class)
public OSService windowsOSService(){
return new WindowsOSServiceImpl();
}
}
- 最后打印所有注入到IoC容器中的OSServiceBean,可以看到只有MacOSServiceImpl实例被注入进来了。(本例使用Mac系统演示)
/**
* @Auther: ZhangShenao
* @Date: 2018/9/21 10:17
* @Description:
*/
public class AnnotationMain {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
Map<String, OSService> osServices = applicationContext.getBeansOfType(OSService.class);
osServices.forEach((s, osService) -> System.err.println(osService));
}
}
二. 源码分析
在Condition接口的matches()方法加断点,可以追溯到ConfigurationClassBeanDefinitionReader类的loadBeanDefinitionsForBeanMethod方法中,该方法会根据配置类的标记了@Bean注解的方法,向容器中注入Bean,方法定义如下:
private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
ConfigurationClass configClass = beanMethod.getConfigurationClass();
MethodMetadata metadata = beanMethod.getMetadata();
String methodName = metadata.getMethodName();
// Do we need to mark the bean as skipped by its condition?
if (this.conditionEvaluator.shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN)) {
configClass.skippedBeanMethods.add(methodName);
return;
}
if (configClass.skippedBeanMethods.contains(methodName)) {
return;
}
// Consider name and any aliases
AnnotationAttributes bean = AnnotationConfigUtils.attributesFor(metadata, Bean.class);
List<String> names = new ArrayList<String>(Arrays.asList(bean.getStringArray("name")));
String beanName = (names.size() > 0 ? names.remove(0) : methodName);
// Register aliases even when overridden
for (String alias : names) {
this.registry.registerAlias(beanName, alias);
}
// Has this effectively been overridden before (e.g. via XML)?
if (isOverriddenByExistingDefinition(beanMethod, beanName)) {
return;
}
ConfigurationClassBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass, metadata);
beanDef.setResource(configClass.getResource());
beanDef.setSource(this.sourceExtractor.extractSource(metadata, configClass.getResource()));
if (metadata.isStatic()) {
// static @Bean method
beanDef.setBeanClassName(configClass.getMetadata().getClassName());
beanDef.setFactoryMethodName(methodName);
}
else {
// instance @Bean method
beanDef.setFactoryBeanName(configClass.getBeanName());
beanDef.setUniqueFactoryMethodName(methodName);
}
beanDef.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR);
beanDef.setAttribute(RequiredAnnotationBeanPostProcessor.SKIP_REQUIRED_CHECK_ATTRIBUTE, Boolean.TRUE);
AnnotationConfigUtils.processCommonDefinitionAnnotations(beanDef, metadata);
Autowire autowire = bean.getEnum("autowire");
if (autowire.isAutowire()) {
beanDef.setAutowireMode(autowire.value());
}
String initMethodName = bean.getString("initMethod");
if (StringUtils.hasText(initMethodName)) {
beanDef.setInitMethodName(initMethodName);
}
String destroyMethodName = bean.getString("destroyMethod");
if (destroyMethodName != null) {
beanDef.setDestroyMethodName(destroyMethodName);
}
// Consider scoping
ScopedProxyMode proxyMode = ScopedProxyMode.NO;
AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(metadata, Scope.class);
if (attributes != null) {
beanDef.setScope(attributes.getAliasedString("value", Scope.class, configClass.getResource()));
proxyMode = attributes.getEnum("proxyMode");
if (proxyMode == ScopedProxyMode.DEFAULT) {
proxyMode = ScopedProxyMode.NO;
}
}
// Replace the original bean definition with the target one, if necessary
BeanDefinition beanDefToRegister = beanDef;
if (proxyMode != ScopedProxyMode.NO) {
BeanDefinitionHolder proxyDef = ScopedProxyCreator.createScopedProxy(
new BeanDefinitionHolder(beanDef, beanName), this.registry, proxyMode == ScopedProxyMode.TARGET_CLASS);
beanDefToRegister = new ConfigurationClassBeanDefinition(
(RootBeanDefinition) proxyDef.getBeanDefinition(), configClass, metadata);
}
if (logger.isDebugEnabled()) {
logger.debug(String.format("Registering bean definition for @Bean method %s.%s()",
configClass.getMetadata().getClassName(), beanName));
}
this.registry.registerBeanDefinition(beanName, beanDefToRegister);
}
该方法会扫描所有@Configuration配置类的标记了@Bean的注解的方法,将方法封装成一个BeanMethod,向容器中注入。在处理前,会调用ConditionEvaluator的shouldSkip()方法判断当前Bean是否要跳过注册。ConditionEvaluator对象是在ConfigurationClassBeanDefinitionReader构造器中实例化的,其主要作用就是处理@Conditional注解的相关逻辑。
在shouldSkip()方法中,会@Bean所在方法或者类上的@Conditional注解,并获取@Conditional注解的所有Condition条件对象,依次调用matcher()方法。只要有一个Condition匹配不成功,就跳过该Bean的注册。具体逻辑如下:
public boolean shouldSkip(AnnotatedTypeMetadata metadata, ConfigurationPhase phase) {
if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
return false;
}
//phase为ConfigurationPhase.REGISTER_BEAN注册Bean阶段
if (phase == null) {
if (metadata instanceof AnnotationMetadata &&
ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
}
return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
}
//获取该@Bean方法上所有的Condition对象,包括方法上定义的和类上定义的
List<Condition> conditions = new ArrayList<Condition>();
for (String[] conditionClasses : getConditionClasses(metadata)) {
for (String conditionClass : conditionClasses) {
Condition condition = getCondition(conditionClass, this.context.getClassLoader());
conditions.add(condition);
}
}
AnnotationAwareOrderComparator.sort(conditions);
//依次调用所有Condition对象的matches()方法,只要有一个匹配失败,就跳过该Bean的注册
for (Condition condition : conditions) {
ConfigurationPhase requiredPhase = null;
if (condition instanceof ConfigurationCondition) {
requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
}
if (requiredPhase == null || requiredPhase == phase) {
if (!condition.matches(this.context, metadata)) {
return true;
}
}
}
三. 在SpringBoot中的应用
@Conditional注解在SpringBoot框架中得到了广泛的使用,SpringBoot定义了大量的基于@Conditional注解的衍生注解,并通过这些注解控制Bean的注册。下面以常用的@ConditionalOnBean注解为例,进行简单的介绍。
@ConditionalOnBean是SpringBoot定义的一个常用的条件注解,含义是只有当IoC容器中已经存在指定Class的实例时,才满足条件。源码如下:
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnBean {
/**
* The class type of bean that should be checked. The condition matches when all of
* the classes specified are contained in the {@link ApplicationContext}.
* @return the class types of beans to check
*/
Class<?>[] value() default {};
/**
* The class type names of bean that should be checked. The condition matches when all
* of the classes specified are contained in the {@link ApplicationContext}.
* @return the class type names of beans to check
*/
String[] type() default {};
/**
* The annotation type decorating a bean that should be checked. The condition matches
* when all of the annotations specified are defined on beans in the
* {@link ApplicationContext}.
* @return the class-level annotation types to check
*/
Class<? extends Annotation>[] annotation() default {};
/**
* The names of beans to check. The condition matches when all of the bean names
* specified are contained in the {@link ApplicationContext}.
* @return the name of beans to check
*/
String[] name() default {};
/**
* Strategy to decide if the application context hierarchy (parent contexts) should be
* considered.
* @return the search strategy
*/
SearchStrategy search() default SearchStrategy.ALL;
}
可以看到,@ConditionalOnBean注解使用了@Conditional,并指定了条件OnBeanCondition。
OnBeanCondition继承自SpringBootCondition,SpringBootCondition是SpringBoot定义的Condition的父类,其核心逻辑是通过getMatchOutcome()方法拿到匹配条件,并进行判断。getMatchOutcome()是一个模板方法,交给子类去实现。
- c++学习笔记之封装篇(上)
- c++学习笔记之继承篇
- c++学习笔记之继承篇
- silverlight:对象拖动的优雅解决方案
- Silverlgiht:快速去除/恢复对象的颜色
- deepin系统下如何设置wifi热点(亲测有效)
- Silverlight单元测试
- Silverlight:页面/控件继承的二种写法
- java学习:Hibernate入门
- Silverlight Telerik控件学习:GridView双向绑定
- XmlWriter/XmlReader示例代码
- Silverlight Telerik控件学习:RadComboBox之自动完成(AutoComplete)
- Silverlight Telerik控件学习:数据录入、数据验证
- AI与自动驾驶
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- 一起来学matlab-matlab学习笔记8 基本绘图命令_2基本绘图操作
- 用一个图书库实例搞懂二分搜索树的底层原理
- 自已做动画及编写程序搞清楚最大堆的实现原理
- 一起来学演化计算-matlab基本函数strcmp num2str 字符串格式
- 一起来学matlab-matlab学习笔记8 基本绘图命令_1 图形窗口简介
- 根据barcode过滤bam文件
- biopython - 比较两个序列的相似性
- 使用阿里函数计算同步OSS增量对象到COS
- GitLab定时备份及恢复
- MySQL 的B+树索引.
- Spring全家桶的深入学习(一):Spring起步
- Spring的学习与实战
- kubernetes 安装笔记
- Spring的学习与实战(续)
- mycat数据库集群系列之数据库多实例安装