彻底读懂Spring(二)你知道Spring是怎么解析配置类的吗?

推荐阅读:

Spring官网阅读系列

彻底读懂Spring(一)读源码,我们可以从第一行读起

Spring执行流程图如下:

如果图片显示不清楚可以访问如下链接查看高清大图:

Spring执行流程图

这个流程图会随着我们的学习不断的变得越来越详细,也会越来越复杂,希望在这个过程中我们都能朝着精通Spring的目标不断前进!

上篇文章我们学习了Spring中的第一行代码,我们已经知道了Spring中的第一行代码其实就是创建了一个AnnotatedBeanDefinitionReader对象,这个对象的主要作用就是注册bd(BeanDefinition)到容器中。并且在创建这个对象的过程中,Spring还为容器注册了开天辟地的几个bd,包括ConfigurationClassPostProcessorAutowiredAnnotationBeanPostProcessor等等。

那么在本文中,我们就一起来看看Spring中的第二行代码又做了些什么?

Spring中的第二行代码

第二行代码在上面的流程图中已经标注的非常明白了,就是

  1. this.scanner = new ClassPathBeanDefinitionScanner(this);

只是简单的创建了一个ClassPathBeanDefinitionScanner对象。那么这个ClassPathBeanDefinitionScanner有什么作用呢?从名字上来看好像就是这个对象来完成Spring中的扫描的,真的是这样吗?希望同学们能带着这两个问题往下看

ClassPathBeanDefinitionScanner源码分析

这个类名直译过来就是:类路径下的BeanDefinition的扫描器,所以我们就直接关注其扫描相关的方法,就是其中的doScan方法。其代码如下:

  1. // 这个方法会完成对指定包名下的class文件的扫描
  2. // basePackages:指定包名,是一个可变参数
  3. protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
  4. Assert.notEmpty(basePackages, "At least one base package must be specified");
  5. Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
  6. for (String basePackage : basePackages) {
  7. // 1.findCandidateComponents这个方法是实际完成扫描的方法,也是接下来我们要分析的方法
  8. Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
  9. for (BeanDefinition candidate : candidates) {、
  10. // 上篇文章中我们已经分析过了,完成了@Scope注解的解析
  11. // 参考《彻底读懂Spring(一)读源码,我们可以从第一行读起》
  12. ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
  13. candidate.setScope(scopeMetadata.getScopeName());
  14. String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
  15. if (candidate instanceof AbstractBeanDefinition) {
  16. // 2.如果你对BeanDefinition有一定了解的话,你肯定会知道这个判断一定会成立的,这意味着 // 所有扫描出来的bd都会执行postProcessBeanDefinition方法进行一些后置处理
  17. postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
  18. }
  19. if (candidate instanceof AnnotatedBeanDefinition) {
  20. // 3. 是不是一个AnnotatedBeanDefinition,如果是的话,还需要进行额外的处理
  21. AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
  22. }
  23. // 4.检查容器中是否已经有这个bd了,如果有就不进行注册了
  24. if (checkCandidate(beanName, candidate)) {
  25. // 下面这段逻辑在上篇文章中都已经分析过了,这里就直接跳过了
  26. BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
  27. definitionHolder =
  28. AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
  29. beanDefinitions.add(definitionHolder);
  30. registerBeanDefinition(definitionHolder, this.registry);
  31. }
  32. }
  33. }
  34. return beanDefinitions;
  35. }

上面这段代码主要做了四件事

  1. 通过findCandidateComponents方法完成扫描
  2. 判断扫描出来的bd是否是一个AbstractBeanDefinition,如果是的话执行postProcessBeanDefinition方法
  3. 判断扫描出来的bd是否是一个AnnotatedBeanDefinition,如果是的话执行processCommonDefinitionAnnotations方法
  4. 检查容器中是否已经有这个bd了,如果有就不进行注册了

接下来我们就一步步分析这个方法,搞明白ClassPathBeanDefinitionScanner到底能起到什么作用

1、通过findCandidateComponents方法完成扫描

findCandidateComponents方法源码如下:

  1. public Set<BeanDefinition> findCandidateComponents(String basePackage) {
  2. if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
  3. return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
  4. }
  5. else {
  6. // 正常情况下都是进入这个判断,对classpath下的class文件进行扫描
  7. return scanCandidateComponents(basePackage);
  8. }
  9. }
  • addCandidateComponentsFromIndex

不用过多关注这个方法。正常情况下Spring都是采用扫描classpath下的class文件来完成扫描,但是虽然基于classpath扫描速度非常快,但通过在编译时创建候选静态列表,可以提高大型应用程序的启动性能。在这种模式下,应用程序的所有模块都必须使用这种机制,因为当 ApplicationContext检测到这样的索引时,它将自动使用它而不是扫描类路径。

要生成索引,只需向包含组件扫描指令目标组件的每个模块添加附加依赖项即可:

Maven:

org.springframework
spring-context-indexer
5.0.6.RELEASE
true

大家有兴趣的话可以参考官网:https://docs.spring.io/spring/docs/5.1.14.BUILD-SNAPSHOT/spring-framework-reference/core.html#beans-scanning-index

这个依赖实在太大了,半天了拉不下来,我这里就不演示了

  • scanCandidateComponents(basePackage)

