前言
在 spring 工程中经常能看到在服务配置中添加了某些服务配置后,这个服务就能用了,例如在yaml文件中配置了下面的配置后,oauth2在校验token时就不会去调动本地的服务,而是会调用token-info-uri中设置的uri来获取token的校验结果
security:
oauth2:
resource:
token-info-uri:
client:
client-id:
client-secret:
这是如何做到的呢?答案就是@Conditional
,通过这个注解,我们就可以动态控制 spring 服务中某一些 bean 是否实例化并放入 spring 容器中,从而控制是否提供对应功能。接下来我们就来看下这个魔力的注解。
使用方式
首先我们来需要知道这个注解怎么用,并且能够达到的效果。
该注解往往是定义在配置类中,放在创建bean的对象上,例如
/**
* 在dbService上定义有@Conditional注解,注解中的值就是需要判断的实体
**/
@Configuration
public class DBConfig {
@Bean
@Conditional(CustomCondition.class)
public DBService dbService() {
DBService dbService = new DBService();
return dbServie;
}
}
/**
* 实现Condition接口,该接口只有一个方法,该方法就是判断是否创建bean实例并放入spring容器的关键,如果该方法返回true,则创建,返回false就不创建。
**/
public class CustomCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
return true;
}
}
在上面的例子中,CustomCondition
中的matches
方法一定会返回true,表示dbService一定会创建出来,同样如果matches方法返回false的话,则dbService就不会被创建。如果dbService被创建出来,我们就可以通过编写代码,调用dbService服务和数据库正常交流了。
配置控制
讲到这里,有些小伙伴就要问了,文章的一开始不是说通过配置来控制么,这里也没法控制呀?
其实是因为在matches
方法中我写的过于简单了,直接返回了true,我们可以通过读取配置中是否存在某个配置,选择返回true,还是false,这样就可以通过配置来动态管理了。
那么我们又该如果获取配置信息呢?
public class CustomCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
Environment environment = conditionContext.getEnvironment();
String dbServiceFlag = environment.getProperty("jdq.DBSercive.enable");
if (StringUtils.hasText(dbServiceFlag) && "true".equalsIgnoreCase(dbServiceFlag)) {
return true;
} else {
return false;
}
}
}
在 conditionContext 中存储有服务上下文信息,可以通过上面的方式去获取服务配置内容。
jdq.DBSercive.enable=true
如果在配置文件上配置上面这个配置后,DBService服务便会生成,如果配置false或者没有配置,那么就不会生成DBService服务。
实现原理
在了解该注解实现方式后,我们就来看下他的实现原理。这个注解本质上就是控制 bean 对象是否导入到 spring 容器中。那么首先我们来看下spring 是如何处理 bean 对象的导入的
。
spring 工程在启动时首先会调用org.springframework.context.support.AbstractApplicationContext#refresh
函数。
查看refresh
可以发现invokeBeanFactoryPostProcessors
这个函数,注释的大概意思就是调用已经注册到下上文中的factory processors。也就是BeanFactoryPostProcessor
这个bean对象。可能有些同学有点懵了,BeanFactoryPostProcessor
是啥玩意,听都没有听过,但是这个类可以说是非常的牛叉,他工作在 spring 启动时, 所有的 bean 定义已经加载好了,但是还没有初始化对应的对象(如果大家对类对象的加载和初始化不太懂的话,建议去学习一下JVM相关的内容,这里就不在解释了),在这个期间,我们可以使用这个类来修改 bean 的定义,就问强不强吧,无论你 bean 是如何定义, BeanFactoryPostProcessor
都可以修改你的定义。并且在BeanFactoryPostProcessor
在处理好后,就开始对应 bean 对象的初始化了,初始化后该初始化生成的对象就存在于 spring 容器中了。
那经过分析,我们可以看下invokeBeanFactoryPostProcessors
函数来探究里面做了哪些工作。
注释主要说:实例化并调用所有的已经注册的BeanFactoryPostProcessor
对象,根据order来先后调用。那我们可以考虑调用BeanFactoryPostProcessor
对象是来干啥呢?带着这个问题我们来看下invokeBeanFactoryPostProcessors
函数,主要就是这个函数做的工作了。
一点进去,我的天,这么长,说实话我截图一下子都截不下来,但是不要慌,spring 的代码绝对不会写无用的代码,我们可以按照功能点对这个函数进行划分。我们先来看最先开始的一部分
换到sublime中还能截的长一点,通过注释我们可以看到这些代码其实就是做了一件事,就是获取BeanDefinitionRegistryPostProcessor
的实现类,并且对其不同的实现类分为了三类
- 实现了
PriorityOrdered
的类 - 实现了
Ordered
的类 - 剩下的其他归为一类
BeanDefinitionRegistryPostProcessor
大家可能又会感觉到陌生,这个其实也是一个接口,并且这个实现了BeanFactoryPostProcessor
接口,BeanDefinitionRegistryPostProcessor
接口上只有一个方法
看这个方法的注释,就可以知道这个方式提供了一种能力,可以允许更多的 bean 定义加载进来,通俗的将就是可以调用这个方法来注册 bean。又是一个非常强悍的接口。
这里我先对BeanDefinitionRegistryPostProcessor
和BeanDefinitionPostProcessor
做一下总结
BeanDefinitionPostProcessor
是在bean加载完后,在bean实例化前处理,可以对bean的定义进行修改BeanDefinitionRegistryPostProcessor
是在bean加载中,可以使用这个来加载bean
所以我们可以知道BeanDefinitionRegistryPostProcessor
的作用时间一定是在BeanDefinitionPostProcessor
之前的,这也是我们先看到调用invokeBeanDefinitionRegistryPostProcessors
方法的原因
这时候需要关注到这个类,他实现了PriorityOrdered
和BeanDefinitionRegistryPostProcessor
, 所以在第一类实现了PriorityOrdered
就会被找到,然后调用invokeBeanDefinitionRegistryPostProcessors
方法来执行所有找到BeanDefinitionRegistryPostProcessor
中的processConfigBeanDefinitions
方法,包括ConfigurationClassPostProcessor
类。
看类上的注释,大意就是 @Configuraion 注解类的引导处理。
这样目标就很明确了,这个类应该就是处理 @Configuraion 注解类实例化的地方了,我们来看下这个类,里面的调用堆栈有点多,这里就不再每一个函数展开了,流程如下
当走到loadBeanDefinitionsForBeanMethod
方法时
可以发现存在这个函数,没错,这个函数就是@Conditional
注解起作用的地方
如果返回false,则表示不需要跳过bean对象初始化,bean就会被初始化进入spring容器中,反之则不会初始化对应的bean,最后初始化的函数是loadBeanDefinitionsForBeanMethod
方法中的DefaultListableBeanFactory#registerBeanDefinition
方法来进行bean的初始化。
@Conditional 注解扩展
在 spring 中@ConditionalOnProperty
,@ConditionalOnBean
等这类的注解我们一定使用过。这些注解本质上也就是在@Conditional
注解上的扩展,我们就拿@ConditionalOnProperty
为例来看下他是如何实现的。
这个注解的作用和文章一开始写的例子相似,都是如果配置中存在某种配置,就实例化对应的bean,当然 @ConditionalOnProperty
里的功能比我上面例子中的功能多很多(在注解中的值中进行控制)。具体逻辑也是写在OnPropertyCondition
其中SpringBootCondition
是模板类,实现了matches
方法
那我们就一起来看下OnPropertyCondition
类中的getMatchOutcome
方法实现。
其他例如@ConditionalOnBean
等其他注解同理,大家有兴趣可以去看下。