Condition 是在spring4.0 增加的条件注解,通过这个可以功能可以实现选择性的注入Bean操作,接下来先学习下Condition是如何使用的,然后分析spring源码了解其中的实现原理。
更多可看==>Spring&SpringBoot 实践和源码学习
Demo
注意:以下三个代码块分属不同的文件,便于说明具体问题
@Bean("contectService")
@Conditional(LoadConditional.class)
// 条件控制,如果对应的match操作返回true,则会注入该bean
// 否则会跳过处理该bean
public ContextService contextService() {
return new ContextService();
}
public class LoadConditional implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 查看在Bootstrap的ENV的值是否等于test
// 后面会介绍这个context上下文,其中包含了整个的bean工厂内容
return Bootstrap.ENV.equals("test");
}
}
@SpringBootApplication
@ComponentScan("com.demo.boot")
@EnableSwagger2
public class Bootstrap {
public static String ENV = "test";
public static void main(String[] args) {
SpringApplication.run(Bootstrap.class);
}
}
这样就简单的通过@Conditional实现选择性的注入Bean操作,实际开发中,主要是为了区分测试环境和线上环境,其实在spring已经添加了一部分常用的条件注解,如下表格
Conditions | 描述 |
---|---|
@ConditionalOnBean | 在存在某个bean的时候 |
@ConditionalOnMissingBean | 不存在某个bean的时候 |
@ConditionalOnClass | 当前classpath可以找到某个类型的类时 |
@ConditionalOnMissingClass | 当前classpath不可以找到某个类型的类时 |
@ConditionalOnResource | 当前classpath是否存在某个资源文件 |
@ConditionalOnProperty | 当前jvm是否包含某个系统属性为某个值 |
@ConditionalOnWebApplication | 当前spring context是否是web应用程序 |
源码学习
在之前的一篇笔记SpringBoot 之 EnableAutoConfiguration 实践和源码学习 中写道@Configuration实现bean的注入
,那么条件注解肯定就类似于一道阀门在实现bean的注入前通过条件筛选去完成选择性的bean注入,接着上面那篇学习笔记,来到ConfigurationClassPostProcessor 类,关于这个类不再过多介绍
随着Spring的BPP的invoke来到processConfigBeanDefinitions 方法
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
// ......忽略前面一大推代码
// 遍历当前装载到Spring 容器的所有类,获取其中包含了@Configuration的bean到candidates中
// 依次遍历解析Configuration的类
do {
parser.parse(candidates);
parser.validate();
// 解析完成,获取所有的类,暂时还不包含@Bean的信息
Set<ConfigurationClass> configClasses = new LinkedHashSet<ConfigurationClass>(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());
}
// 配置类读取信息,进行条件筛选
// 也就是我们本篇学习笔记的Condition注解原理所在地
this.reader.loadBeanDefinitions(configClasses);
alreadyParsed.addAll(configClasses);
// ....
}
来到ConfigurationClassBeanDefinitionReader 类中,肯定会进行类的遍历操作,获取其中的@Bean,这个方法非常的关键,包含了很多功能
ConfigurationClassBeanDefinitionReader 类
public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
for (ConfigurationClass configClass : configurationModel) {
loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
}
// 传递的configClass就是添加了@Configuration 的包装类 // trackedConditionEvaluator 记录着类是否会拦截的处理类
}
image
image
再来到其实现的细节方法中
private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass,
TrackedConditionEvaluator trackedConditionEvaluator) {
if (trackedConditionEvaluator.shouldSkip(configClass)) {
// 如果该configClass被过滤掉,也就是不应该实例化
// 先直接进入到该方法中看看实现细节
String beanName = configClass.getBeanName();
if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
this.registry.removeBeanDefinition(beanName);
// 从context上下文中移除该config的bean信息
}
this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
return;
// 直接返回
}
// 。。。。。 待续
}
private class TrackedConditionEvaluator {
private final Map<ConfigurationClass, Boolean> skipped = new HashMap<ConfigurationClass, Boolean>();
// 保存着所有config类的筛选状态
public boolean shouldSkip(ConfigurationClass configClass) {
Boolean skip = this.skipped.get(configClass);
if (skip == null) {
// 新处理的类信息
if (configClass.isImported()) {
// 如果该配置类包含@Import导入的类
boolean allSkipped = true;
for (ConfigurationClass importedBy : configClass.getImportedBy()) {
// 以此遍历迭代执行
if (!shouldSkip(importedBy)) {
// 迭代每一个被Import导入的类,如果返回false,也就是不应该被忽略的类
// 则直接break,是否会导致 后面的需要跳过的类被忽略?
allSkipped = false;
break;
}
}
if (allSkipped) {
// 所有通过@Import的类全部跳过处理
skip = true;
}
}
if (skip == null) {
skip = conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN);
// 获取元信息(也就是类似注解信息)判断是否可以跳过。重点关注!
}
this.skipped.put(configClass, skip);
}
return skip;
}
}
ConditionEvaluator 类
public boolean shouldSkip(AnnotatedTypeMetadata metadata, ConfigurationPhase phase) {
if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
// 如果没有元信息,或者注解类中不包含Conditional类,就直接返回false,表示不能跳过
// 注解类Conditional的用途就在这里了!!!!
return false;
}
if (phase == null) {
// 默认传递的phase是ConfigurationPhase.REGISTER_BEAN
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<Condition>();
for (String[] conditionClasses : getConditionClasses(metadata)) {
// 获取包含了@Conditional 的value值
// 类比demo中就是@Conditional(LoadConditional.class)的LoadConditional类全称
for (String conditionClass : conditionClasses) {
Condition condition = getCondition(conditionClass, this.context.getClassLoader());
// 完成LoadConditional类的实例化(必须是不带参数的构造器)
conditions.add(condition);
}
}
AnnotationAwareOrderComparator.sort(conditions);
// 按照order进行排序
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)) {
// 这个matchee 方法就是自定义实现的方法,具体如上demo中的LoadConditional的重载方法
// 这是个循环的方法,意味着可以有多个条件注解,
// 而且一旦出现了一个被过滤了则直接认定为需要被跳过
return true;
}
}
}
return false;
}
该方法就是获取configuration类的注解信息,然后调用相关条件过滤的matches方法获取匹配结果
image这个图也正好说明了传递到matches方法的context包含的内容,例如Spring的Bean工厂,以及上下文环境等信息(一般这个上下文环境使用的比较多)
到这里整个过程感觉比较清晰,但是这是从类的角度触发,处理的也是@Configuration类,而没有@Bean的处理过程
那么继续来到loadBeanDefinitionsForConfigurationClass 类中
private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass,
TrackedConditionEvaluator trackedConditionEvaluator) {
if (trackedConditionEvaluator.shouldSkip(configClass)) {
// 上面已经说了
String beanName = configClass.getBeanName();
if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
this.registry.removeBeanDefinition(beanName);
}
this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
return;
}
if (configClass.isImported()) {
// 包含了@Import的情况
registerBeanDefinitionForImportedConfigurationClass(configClass);
}
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
// 这个地方是处理@Bean的地方,也就是我们需要关注的地方,先忽略下
loadBeanDefinitionsForBeanMethod(beanMethod);
}
loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
// 处理使用了@ImportResource 导入的xml、groovy文件等处理入口
// 肯定是按照读取xml的原理一样,声明一个reader然后解析resource的内容,然后填充到register中
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}
回过头关注处理@Bean的细节
private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
ConfigurationClass configClass = beanMethod.getConfigurationClass();
// 获取方法的@Configuration信息
MethodMetadata metadata = beanMethod.getMetadata();
String methodName = metadata.getMethodName();
if (this.conditionEvaluator.shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN)) {
// 这个就是上面说没有处理method的另一个处理入口
// 返回true就意味着可以被跳过处理
configClass.skippedBeanMethods.add(methodName);
return;
}
if (configClass.skippedBeanMethods.contains(methodName)) {
return;
}
// Consider name and any aliases
// name值是一个String[] 的样式,主要是处理别名的情况
AnnotationAttributes bean = AnnotationConfigUtils.attributesFor(metadata, Bean.class);
List<String> names = new ArrayList<String>(Arrays.asList(bean.getStringArray("name")));
String beanName = (!names.isEmpty() ? 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)?
// springboot本身的重名会被IDEA类似工具发现,但是无法判断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);
}
// 设置init、destroy方法等
// scope范围等数据
// Consider scoping
ScopedProxyMode proxyMode = ScopedProxyMode.NO;
AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(metadata, Scope.class);
if (attributes != null) {
beanDef.setScope(attributes.getString("value"));
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);
}
到这里整个的源码学习就完全结束了,整个的过程也还是按照spring原本的套路去实现的,从一个Spring BPP作为入口处理
网友评论