正常情况下我们的应用都是通过这个方法完成扫描的,其代码如下:

  1. private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
  2. // 用来存储返回的bd的集合
  3. Set<BeanDefinition> candidates = new LinkedHashSet<>();
  4. try {
  5. // 拼接成这种形式:classpath*:com.dmz.spring
  6. String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
  7. resolveBasePackage(basePackage) + '/' + this.resourcePattern;
  8. // 获取到所有的class文件封装而成的Resource对象
  9. Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
  10. // 遍历得到的所有class文件封装而成的Resource对象
  11. for (Resource resource : resources) {
  12. if (traceEnabled) {
  13. logger.trace("Scanning " + resource);
  14. }
  15. if (resource.isReadable()) {
  16. try {
  17. // 通过Resource构建一个MetadataReader对象,这个MetadataReader对象包含了对应class文件的解析出来的class的元信息以及注解元信息
  18. MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
  19. // 并不是所有的class文件文件都要被解析成为bd,只有被添加了注解(@Component,@Controller等)才是Spring中的组件
  20. if (isCandidateComponent(metadataReader)) {
  21. // 解析元信息(class元信息以及注解元信息)得到一个ScannedGenericBeanDefinition
  22. ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
  23. sbd.setResource(resource);
  24. sbd.setSource(resource);
  25. if (isCandidateComponent(sbd)) {
  26. if (debugEnabled) {
  27. logger.debug("Identified candidate component class: " + resource);
  28. }
  29. candidates.add(sbd);
  30. }
  31. // 省略多余的代码
  32. return candidates;
  33. }

Spring官网阅读(一)容器及实例化一文中,我画过这样一张图

从上图中可以看出,java class + configuration metadata 最终会转换为一个BenaDefinition,结合我们上面的代码分析可以知道,java class + configuration metadata实际上就是一个MetadataReader对象,而转换成一个BenaDefinition则是指通过这个MetadataReader对象创建一个ScannedGenericBeanDefinition

2、执行postProcessBeanDefinition方法

  1. protected void postProcessBeanDefinition(AbstractBeanDefinition beanDefinition, String beanName) {
  2. // 为bd中的属性设置默认值
  3. beanDefinition.applyDefaults(this.beanDefinitionDefaults);
  4. // 注解模式下这个值必定为null,使用XML配置时,
  5. if (this.autowireCandidatePatterns != null) {
  6. beanDefinition.setAutowireCandidate(PatternMatchUtils.simpleMatch(this.autowireCandidatePatterns, beanName));
  7. }
  8. }
  9. // 设置默认值
  10. public void applyDefaults(BeanDefinitionDefaults defaults) {
  11. setLazyInit(defaults.isLazyInit());
  12. setAutowireMode(defaults.getAutowireMode());
  13. setDependencyCheck(defaults.getDependencyCheck());
  14. setInitMethodName(defaults.getInitMethodName());
  15. setEnforceInitMethod(false);
  16. setDestroyMethodName(defaults.getDestroyMethodName());
  17. setEnforceDestroyMethod(false);
  18. }

可以看出,postProcessBeanDefinition方法最主要的功能就是给扫描出来的bd设置默认值,进一步填充bd中的属性

3、执行processCommonDefinitionAnnotations方法

这句代码将进一步解析class上的注解信息,Spring在创建这个abd的信息时候就已经将当前的class放入其中了,所有这行代码主要做的就是通过class对象获取到上面的注解(包括@Lazy,@Primary,@DependsOn注解等等),然后将得到注解中对应的配置信息并放入到bd中的属性中

4、注册BeanDefinition

彻底读懂Spring(一)读源码,我们可以从第一行读起的注册逻辑是一样的


通过上面的分析,我们已经知道了ClassPathBeanDefinitionScanner的作用,毋庸置疑,Spring肯定是通过这个类来完成扫描的,但是问题是,Spring是通过第二步创建的这个对象来完成扫描的吗?我们再来看看这个ClassPathBeanDefinitionScanner的创建过程:

  1. // 第一步
  2. public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) {
  3. this(registry, true);
  4. }
  5. // 第二步
  6. public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters) {
  7. this(registry, useDefaultFilters, getOrCreateEnvironment(registry));
  8. }
  9. // 第三步
  10. public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
  11. Environment environment) {
  12. this(registry, useDefaultFilters, environment,
  13. (registry instanceof ResourceLoader ? (ResourceLoader) registry : null));
  14. }
  15. // 第四步
  16. public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
  17. Environment environment, @Nullable ResourceLoader resourceLoader) {
  18. Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
  19. this.registry = registry;
  20. if (useDefaultFilters) {
  21. // 注册默认的扫描过滤规则(要被@Component注解修饰)
  22. registerDefaultFilters();
  23. }
  24. setEnvironment(environment);
  25. setResourceLoader(resourceLoader);
  26. }

在这个ClassPathBeanDefinitionScanner的创建过程中我们全程无法干涉,不能对这个ClassPathBeanDefinitionScanner进行任何配置。而我们在配置类上明明是可以对扫描的规则进行配置的,例如:

  1. @ComponentScan(value = "com.spring.study.springfx.aop.service", useDefaultFilters = true,
  2. excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {IndexService.class}))

所以Spring中肯定不是使用在这里创建的这个ClassPathBeanDefinitionScanner对象。

实际上真正完成扫描的时机是在我们流程图中的3-5-1步。完成扫描这个功能的类就是我们在上篇文章中所提到的ConfigurationClassPostProcessor。接下来我们就通过这个类,看看Spring到底是如何完成的扫描,这也是本文重点想要说明的问题

Spring是怎么解析配置类的?

1、解析时机分析

解析前Spring做了什么?

注册配置类

在分析扫描时机之前我们先回顾下之前的代码,整个程序的入口如下:

  1. public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) {
  2. this();
  3. register(annotatedClasses);
  4. refresh();
  5. }

其中在this()空参构造中Spring实例化了两个对象,一个是AnnotatedBeanDefinitionReader,在上篇文章中已经介绍过了,另外一个是ClassPathBeanDefinitionScanner,在前文中也进行了详细的分析。

在完成这两个对象的创建后,Spring紧接着就利用第一步中创建的AnnotatedBeanDefinitionReader去将配置类注册到了容器中。看到这里不知道大家有没有一个疑问,既然Spring是直接通过这种方式来注册配置类,为什么我们还非要在配置类上添加@Configuration注解呢?按照这个代码的话,我不在配置类上添加任何注解,也能将配置类注册到容器中,例如下面这样:

  1. public class Config {
  2. }
  3. public class Main {
  4. public static void main(String[] args) throws Exception {
  5. AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
  6. System.out.println(ac.getBean("config"));
  7. // 程序输出:com.spring.study.springfx.aop.Config@7b69c6ba
  8. // 意味着Config被注册到了容器中
  9. }
  10. }

大家仔细想想我这个问题,不妨带着这些疑问继续往下看。

调用refresh方法

在将配置类注册到容器中后,Spring紧接着又调用了refresh方法,其源码如下:

  1. public void refresh() throws BeansException, IllegalStateException {
  2. synchronized (this.startupShutdownMonitor) {
  3. // 这个方法主要做了以下几件事
  4. // 1.记录容器的启动时间,并将容器状态更改为激活
  5. // 2.调用initPropertySources()方法,主要用于web环境下初始化封装相关的web资源,比如将servletContext封装成为ServletContextPropertySource
  6. // 3.校验环境中必要的属性是否存在
  7. // 4.提供了一个扩展点可以提前放入一些事件,当applicationEventMulticaster这个bean被注册到容器中后就直接发布事件
  8. prepareRefresh();
  9. // 实际上获取的就是一个DefaultListableBeanFactory
  10. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
  11. // 为bean工厂设置一些属性
  12. prepareBeanFactory(beanFactory);
  13. try {
  14. // 提供给子类复写的方法,允许子类在这一步对beanFactory做一些后置处理
  15. postProcessBeanFactory(beanFactory);
  16. // 执行已经注册在容器中的bean工厂的后置处理器,在这里完成的扫描
  17. invokeBeanFactoryPostProcessors(beanFactory);
  18. // 后面的代码跟扫描无关,我们在之后的文章再介绍
  19. }
  20. // .....
  21. }
  22. }

