内容简介:先抛出个问题:SpringBoot 允许通过注解根据某个类是否存在来决定配置,如我们也知道Spring 是如何实现这样的条件加载而不会抛出
1. 一个 Spring 加载类的问题
先抛出个问题:SpringBoot 允许通过注解根据某个类是否存在来决定配置,如
@Bean @ConditionalOnClass(value = HikariDataSource.class) public DataSource hikariDataSource() { return new HikariDataSource(); } @Bean @ConditionalOnClass(value = BasicDataSource.class) public DataSource basicDataSource() { return new BasicDataSource(); }
我们也知道 ClassLoader
加载一个类时,如果这个类或这个类依赖的类找不到则会抛出 ClassNotFoundException
。
Spring 是如何实现这样的条件加载而不会抛出 ClassNotFoundException
异常?
2. Spring 解析配置类的过程
在第一篇已经介绍过 ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry
会注册所有的 Bean 定义。现在来仔细看看这个过程。
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { int registryId = System.identityHashCode(registry); if (this.registriesPostProcessed.contains(registryId)) { throw new IllegalStateException( "postProcessBeanDefinitionRegistry already called on this post-processor against " + registry); } if (this.factoriesPostProcessed.contains(registryId)) { throw new IllegalStateException( "postProcessBeanFactory already called on this post-processor against " + registry); } this.registriesPostProcessed.add(registryId); processConfigBeanDefinitions(registry); }
这个方法主要是做了防止重复注册的处理,具体的注册逻辑在 processConfigBeanDefinitions(registry)
。
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) { List<BeanDefinitionHolder> configCandidates = new ArrayList<BeanDefinitionHolder>(); String[] candidateNames = registry.getBeanDefinitionNames(); // 首先从 registry 找出已经注册的未处理的配置类的定义作为配置类候选 for (String beanName : candidateNames) { BeanDefinition beanDef = registry.getBeanDefinition(beanName); if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) || ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) { if (logger.isDebugEnabled()) { logger.debug("Bean definition has already been processed as a configuration class: " + beanDef); } } else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) { configCandidates.add(new BeanDefinitionHolder(beanDef, beanName)); } } // Return immediately if no @Configuration classes were found if (configCandidates.isEmpty()) { return; } // 对配置类候选进行排序,以满足依赖配置依赖顺序 // Sort by previously determined @Order value, if applicable Collections.sort(configCandidates, new Comparator<BeanDefinitionHolder>() { @Override public int compare(BeanDefinitionHolder bd1, BeanDefinitionHolder bd2) { int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition()); int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition()); return (i1 < i2) ? -1 : (i1 > i2) ? 1 : 0; } }); // 找出 beanName 的生成器 // Detect any custom bean name generation strategy supplied through the enclosing application context SingletonBeanRegistry sbr = null; if (registry instanceof SingletonBeanRegistry) { sbr = (SingletonBeanRegistry) registry; if (!this.localBeanNameGeneratorSet && sbr.containsSingleton(CONFIGURATION_BEAN_NAME_GENERATOR)) { BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR); this.componentScanBeanNameGenerator = generator; this.importBeanNameGenerator = generator; } } // Parse each @Configuration class // 创建 ConfigurationClassParser 来解析候选配置类定义 ConfigurationClassParser parser = new ConfigurationClassParser( this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, this.componentScanBeanNameGenerator, registry); // 当前待解析的候选配置类定义 Set<BeanDefinitionHolder> candidates = new LinkedHashSet<BeanDefinitionHolder>(configCandidates); // 当前已解析的配置类 Set<ConfigurationClass> alreadyParsed = new HashSet<ConfigurationClass>(configCandidates.size()); do { // 解析当前候选集里的所有配置类定义 parser.parse(candidates); // 验证解析的结果 parser.validate(); // 从解析获取解析得到的所有配置类 Set<ConfigurationClass> configClasses = new LinkedHashSet<ConfigurationClass>(parser.getConfigurationClasses()); // 移除已经处理过的配置类,因为下面要进行注册,防止重复注册 configClasses.removeAll(alreadyParsed); // 创建 ConfigurationClassBeanDefinitionReader,用于注册配置类 // reader 的创建应该是可以放到循环外的 // 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()); } // 注册配置类 this.reader.loadBeanDefinitions(configClasses); // 把本轮处理完的配置类加到已处理集合 alreadyParsed.addAll(configClasses); // 清除已处理的候选集,复用这个集合 candidates.clear(); // 从 registry 找出新加入、未处理过的配置类定义,加入当前候选集 if (registry.getBeanDefinitionCount() > candidateNames.length) { String[] newCandidateNames = registry.getBeanDefinitionNames(); Set<String> oldCandidateNames = new HashSet<String>(Arrays.asList(candidateNames)); Set<String> alreadyParsedClasses = new HashSet<String>(); for (ConfigurationClass configurationClass : alreadyParsed) { alreadyParsedClasses.add(configurationClass.getMetadata().getClassName()); } for (String candidateName : newCandidateNames) { if (!oldCandidateNames.contains(candidateName)) { // 过滤出新加入的 BeanDefinition bd = registry.getBeanDefinition(candidateName); if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) && !alreadyParsedClasses.contains(bd.getBeanClassName())) { // 找出未处理过的 candidates.add(new BeanDefinitionHolder(bd, candidateName)); } } } candidateNames = newCandidateNames; } } while (!candidates.isEmpty()); // 如果当前候选集不为空则继续下一轮处理 // 至此,通过 Java 类配置的 bean 定义都加载并注册到容器 // Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes if (sbr != null) { if (!sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) { sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry()); } } if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) { ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache(); } }
上述方法并未涉及某个配置类定义如何解析得到配置类、注册配置类定义的细节这一层的逻辑如下:
1. 从 registry
收集配置类定义放到 Set<BeanDefinitionHolder> candidates
作为处理的开始源;
2. 创建 ConfigurationClassParser parser
用于解析新的配置类定义和 ConfigurationClassBeanDefinitionReader reader
用于注册新的配置类定义;
3. 初始化已解析集合 alreadyParsed
;
4. 用 parser
解析候选集 candidates
,进行验证;
5. 得到解析结果 Set<ConfigurationClass> configClasses
,移除已经处理过的 Set<ConfigurationClass> alreadyParsed
;
6. 用 reader
把收集到的配置类集合 configClasses
注册到 registry
;
7. 把 configClasses
加入已解析集合 alreadyParsed
;
8. 清空候选集 candidates
,重新收集候选集 candidates
;
9. 如果收集到的候选集 candidates
不为空,继续跌代步骤 4 。
现在来看如何解析候选集, ConfigurationClassParser.parse(Set<BeanDefinitionHolder> configCandidates)
:
public void parse(Set<BeanDefinitionHolder> configCandidates) { this.deferredImportSelectors = new LinkedList<DeferredImportSelectorHolder>(); for (BeanDefinitionHolder holder : configCandidates) { BeanDefinition bd = holder.getBeanDefinition(); try { if (bd instanceof AnnotatedBeanDefinition) {// 基于 Java 类配置的都是走这里 parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName()); } else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) { parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName()); } else { parse(bd.getBeanClassName(), holder.getBeanName()); } } catch (BeanDefinitionStoreException ex) { throw ex; } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex); } } processDeferredImportSelectors(); } protected void processConfigurationClass(ConfigurationClass configClass) throws IOException { // ....删掉了防止重复处理的逻辑 // SourceClass 是一个简单的包装,允许被注解的源类以统一的方式进行处理,而不管它们是如何加载的 // 递归处理配置类和它的超类 SourceClass sourceClass = asSourceClass(configClass); do { // 处理给定的配置类,如果它有非 Object 的父类,则返回其父类,递归处理 sourceClass = doProcessConfigurationClass(configClass, sourceClass); } while (sourceClass != null); this.configurationClasses.put(configClass, configClass); } protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass) throws IOException { // 首先递归处理内部嵌套类 processMemberClasses(configClass, sourceClass); // 处理 @PropertySource 注解,把资源文件加载到 environment 里 for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), PropertySources.class, org.springframework.context.annotation.PropertySource.class)) { if (this.environment instanceof ConfigurableEnvironment) { processPropertySource(propertySource); } else { logger.warn("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() + "]. Reason: Environment must implement ConfigurableEnvironment"); } } // 处理 @ComponentScan 注解,如果有,立即执行扫描 Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class); if (!componentScans.isEmpty() && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) { for (AnnotationAttributes componentScan : componentScans) { // 如果配置类有 @ComponentScan 注解,立即执行扫描 Set<BeanDefinitionHolder> scannedBeanDefinitions = this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName()); // 检查扫描到的定义里是否有更多的配置类,有则递归 parse for (BeanDefinitionHolder holder : scannedBeanDefinitions) { BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition(); if (bdCand == null) { bdCand = holder.getBeanDefinition(); } if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) { parse(bdCand.getBeanClassName(), holder.getBeanName()); } } } } // 处理 @Import 注解 processImports(configClass, sourceClass, getImports(sourceClass), true); // 处理 @ImportResource 注解 if (sourceClass.getMetadata().isAnnotated(ImportResource.class.getName())) { AnnotationAttributes importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class); String[] resources = importResource.getStringArray("locations"); Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader"); for (String resource : resources) { String resolvedResource = this.environment.resolveRequiredPlaceholders(resource); configClass.addImportedResource(resolvedResource, readerClass); } } // 处理 @Bean 注解的方法 Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass); for (MethodMetadata methodMetadata : beanMethods) { configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass)); } // 处理接口的默认方法 processInterfaces(configClass, sourceClass); // 如果有,处理父类 if (sourceClass.getMetadata().hasSuperClass()) { String superclass = sourceClass.getMetadata().getSuperClassName(); if (!superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) { this.knownSuperclasses.put(superclass, configClass); // Superclass found, return its annotation metadata and recurse return sourceClass.getSuperClass(); } } // 没有父类,处理完成 return null; }
解析一个配置类的大致过程:
- 递归处理所有嵌套类;
-
处理源类上
@PropertySource
注解; -
处理
@ComponentScan
注解; -
处理
@Import
注解,通过自定义的ImportSelector/ImportBeanDefinitionRegistrar
接口实现来加载bean定义; -
处理
@ImportResource
注解; -
处理所有有
@Bean
注解的方法,方法的信息封装在MethodMetadata
,与所在的配置类一起封装成BeanMethod
添加到配置类的beanMethods
集合里; - 递归处理所实现的 interfaces 上的默认方法;
- 如果非 Object、非 java 自带类的父类,则返回进行递归处理;
上面的解析过程中有个很重要的角色是 SourceClass
,表示 SpringBoot 自己解析到的源文件信息。
// SourceClass 是一个简单的包装,允许被注解的源类以统一的方式进行处理,而不管它们是如何加载的 // 递归处理配置类和它的超类 SourceClass sourceClass = asSourceClass(configClass); do { // 处理给定的配置类,如果它有非 Object 的父类,则返回其父类,递归处理 sourceClass = doProcessConfigurationClass(configClass, sourceClass); } while (sourceClass != null);
在上述的递归处理过程中,sourceClass 一开始是配置类本身对应的源类的信息,后续是其父类的源类信息。
SourceClass
SourceClass 与 Class 啥区别?为什么需要它?
从上面代码的 SourceClass sourceClass = asSourceClass(configClass);
开始看:
SourceClass asSourceClass(String className) throws IOException { if (className.startsWith("java")) { // 对于核心Java类不使用ASM,因为肯定存在的,直接加载 try { return new SourceClass(this.resourceLoader.getClassLoader().loadClass(className)); } catch (ClassNotFoundException ex) { throw new NestedIOException("Failed to load class [" + className + "]", ex); } } return new SourceClass(this.metadataReaderFactory.getMetadataReader(className)); }
metadataReaderFactory 用完整类名构造一个 Resource,然后这个 Resource 和 ClassLoader 来构造一个 SimpleMetadataReader。SimpleMetadataReader 通过 ClassReader 来读取这个类的字节码文件,再通过 ASM 工具 AnnotationMetadataReadingVisitor 进行解析,得到关于这个类的元数据。这个过程绕过了JDK 的类加载机制,也就避免了一开始那个可能出现类找不到的问题。
有兴趣的可以继续看下 SimpleMetadataReader, MetadataReader
的唯一实现类:
final class SimpleMetadataReader implements MetadataReader { private final Resource resource; private final ClassMetadata classMetadata; private final AnnotationMetadata annotationMetadata; SimpleMetadataReader(Resource resource, ClassLoader classLoader) throws IOException { InputStream is = new BufferedInputStream(resource.getInputStream()); ClassReader classReader; try { classReader = new ClassReader(is); } catch (IllegalArgumentException ex) { throw new NestedIOException("ASM ClassReader failed to parse class file - " + "probably due to a new Java class file version that isn't supported yet: " + resource, ex); } finally { is.close(); } AnnotationMetadataReadingVisitor visitor = new AnnotationMetadataReadingVisitor(classLoader); classReader.accept(visitor, ClassReader.SKIP_DEBUG); this.annotationMetadata = visitor; // (since AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor) this.classMetadata = visitor; this.resource = resource; } // 省略其他代码 }
以上所述就是小编给大家介绍的《SpringBoot 启动分析(四) — 注解驱动的 Bean 定义加载》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- Spring 注解编程之模式注解
- Java注解之编译时注解
- Java注解之运行时注解
- Java中的注解-自定义注解
- Java注解Annotation与自定义注解详解
- Java 元注解及 Spring 组合注解应用
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。