大部分的代码都写了很详细的注释,对于其中两个比较复杂的方法我们单独分析

  1. prepareBeanFactory
  2. invokeBeanFactoryPostProcessors
prepareBeanFactory做了什么?
  1. protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
  2. // 设置classLoader,一般就是appClassLoader
  3. beanFactory.setBeanClassLoader(getClassLoader());
  4. // 设置el表达式解析器
  5. beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
  6. // 容器中添加一个属性编辑器注册表,关于属性编辑在《Spring官网阅读(十四)Spring中的BeanWrapper及类型转换》有过详细介绍,这里就不再赘述了
  7. beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));
  8. // 添加了一个bean的后置处理器,用于执行xxxAware方法
  9. beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
  10. // 自动注入模型下,如果bean中存在以下类型的依赖,不进行注入
  11. beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
  12. beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class);
  13. beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);
  14. beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);
  15. beanFactory.ignoreDependencyInterface(MessageSourceAware.class);
  16. beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);
  17. // 为什么我们能直接将ApplicationContext等一些对象直接注入到bean中呢?就是下面这段代码的作用啦!
  18. // Spring在进行属性注入时会从resolvableDependencies的map中查找是否有对应类型的bean存在,如果有的话就直接注入,下面这段代码就是将对应的bean放入到resolvableDependencies这个map中
  19. beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
  20. beanFactory.registerResolvableDependency(ResourceLoader.class, this);
  21. beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
  22. beanFactory.registerResolvableDependency(ApplicationContext.class, this);
  23. // 添加一个后置处理器,用于处理ApplicationListener
  24. beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));
  25. // 是否配置了LTW,也就是在类加载时期进行织入,一般都不会配置
  26. if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
  27. beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
  28. // 加载时期织入会配置一个临时的类加载器
  29. beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
  30. }
  31. // 配置一些默认的环境相关的bean
  32. if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) {
  33. beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());
  34. }
  35. if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) {
  36. beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties());
  37. }
  38. if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) {
  39. beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment());
  40. }
  41. }

上面这段代码整体来说还是非常简单的,逻辑也很清晰,就是在为beanFactory做一些配置,我们需要注意的是跟后置处理器相关的内容,可以看到在这一步一共注册了两个后置处理器

  • ApplicationContextAwareProcessor,用于执行xxxAware接口中的方法
  • ApplicationListenerDetector,保证监听器被添加到容器中

关于ApplicationListenerDetector请参考Spring官网阅读(八)容器的扩展点(三)(BeanPostProcessor)

invokeBeanFactoryPostProcessors做了什么?

这个方法的执行流程在Spring官网阅读(六)容器的扩展点(一)BeanFactoryPostProcessor已经做过非常详细的分析了,其执行流程如下

整的来说,它就是将容器中已经注册的bean工厂的后置处理器按照一定的顺序进行执行。

那么到这一步为止,容器中已经有哪些bean工厂的后置处理器呢?

还记得我们在上篇文章中提到的ConfigurationClassPostProcessor吗?在创建AnnotatedBeanDefinitionReader的过程中它对应的BeanDefinition就被注册到容器中了。接下来我们就来分析ConfigurationClassPostProcessor这个类的源码

ConfigurationClassPostProcessor源码分析

它实现了BeanDefinitionRegistryPostProcessor,所以首先执行它的postProcessBeanDefinitionRegistry方法,其源码如下

  1. public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
  2. // 生成一个注册表ID
  3. int registryId = System.identityHashCode(registry);
  4. //.....
  5. // 表明这个工厂已经经过了后置处理器了
  6. this.registriesPostProcessed.add(registryId);
  7. // 从名字来看这个方法是再对配置类的bd进行处理
  8. processConfigBeanDefinitions(registry);
  9. }

processConfigBeanDefinitions方法的代码很长,我们拆分一段段分析,先看第一段

第一段
  1. public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
  2. // ========第一段代码========
  3. List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
  4. // 大家可以思考一个问题,当前容器中有哪些BeanDefinition呢?
  5. // 这个地方应该能获取到哪些名字?
  6. String[] candidateNames = registry.getBeanDefinitionNames();
  7. for (String beanName : candidateNames) {
  8. // 根据名称获取到对应BeanDefinition
  9. BeanDefinition beanDef = registry.getBeanDefinition(beanName);
  10. // 省略日志打印
  11. // 检查是否是配置类,在这里会将对应的bd标记为FullConfigurationClass或者LiteConfigurationClass
  12. else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
  13. // 是配置类的话,将这个bd添加到configCandidates中
  14. configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
  15. }
  16. }
  17. // 没有配置类,直接返回
  18. if (configCandidates.isEmpty()) {
  19. return;
  20. }
  21. // 根据@Order注解进行排序
  22. configCandidates.sort((bd1, bd2) -> {
  23. int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
  24. int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
  25. return Integer.compare(i1, i2);
  26. });
  27. // .....

上面这段代码有这么几个问题:

  1. 当前容器中有哪些BeanDefinition

如果你看过上篇文章的话应该知道,在创建AnnotatedBeanDefinitionReader对象的时候Spring已经往容器中注册了5个BeanDefinition,再加上注册的配置类,那么此时容器中应该存在6个BeanDefinition,我们可以打个断点验证

不出所料,确实是6个

  1. checkConfigurationClassCandidate

代码如下:

  1. public static boolean checkConfigurationClassCandidate(
  2. BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
  3. String className = beanDef.getBeanClassName();
  4. if (className == null || beanDef.getFactoryMethodName() != null) {
  5. return false;
  6. }
  7. // 下面这一段都是为了获取一个AnnotationMetadata
  8. // AnnotationMetadata包含了对应class上的注解元信息以及class元信息
  9. AnnotationMetadata metadata;
  10. if (beanDef instanceof AnnotatedBeanDefinition &&
  11. className.equals(((AnnotatedBeanDefinition) beanDef).getMetadata().getClassName())) {
  12. // 已经解析过了,比如注册的配置类就属于这种,直接从bd中获取
  13. metadata = ((AnnotatedBeanDefinition) beanDef).getMetadata();
  14. }
  15. else if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) {
  16. // 拿到字节码重新解析获取到一个AnnotationMetadata
  17. Class<?> beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass();
  18. metadata = new StandardAnnotationMetadata(beanClass, true);
  19. }
  20. else {
  21. try {
  22. // class属性都没有,就根据className利用ASM字节码技术获取到这个AnnotationMetadata
  23. MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(className);
  24. metadata = metadataReader.getAnnotationMetadata();
  25. }
  26. catch (IOException ex) {
  27. return false;
  28. }
  29. }
  30. // 如果被@Configuration注解标注了,说明是一个FullConfigurationCandidate
  31. if (isFullConfigurationCandidate(metadata)) {
  32. beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
  33. }
  34. // 如果被这些注解标注了,@Component,@ComponentScan,@Import,@ImportResource
  35. // 或者方法上有@Bean注解,那么就是一个LiteConfigurationCandidate
  36. // 也就是说你想把这个类当配置类使用,但是没有添加@Configuration注解
  37. else if (isLiteConfigurationCandidate(metadata)) {
  38. beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
  39. }
  40. else {
  41. return false;
  42. }
  43. // 解析@Order注解,用于排序
  44. Integer order = getOrder(metadata);
  45. if (order != null) {
  46. beanDef.setAttribute(ORDER_ATTRIBUTE, order);
  47. }
  48. return true;
  49. }
第二段
  1. public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
  2. // 第一段
  3. // .....
  4. SingletonBeanRegistry sbr = null;
  5. if (registry instanceof SingletonBeanRegistry) {
  6. sbr = (SingletonBeanRegistry) registry;
  7. // beanName的生成策略,不重要
  8. if (!this.localBeanNameGeneratorSet) {
  9. BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR);
  10. if (generator != null) {
  11. this.componentScanBeanNameGenerator = generator;
  12. this.importBeanNameGenerator = generator;
  13. }
  14. }
  15. }
  16. if (this.environment == null) {
  17. this.environment = new StandardEnvironment();
  18. }
  19. // 核心目的就是创建这个ConfigurationClassParser对象
  20. ConfigurationClassParser parser = new ConfigurationClassParser(
  21. this.metadataReaderFactory, this.problemReporter, this.environment,
  22. this.resourceLoader, this.componentScanBeanNameGenerator, registry);
  23. // 第三段
  24. }

这段代码核心目的就是为了创建一个ConfigurationClassParser,这个类主要用于后续的配置类的解析。

第三段
  1. public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
  2. // 第一段,第二段
  3. // .....
  4. Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
  5. Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
  6. do {
  7. // 在第二段代码中创建了一个ConfigurationClassParser,这里就是使用这个parser来解析配置类
  8. // 我们知道扫描就是通过@ComponentScan,@ComponentScans来完成的,那么不出意外必定是在这里完成的扫描
  9. parser.parse(candidates);
  10. // 校验在解析过程是中是否发生错误,同时会校验@Configuration注解的类中的@Bean方法能否被复写(被final修饰或者访问权限为private都不能被复写),如果不能被复写会抛出异常,因为cglib代理要通过复写父类的方法来完成代理,后文会做详细介绍
  11. parser.validate();
  12. // 已经解析过的配置类
  13. Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
  14. // 移除已经解析过的配置类,防止重复加载了配置类中的bd
  15. configClasses.removeAll(alreadyParsed);
  16. // Read the model and create bean definitions based on its content
  17. if (this.reader == null) {
  18. this.reader = new ConfigurationClassBeanDefinitionReader(
  19. registry, this.sourceExtractor, this.resourceLoader, this.environment,
  20. this.importBeanNameGenerator, parser.getImportRegistry());
  21. }
  22. // 将通过解析@Bean,@Import等注解得到相关信息解析成bd被注入到容器中
  23. this.reader.loadBeanDefinitions(configClasses);
  24. alreadyParsed.addAll(configClasses);
  25. candidates.clear();
  26. // 如果大于,说明容器中新增了一些bd,那么需要重新判断新增的bd是否是配置类,如果是配置类,需要再次解析
  27. if (registry.getBeanDefinitionCount() > candidateNames.length) {
  28. String[] newCandidateNames = registry.getBeanDefinitionNames();
  29. Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
  30. Set<String> alreadyParsedClasses = new HashSet<>();
  31. for (ConfigurationClass configurationClass : alreadyParsed) {
  32. alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
  33. }
  34. for (String candidateName : newCandidateNames) {
  35. if (!oldCandidateNames.contains(candidateName)) {
  36. BeanDefinition bd = registry.getBeanDefinition(candidateName);
  37. if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
  38. !alreadyParsedClasses.contains(bd.getBeanClassName())) {
  39. candidates.add(new BeanDefinitionHolder(bd, candidateName));
  40. }
  41. }
  42. }
  43. candidateNames = newCandidateNames;
  44. }
  45. }
  46. while (!candidates.isEmpty());
  47. // 注册ImportRegistry到容器中
  48. // 当通过@Import注解导入一个全配置类A(被@Configuration注解修饰的类),A可以实现ImportAware接口
  49. // 通过这个Aware可以感知到是哪个类导入的A
  50. if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
  51. sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
  52. }
  53. if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
  54. ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
  55. }
  56. }

2、解析源码分析

在上面的源码分析中,我们已经能够确定了Spring是通过ConfigurationClassParserparse方法来完成对配置类的解析的。Spring对类的取名可以说是很讲究了,ConfigurationClassParser直译过来就是配置类解析器。接着我们就来看看它的源码

2.1、parse方法
  1. public void parse(Set<BeanDefinitionHolder> configCandidates) {
  2. this.deferredImportSelectors = new LinkedList<>();
  3. // 遍历所有的配置类,一个个完成解析
  4. for (BeanDefinitionHolder holder : configCandidates) {
  5. BeanDefinition bd = holder.getBeanDefinition();
  6. try {
  7. // 三个判断最终都会进入到同一个方法---->processConfigurationClass方法
  8. if (bd instanceof AnnotatedBeanDefinition) {
  9. parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
  10. }
  11. else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
  12. parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
  13. }
  14. else {
  15. parse(bd.getBeanClassName(), holder.getBeanName());
  16. }
  17. }
  18. catch (BeanDefinitionStoreException ex) {
  19. throw ex;
  20. }
  21. catch (Throwable ex) {
  22. throw new BeanDefinitionStoreException(
  23. "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
  24. }
  25. }
  26. // 对ImportSelector进行延迟处理
  27. processDeferredImportSelectors();
  28. }
2.2、processConfigurationClass方法
  1. protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
  2. // 解析@Conditional注解,判断是否需要解析
  3. if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
  4. return;
  5. }
  6. // 判断解析器是否已经解析过这个配置类了
  7. ConfigurationClass existingClass = this.configurationClasses.get(configClass);
  8. // 不为null,说明已经解析过了
  9. if (existingClass != null) {
  10. // 如果这个要被解析的配置类是被@Import注解导入的
  11. if (configClass.isImported()) {
  12. // 并且解析过的配置类也是被导入的
  13. if (existingClass.isImported()) {
  14. // 那么这个配置类的导入类集合中新增当前的配置类的导入类,(A通过@Import导入了B,那么A就是B的导入类,B被A导入)
  15. existingClass.mergeImportedBy(configClass);
  16. }
  17. // Otherwise ignore new imported config class; existing non-imported class overrides it.
  18. // 如果已经解析过的配置类不是被导入的,那么直接忽略新增的这个被导入的配置类。也就是说如果一个配置类同时被@Import导入以及正常的
  19. // 添加到容器中,那么正常添加到容器中的配置类会覆盖被导入的类
  20. return;
  21. }
  22. else {
  23. // Explicit bean definition found, probably replacing an import.
  24. // Let's remove the old one and go with the new one.
  25. // 就是说新要被解析的这个配置类不是被导入的,所以这种情况下,直接移除调原有的解析的配置类
  26. // 为什么不是remove(existingClass)呢?可以看看hashCode跟equals方法
  27. // remove(existingClass)跟remove(configClass)是等价的
  28. this.configurationClasses.remove(configClass);
  29. this.knownSuperclasses.values().removeIf(configClass::equals);
  30. }
  31. }
  32. // Recursively process the configuration class and its superclass hierarchy.
  33. // 下面这段代码主要是递归的处理配置类及其父类
  34. // 将配置类封装成一个SourceClass方便进行统一的处理
  35. SourceClass sourceClass = asSourceClass(configClass);
  36. do {
  37. // doxxx方法,真正干活的方法,对配置类进行处理,返回值是当前这个类的父类
  38. sourceClass = doProcessConfigurationClass(configClass, sourceClass);
  39. }
  40. while (sourceClass != null);
  41. this.configurationClasses.put(configClass, configClass);
  42. }
2.3、doProcessConfigurationClass方法
  1. protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
  2. throws IOException {
  3. // Recursively process any member (nested) classes first
  4. // 递归处理内部类
  5. processMemberClasses(configClass, sourceClass);
  6. // Process any @PropertySource annotations
  7. // 处理@PropertySources跟@PropertySource注解,将对应的属性资源添加容器中(实际上添加到environment中)
  8. for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
  9. sourceClass.getMetadata(), PropertySources.class,
  10. org.springframework.context.annotation.PropertySource.class)) {
  11. if (this.environment instanceof ConfigurableEnvironment) {
  12. processPropertySource(propertySource);
  13. }
  14. else {
  15. logger.warn("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
  16. "]. Reason: Environment must implement ConfigurableEnvironment");
  17. }
  18. }
  19. // Process any @ComponentScan annotations、
  20. // 处理@ComponentScan,@ComponentScans注解,真正进行扫描的地方就是这里
  21. Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
  22. sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
  23. if (!componentScans.isEmpty() &&
  24. !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
  25. for (AnnotationAttributes componentScan : componentScans) {
  26. // The config class is annotated with @ComponentScan -> perform the scan immediately
  27. // 核心代码,在这里完成的扫描
  28. Set<BeanDefinitionHolder> scannedBeanDefinitions =
  29. this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
  30. // Check the set of scanned definitions for any further config classes and parse recursively if needed
  31. // 检查扫描出来的bd是否是配置类,如果是配置类递归进行解析
  32. for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
  33. // 一般情况下getOriginatingBeanDefinition获取到的都是null
  34. // 什么时候不为null呢?,参考:ScopedProxyUtils.createScopedProxy方法
  35. // 在创建一个代理的bd时不会为null
  36. BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
  37. if (bdCand == null) {
  38. bdCand = holder.getBeanDefinition();
  39. }
  40. // 判断扫描出来的bd是否是一个配置类,如果是的话继续递归处理
  41. if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
  42. parse(bdCand.getBeanClassName(), holder.getBeanName());
  43. }
  44. }
  45. }
  46. }
  47. // Process any @Import annotations
  48. // 处理@Import注解
  49. processImports(configClass, sourceClass, getImports(sourceClass), true);
  50. // Process any @ImportResource annotations
  51. // 处理@ImportResource注解
  52. AnnotationAttributes importResource =
  53. AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
  54. if (importResource != null) {
  55. String[] resources = importResource.getStringArray("locations");
  56. Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
  57. for (String resource : resources) {
  58. String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
  59. configClass.addImportedResource(resolvedResource, readerClass);
  60. }
  61. }
  62. // Process individual @Bean methods
  63. // 处理@Bean注解
  64. // 获取到被@Bean标注的方法
  65. Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
  66. for (MethodMetadata methodMetadata : beanMethods) {
  67. // 添加到configClass中
  68. configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
  69. }
  70. // Process default methods on interfaces
  71. // 处理接口中的default方法
  72. processInterfaces(configClass, sourceClass);
  73. // Process superclass, if any
  74. // 返回父类,进行递归处理
  75. if (sourceClass.getMetadata().hasSuperClass()) {
  76. String superclass = sourceClass.getMetadata().getSuperClassName();
  77. if (superclass != null && !superclass.startsWith("java") &&
  78. !this.knownSuperclasses.containsKey(superclass)) {
  79. this.knownSuperclasses.put(superclass, configClass);
  80. // Superclass found, return its annotation metadata and recurse
  81. return sourceClass.getSuperClass();
  82. }
  83. }
  84. // No superclass -> processing is complete
  85. return null;
  86. }

可以看到,在doProcessConfigurationClass真正完成了对配置类的解析,一共做了下面几件事

  1. 解析配置类中的内部类,看内部类中是否有配置类,如果有进行递归处理
  2. 处理配置类上的@PropertySources跟@PropertySource注解
  3. 处理@ComponentScan,@ComponentScans注解
  4. 处理@Import注解
  5. 处理@ImportResource注解
  6. 处理@Bean注解
  7. 处理接口中的default方法
  8. 返回父类,让外部的循环继续处理当前配置类的父类

我们逐一进行分析

2.4、处理配置类中的内部类

这段代码非常简单,限于篇幅原因我这里就不再专门分析了,就是获取到当前配置类中的所有内部类,然后遍历所有的内部类,判断是否是一个配置类,如果是配置类的话就递归进行解析

2.5、处理@PropertySource注解

代码也非常简单,根据注解中的信息加载对应的属性文件然后添加到容器中

2.6、处理@ComponentScan注解

这个段我们就需要看一看了,Spring在这里完成的扫描,我们直接查看其核心方法,org.springframework.context.annotation.ComponentScanAnnotationParser#parse

  1. public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
  2. // 第一步就创建了一个ClassPathBeanDefinitionScanner对象
  3. // 在这里我们就知道了,Spring在进行扫描时没有使用在最开始的时候创建的那个对象进行扫描
  4. ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
  5. componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
  6. // 解析成bd时采用的beanName的生成规则
  7. Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
  8. boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
  9. scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :
  10. BeanUtils.instantiateClass(generatorClass));
  11. // 配置这个扫描规则下的ScopedProxyMode的默认值
  12. ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy");
  13. if (scopedProxyMode != ScopedProxyMode.DEFAULT) {
  14. scanner.setScopedProxyMode(scopedProxyMode);
  15. }
  16. else {
  17. Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver");
  18. scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass));
  19. }
  20. // 配置扫描器的匹配规则
  21. scanner.setResourcePattern(componentScan.getString("resourcePattern"));
  22. // 配置扫描器需要扫描的组件
  23. for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {
  24. for (TypeFilter typeFilter : typeFiltersFor(filter)) {
  25. scanner.addIncludeFilter(typeFilter);
  26. }
  27. }
  28. // 配置扫描器不需要扫描的组件
  29. for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {
  30. for (TypeFilter typeFilter : typeFiltersFor(filter)) {
  31. scanner.addExcludeFilter(typeFilter);
  32. }
  33. }
  34. // 配置默认是否进行懒加载
  35. boolean lazyInit = componentScan.getBoolean("lazyInit");
  36. if (lazyInit) {
  37. scanner.getBeanDefinitionDefaults().setLazyInit(true);
  38. }
  39. // 配置扫描器扫描的包名
  40. Set<String> basePackages = new LinkedHashSet<>();
  41. String[] basePackagesArray = componentScan.getStringArray("basePackages");
  42. for (String pkg : basePackagesArray) {
  43. String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
  44. ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
  45. Collections.addAll(basePackages, tokenized);
  46. }
  47. for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
  48. basePackages.add(ClassUtils.getPackageName(clazz));
  49. }
  50. if (basePackages.isEmpty()) {
  51. basePackages.add(ClassUtils.getPackageName(declaringClass));
  52. }
  53. // 排除自身
  54. scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
  55. @Override
  56. protected boolean matchClassName(String className) {
  57. return declaringClass.equals(className);
  58. }
  59. });
  60. // 在完成对扫描器的配置后,直接调用其doScan方法进行扫描
  61. return scanner.doScan(StringUtils.toStringArray(basePackages));
  62. }

看到了吧,第一步就创建了一个ClassPathBeanDefinitionScanner,紧接着通过解析注解,对这个扫描器进行了各种配置,然后调用doScan方法完成了扫描。

2.7、处理@Import注解
  1. private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
  2. Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
  3. // 没有要导入的类,直接返回
  4. if (importCandidates.isEmpty()) {
  5. return;
  6. }
  7. // checkForCircularImports:Spring中写死的为true,需要检查循环导入
  8. // isChainedImportOnStack方法:检查导入栈中是否存在了这个configClass,如果存在了说明
  9. // 出现了A import B,B import A的情况,直接抛出异常
  10. if (checkForCircularImports && isChainedImportOnStack(configClass)) {
  11. this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
  12. }
  13. else {
  14. // 没有出现循环导入,先将当前的这个配置类加入到导入栈中
  15. this.importStack.push(configClass);
  16. try {
  17. // 遍历所有要导入的类
  18. for (SourceClass candidate : importCandidates) {
  19. // 如果要导入的类是一个ImportSelector
  20. if (candidate.isAssignable(ImportSelector.class)) {
  21. // Candidate class is an ImportSelector -> delegate to it to determine imports
  22. // 反射创建这个ImportSelector
  23. Class<?> candidateClass = candidate.loadClass();
  24. ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
  25. // 执行xxxAware方法
  26. ParserStrategyUtils.invokeAwareMethods(
  27. selector, this.environment, this.resourceLoader, this.registry);
  28. // 如果是一个DeferredImportSelector,添加到deferredImportSelectors集合中去
  29. // 在所有的配置类完成解析后再去处理deferredImportSelectors集合中的ImportSelector
  30. if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {
  31. this.deferredImportSelectors.add(
  32. new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
  33. }
  34. else {
  35. // 不是一个DeferredImportSelector,那么通过这个ImportSelector获取到要导入的类名
  36. String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
  37. // 将其转换成SourceClass
  38. Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
  39. // 递归处理要导入的类,一般情况下这个时候进入的就是另外两个判断了
  40. processImports(configClass, currentSourceClass, importSourceClasses, false);
  41. }
  42. }
  43. else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
  44. // Candidate class is an ImportBeanDefinitionRegistrar ->
  45. // delegate to it to register additional bean definitions
  46. // 如果是一个ImportBeanDefinitionRegistrar
  47. // 先通过反射创建这个ImportBeanDefinitionRegistrar
  48. Class<?> candidateClass = candidate.loadClass();
  49. ImportBeanDefinitionRegistrar registrar =
  50. BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
  51. // 再执行xxxAware方法
  52. ParserStrategyUtils.invokeAwareMethods(
  53. registrar, this.environment, this.resourceLoader, this.registry);
  54. // 最后将其添加到configClass的importBeanDefinitionRegistrars集合中
  55. // 之后会统一调用其ImportBeanDefinitionRegistrar的registerBeanDefinitions方法,将对应的bd注册到容器中
  56. configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
  57. }
  58. else {
  59. // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
  60. // process it as an @Configuration class
  61. // 既不是一个ImportSelector也不是一个ImportBeanDefinitionRegistrar,直接导入一个普通类
  62. // 并将这个类作为配置类进行递归处理
  63. this.importStack.registerImport(
  64. currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
  65. processConfigurationClass(candidate.asConfigClass(configClass));
  66. }
  67. }
  68. }
  69. catch (BeanDefinitionStoreException ex) {
  70. throw ex;
  71. }
  72. catch (Throwable ex) {
  73. throw new BeanDefinitionStoreException(
  74. "Failed to process import candidates for configuration class [" +
  75. configClass.getMetadata().getClassName() + "]", ex);
  76. }
  77. finally {
  78. // 在循环前我们将其加入了导入栈中,循环完成后将其弹出,主要是为了处理循环导入
  79. this.importStack.pop();
  80. }
  81. }
  82. }
2.8、处理@ImportResource注解

代码也很简单,在指定的位置加载资源,然后添加到configClass中。一般情况下,我们通过@ImportResource注解导入的就是一个XML配置文件。将这个Resource添加到configClass后,Spring会在后文中解析这个XML配置文件然后将其中的bd注册到容器中,可以参考org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitions方法

2.9、处理@Bean注解

将配置类中所有的被@Bean标注的方法添加到configClass的BeanMethod集合中

2.10、处理接口中的default方法

代码也很简单,Java8中接口能定义default方法,这里就是处理接口中的default方法,看其是否有@Bean标注的方法

到此为止,我们分析完了整个解析的过程。可以发现Spring将所有解析到的配置信息都存储在了ConfigurationClass类中,但是到目前为止这些存储的信息都没有进行使用。那么Spring是在哪里使用的这些信息呢?回到我们的第三段代码,其中有一行代码如图所示:

也就是在这里Spring完成了对解析好的配置类的信息处理。

2.11、加载解析完成的配置信息
  1. // configurationModel:被解析完成了配置类集合,其中保存了@Bean注解解析信息,@Import注解解析信息等等
  2. public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
  3. TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
  4. for (ConfigurationClass configClass : configurationModel) {
  5. // 调用这个方法完成的加载
  6. loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
  7. }
  8. }
  1. private void loadBeanDefinitionsForConfigurationClass(
  2. ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
  3. // 判断是否需要跳过,例如A导入了B,A不满足加载的条件需要被跳过,那么B也应该被跳过
  4. if (trackedConditionEvaluator.shouldSkip(configClass)) {
  5. String beanName = configClass.getBeanName();
  6. if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
  7. this.registry.removeBeanDefinition(beanName);
  8. }
  9. this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
  10. return;
  11. }
  12. // 判断配置类是否是被导入进来的,实际的代码就是判断解析出来的configclass中的importedBy集合是否为空
  13. // 那么这个importedBy集合是做什么的呢?
  14. // 例如A通过@Import导入了B,那么解析B得到得configclass中得importedBy集合就包含了A
  15. // 简而言之,importedBy集合就是导入了这个类的其它类(可能同时被多个类导入)
  16. // 在前文中我们也分析过了,被多个类同时导入时会调用mergeImportedBy方法在集合中添加一个元素
  17. if (configClass.isImported()) {
  18. registerBeanDefinitionForImportedConfigurationClass(configClass);
  19. }
  20. // 解析@Bean标注的Method得到对应的BeanDefinition并注册到容器中
  21. for (BeanMethod beanMethod : configClass.getBeanMethods()) {
  22. loadBeanDefinitionsForBeanMethod(beanMethod);
  23. }
  24. // 解析导入的配置文件,并将从中得到的bd注册到容器中
  25. loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
  26. // 执行configClass中的所有ImportBeanDefinitionRegistrar的registerBeanDefinitions方法
  27. loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
  28. }

这段代码阅读起来还是非常简单的,这里我就跟大家一起看下BeanMethod的相关代码,主要是为了让大家对BeanDefinition的理解能够更加深入,其源码如下:

  1. private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
  2. ConfigurationClass configClass = beanMethod.getConfigurationClass();
  3. MethodMetadata metadata = beanMethod.getMetadata();
  4. String methodName = metadata.getMethodName();
  5. // 根据@Conditional注解判断是否需要跳过
  6. if (this.conditionEvaluator.shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN)) {
  7. configClass.skippedBeanMethods.add(methodName);
  8. return;
  9. }
  10. if (configClass.skippedBeanMethods.contains(methodName)) {
  11. return;
  12. }
  13. // 获取@Bean注解中的属性
  14. AnnotationAttributes bean = AnnotationConfigUtils.attributesFor(metadata, Bean.class);
  15. Assert.state(bean != null, "No @Bean annotation attributes");
  16. // 从这里可以看出,如果没有配置beanName,默认会取方法名称作为beanName
  17. List<String> names = new ArrayList<>(Arrays.asList(bean.getStringArray("name")));
  18. String beanName = (!names.isEmpty() ? names.remove(0) : methodName);
  19. // 注册别名
  20. for (String alias : names) {
  21. this.registry.registerAlias(beanName, alias);
  22. }
  23. // isOverriddenByExistingDefinition这个方法判断的是当前注册的bd是否被原有的存在的bd所覆盖了
  24. // 什么是覆盖呢?后文中我们详细分析
  25. if (isOverriddenByExistingDefinition(beanMethod, beanName)) {
  26. // 满足下面这个if的话意味着@Bean创建的bean跟@Bean标注的方法所所在的配置类的名称一样了,这种情况下直接抛出异常
  27. if (beanName.equals(beanMethod.getConfigurationClass().getBeanName())) {
  28. throw new BeanDefinitionStoreException(beanMethod.getConfigurationClass().getResource().getDescription(),
  29. beanName, "Bean name derived from @Bean method '" + beanMethod.getMetadata().getMethodName() +
  30. "' clashes with bean name for containing configuration class; please make those names unique!");
  31. }
  32. return;
  33. }
  34. // 创建一个ConfigurationClassBeanDefinition,从这里可以看出通过@Bean创建的Bean所对应的bd全是ConfigurationClassBeanDefinition
  35. ConfigurationClassBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass, metadata);
  36. beanDef.setResource(configClass.getResource());
  37. beanDef.setSource(this.sourceExtractor.extractSource(metadata, configClass.getResource()));
  38. // @Bean是静态的,那么只需要知道静态方法所在类名以及方法名就能执行这个方法了
  39. if (metadata.isStatic()) {
  40. // static @Bean method
  41. beanDef.setBeanClassName(configClass.getMetadata().getClassName());
  42. beanDef.setFactoryMethodName(methodName);
  43. }
  44. else {
  45. //
  46. // instance @Bean method
  47. beanDef.setFactoryBeanName(configClass.getBeanName());
  48. beanDef.setUniqueFactoryMethodName(methodName);
  49. }
  50. // 接下来的代码就是设置一些bd的属性,然后将bd注册到容器中,相关的源码在之前的文章中已经分析过了
  51. // 这里我就不在分析了,参考本文推荐阅读文章的《读源码,我们可以从第一行读起》
  52. //.....
  53. }

上面这个方法的主要目的就是将@Bean标注的方法解析成BeandDefinition然后注册到容器中。关于这个方法我们可以对比下之前分析过的org.springframework.context.annotation.AnnotatedBeanDefinitionReader#doRegisterBean方法。对比我们可以发现,这两个方法最大的不同在于一个是基于Class对象的,而另一个则是基于Method对象的。

正因为如此,所有它们有一个很大的不同点在于BeanDefinition中BeanClasss属性的设置。可以看到,对于@Bean形式创建的Bean其BeanDefinition中是没有设置BeanClasss属性的,但是额外设置了其它的属性

  • 静态方法下,设置了BeanClassName以及FactoryMethodName属性,其中的BeanClassName是静态方法所在类的类名,FactoryMethodName是静态方法的方法名
  • 实例方法下,设置了FactoryBeanName以及FactoryMethodName属性,其中FactoryBeanName是实例对应的Bean的名称,而FactoryMethodName是实例中对应的方法名

之所以不用设置BeanClasss属性是因为,通过指定的静态方法或者指定的实例中的方法也能唯一确定一个Bean。

除此之外,注册@Bean形式得到的BeanDefinition时,还进行了一个isOverriddenByExistingDefinition(beanMethod, beanName)方法的判断,这个方法的主要作用是判断当前要注册的bean是否被之前已经存在的Bean覆盖了。但是在直接通过AnnotatedBeanDefinitionReader#doRegisterBean方法注册Bean时是没有进行这个判断的,如果存在就直接覆盖了,而不会用之前的bd来覆盖现在要注册的bd。这是为什么呢?据笔者自己的理解,是因为Spring将Bean也是分成了三六九等的,通过@Bean方式得到的bd可以覆盖扫描出来的普通bd(ScannedGenericBeanDefinition),但是不能覆盖配置类,所以当已经存在的bd是一个ScannedGenericBeanDefinition时,那么直接进行覆盖,但是当已经存在的bd是一个配置类时,就不能进行覆盖了,要使用已经存在的bd来覆盖本次要注册的bd。

到此为止,我们就完成了Spring中的整个配置类解析、注册的相关源码分析,不过还没完,我们还得解决一个问题,就是为什么要在配置类上添加@Configuration注解,在之前的源码分析中我们知道,添加@Configuration注解的作用就是讲配置类标志成了一个full configurationClass,这个的目的是什么呢?本来是打算一篇文章写完的,不过实在是太长了,接近6w字,所以还是拆成了两篇,预知后事如何,请看下文:

配置类为什么要添加@Configuration注解呢?

总结

我们结合上篇文章彻底读懂Spring(一)读源码,我们可以从第一行读起整理下目前Spring的执行流程

原图地址: 原图

清晰的知道了执行的流程,我们再来回想下postProcessBeanDefinitionRegistry做了什么。



码字不易,对你有帮助的话记得点个赞,关注一波哦,万分感谢!

你知道Spring是怎么解析配置类的吗?的更多相关文章

  1. Spring5源码解析6-ConfigurationClassParser 解析配置类

    ConfigurationClassParser 在ConfigurationClassPostProcessor#processConfigBeanDefinitions方法中创建了Configur ...

  2. 【Spring】简述@Configuration配置类注册BeanDefinition到Spring容器的过程

    概述 本文以SpringBoot应用为基础,尝试分析基于注解@Configuration的配置类是如何向Spring容器注册BeanDefinition的过程 其中主要分析了 Configuratio ...

  3. Spring源码解析——核心类介绍

    前言: Spring用了这么久,虽然Spring的两大核心:IOC和AOP一直在用,但是始终没有搞懂Spring内部是怎么去实现的,于是决定撸一把Spring源码,前前后后也看了有两边,很多东西看了就 ...

  4. Spring源码解析-核心类之XmlBeanDefinitionReader

    XmlBeanDefinitionReader XML配置文件的读取是 Spring 中重要的功能,因为 Spring 的大部分功能都是以配置作为切入点的,那么我们可以从 XmlBeanDefinit ...

  5. Spring源码解析-核心类之XmlBeanFactory 、DefaultListableBeanFactory

    DefaultListableBeanFactory XmlBeanFactory 继承自 DefaultListableBeanFactory , 而 DefaultListableBeanFact ...

  6. spring data redis的配置类RedisConfig

    package com.tz.config; import org.springframework.context.annotation.Bean; import org.springframewor ...

  7. spring boot 排除个别配置类的代码

    废话不说,直接上代码 @SpringBootApplication(exclude={DataSourceAutoConfiguration.class,HibernateJpaAutoConfigu ...

  8. 配置类为什么要添加@Configuration注解呢?

    配置类为什么要添加@Configuration注解呢? 本系列文章: 读源码,我们可以从第一行读起 你知道Spring是怎么解析配置类的吗? 推荐阅读: Spring官网阅读 | 总结篇 Spring ...

  9. Spring源码解析之ConfigurationClassPostProcessor(三)

    在上一章笔者介绍了ConfigurationClassParser.doProcessConfigurationClass(...)方法,在这个方法里调用了processImports(...)方法处 ...

随机推荐

  1. windows powershell校验下载的文件MD5和SHA1值

    Windows自带MD5 SHA1 SHA256命令行工具 certutil -hashfile <文件名> <hash类型> 打开windows powershell,进入到 ...

  2. 【Android】EventReminder使用教程(日历事件导出封装库)

    碎碎念 为啥要写这个库呢? 尝试自己写一个库调用,学习一下这个流程,为以后做准备 日历库在网上的资料太少了,而这个功能却又很实用 自己做的项目都会涉及到事件导出功能,不想重复写代码 使用方法 引入 在 ...

  3. 阿里Canal框架数据库同步-实战教程

    一.Canal简介: canal是阿里巴巴旗下的一款开源项目,纯Java开发.基于数据库增量日志解析,提供增量数据订阅&消费,目前主要支持了MySQL(也支持mariaDB). 二.背景介绍: ...

  4. codeforces 122C perfect team

    You may have already known that a standard ICPC team consists of exactly three members. The perfect ...

  5. 浏览器中 JS 的事件循环机制

    目录 事件循环机制 宏任务与微任务 实例分析 参考 1.事件循环机制 浏览器执行JS代码大致可以分为三个步骤,而这三个步骤的往复构成了JS的事件循环机制(如图). 第一步:主线程(JS引擎线程)中执行 ...

  6. 从hfctf学习JWT伪造

    本文作者:Ch3ng easy_login 简单介绍一下什么是JWT Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519) ...

  7. PHP函数:debug_backtrace

    debug_backtrace()  - 产生一条 PHP 的回溯跟踪(backtrace). 说明: debug_backtrace ([ int $options = DEBUG_BACKTRAC ...

  8. 如何将dotnet core webapi发布到docker中…

    如何将dotnet core webapi发布到docker中 今天想起来撸一下docker,中途还是遇到些问题,但是这些问题都是由于路径什么的导致不正确,在这儿还是记录下操作过程,今天是基于wind ...

  9. IDEA惊天bug:进程已结束,退出代码-1073741819 (0xC0000005)

    由于昨天要写的文章没有写完,于是今天早上我四点半就"自然醒"了,心里面有事,睡觉也不安稳.洗漱完毕后,我打开电脑,正襟危坐,摆出一副要干架的态势,不能再拖了. 要写的文章中涉及到一 ...

  10. Linux-LAMP虚拟主机配置

    1.配置用户认证 <Directory /data/discuz/passwd> AllowOverride AuthConfig AuthName "自定义的" Au ...