你知道Spring是怎么解析配置类的吗?(2)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 你知道Spring是怎么解析配置类的吗?(2)

Spring是怎么解析配置类的?


1、解析时机分析


解析前Spring做了什么?


注册配置类


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

public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) {
    this();
    register(annotatedClasses);
    refresh();
}

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


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

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

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


调用refresh方法


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

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

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

1.prepareBeanFactory

2.invokeBeanFactoryPostProcessors


prepareBeanFactory做了什么?

protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
    // 设置classLoader,一般就是appClassLoader
    beanFactory.setBeanClassLoader(getClassLoader());
    // 设置el表达式解析器
    beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
    // 容器中添加一个属性编辑器注册表,关于属性编辑在《Spring官网阅读(十四)Spring中的BeanWrapper及类型转换》有过详细介绍,这里就不再赘述了
    beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));
    //  添加了一个bean的后置处理器,用于执行xxxAware方法
    beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
    // 对以下类型的依赖,不进行依赖检查,不进行依赖检查也就不会进行自动注入
    beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
    beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class);
    beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);
    beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);
    beanFactory.ignoreDependencyInterface(MessageSourceAware.class);
    beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);
    // 为什么我们能直接将ApplicationContext等一些对象直接注入到bean中呢?就是下面这段代码的作用啦!
    // Spring在进行属性注入时会从resolvableDependencies的map中查找是否有对应类型的bean存在,如果有的话就直接注入,下面这段代码就是将对应的bean放入到resolvableDependencies这个map中
    beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
    beanFactory.registerResolvableDependency(ResourceLoader.class, this);
    beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
    beanFactory.registerResolvableDependency(ApplicationContext.class, this);
    // 添加一个后置处理器,用于处理ApplicationListener
    beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));
    // 是否配置了LTW,也就是在类加载时期进行织入,一般都不会配置
    if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
        beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
        // 加载时期织入会配置一个临时的类加载器
        beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
    }
    // 配置一些默认的环境相关的bean
    if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) {
        beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());
    }
    if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) {
        beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties());
    }
    if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) {
        beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment());
    }
}

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


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

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


invokeBeanFactoryPostProcessors做了什么?


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

微信图片_20221113151353.jpg

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


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


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


ConfigurationClassPostProcessor源码分析


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

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

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

第一段

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

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


1.当前容器中有哪些BeanDefinition

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

微信图片_20221113151757.png

不出所料,确实是6个

2.checkConfigurationClassCandidate

代码如下:

  public static boolean checkConfigurationClassCandidate(
      BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
    String className = beanDef.getBeanClassName();
    if (className == null || beanDef.getFactoryMethodName() != null) {
      return false;
    }
        // 下面这一段都是为了获取一个AnnotationMetadata
        // AnnotationMetadata包含了对应class上的注解元信息以及class元信息
    AnnotationMetadata metadata;
    if (beanDef instanceof AnnotatedBeanDefinition &&
        className.equals(((AnnotatedBeanDefinition) beanDef).getMetadata().getClassName())) {
      // 已经解析过了,比如注册的配置类就属于这种,直接从bd中获取
      metadata = ((AnnotatedBeanDefinition) beanDef).getMetadata();
    }
    else if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) {
      // 拿到字节码重新解析获取到一个AnnotationMetadata
      Class<?> beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass();
      metadata = new StandardAnnotationMetadata(beanClass, true);
    }
    else {
      try {
                // class属性都没有,就根据className利用ASM字节码技术获取到这个AnnotationMetadata
        MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(className);
        metadata = metadataReader.getAnnotationMetadata();
      }
      catch (IOException ex) {
        return false;
      }
    }
        // 如果被@Configuration注解标注了,说明是一个FullConfigurationCandidate
    if (isFullConfigurationCandidate(metadata)) {
      beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
    }
        // 如果被这些注解标注了,@Component,@ComponentScan,@Import,@ImportResource
        // 或者方法上有@Bean注解,那么就是一个LiteConfigurationCandidate
        // 也就是说你想把这个类当配置类使用,但是没有添加@Configuration注解
    else if (isLiteConfigurationCandidate(metadata)) {
      beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
    }
    else {
      return false;
    }
    // 解析@Order注解,用于排序
    Integer order = getOrder(metadata);
    if (order != null) {
      beanDef.setAttribute(ORDER_ATTRIBUTE, order);
    }
    return true;
  }

第二段

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    // 第一段
    // .....
    SingletonBeanRegistry sbr = null;
    if (registry instanceof SingletonBeanRegistry) {
        sbr = (SingletonBeanRegistry) registry;
        // beanName的生成策略,不重要
        if (!this.localBeanNameGeneratorSet) {
            BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR);
            if (generator != null) {
                this.componentScanBeanNameGenerator = generator;
                this.importBeanNameGenerator = generator;
            }
        }
    }
    if (this.environment == null) {
        this.environment = new StandardEnvironment();
    }
  // 核心目的就是创建这个ConfigurationClassParser对象
    ConfigurationClassParser parser = new ConfigurationClassParser(
        this.metadataReaderFactory, this.problemReporter, this.environment,
        this.resourceLoader, this.componentScanBeanNameGenerator, registry);
   // 第三段
}

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

第三段

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    // 第一段,第二段
    // .....
    Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
    Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
    do {
        // 在第二段代码中创建了一个ConfigurationClassParser,这里就是使用这个parser来解析配置类
        // 我们知道扫描就是通过@ComponentScan,@ComponentScans来完成的,那么不出意外必定是在这里完成的扫描
        parser.parse(candidates);
        // 校验在解析过程是中是否发生错误,同时会校验@Configuration注解的类中的@Bean方法能否被复写(被final修饰或者访问权限为private都不能被复写),如果不能被复写会抛出异常,因为cglib代理要通过复写父类的方法来完成代理,后文会做详细介绍
        parser.validate();
        // 已经解析过的配置类
        Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
        // 移除已经解析过的配置类,防止重复加载了配置类中的bd
        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());
        }
        // 将通过解析@Bean,@Import等注解得到相关信息解析成bd被注入到容器中
        this.reader.loadBeanDefinitions(configClasses);
        alreadyParsed.addAll(configClasses);
        candidates.clear();
        // 如果大于,说明容器中新增了一些bd,那么需要重新判断新增的bd是否是配置类,如果是配置类,需要再次解析
        if (registry.getBeanDefinitionCount() > candidateNames.length) {
            String[] newCandidateNames = registry.getBeanDefinitionNames();
            Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
            Set<String> alreadyParsedClasses = new HashSet<>();
            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());
    // 注册ImportRegistry到容器中
    // 当通过@Import注解导入一个全配置类A(被@Configuration注解修饰的类),A可以实现ImportAware接口
    // 通过这个Aware可以感知到是哪个类导入的A
    if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
        sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
    }
    if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
        ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
    }
}
相关文章
|
7天前
|
数据可视化 数据挖掘 BI
团队管理者必读:高效看板类协同软件的功能解析
在现代职场中,团队协作的效率直接影响项目成败。看板类协同软件通过可视化界面,帮助团队清晰规划任务、追踪进度,提高协作效率。本文介绍看板类软件的优势,并推荐五款优质工具:板栗看板、Trello、Monday.com、ClickUp 和 Asana,助力团队实现高效管理。
29 2
|
5天前
|
NoSQL Java Redis
Spring Boot 自动配置机制:从原理到自定义
Spring Boot 的自动配置机制通过 `spring.factories` 文件和 `@EnableAutoConfiguration` 注解,根据类路径中的依赖和条件注解自动配置所需的 Bean,大大简化了开发过程。本文深入探讨了自动配置的原理、条件化配置、自定义自动配置以及实际应用案例,帮助开发者更好地理解和利用这一强大特性。
46 14
|
3天前
|
XML Java 数据格式
Spring容器Bean之XML配置方式
通过对以上内容的掌握,开发人员可以灵活地使用Spring的XML配置方式来管理应用程序的Bean,提高代码的模块化和可维护性。
20 6
|
4天前
|
XML Java 数据格式
🌱 深入Spring的心脏:Bean配置的艺术与实践 🌟
本文深入探讨了Spring框架中Bean配置的奥秘,从基本概念到XML配置文件的使用,再到静态工厂方式实例化Bean的详细步骤,通过实际代码示例帮助读者更好地理解和应用Spring的Bean配置。希望对你的Spring开发之旅有所助益。
33 3
|
24天前
|
前端开发 Java 开发者
Spring MVC中的请求映射:@RequestMapping注解深度解析
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的关键,它将HTTP请求映射到相应的处理器方法上。本文将深入探讨`@RequestMapping`注解的工作原理、使用方法以及最佳实践,为开发者提供一份详尽的技术干货。
71 2
|
24天前
|
前端开发 Java Spring
探索Spring MVC:@Controller注解的全面解析
在Spring MVC框架中,`@Controller`注解是构建Web应用程序的基石之一。它不仅简化了控制器的定义,还提供了一种优雅的方式来处理HTTP请求。本文将全面解析`@Controller`注解,包括其定义、用法、以及在Spring MVC中的作用。
40 2
|
24天前
|
前端开发 Java 开发者
Spring MVC中的控制器:@Controller注解全解析
在Spring MVC框架中,`@Controller`注解是构建Web应用程序控制层的核心。它不仅简化了控制器的定义,还提供了灵活的请求映射和处理机制。本文将深入探讨`@Controller`注解的用法、特点以及在实际开发中的应用。
58 0
|
2月前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
230 2
|
28天前
|
缓存 IDE Java
SpringBoot入门(7)- 配置热部署devtools工具
SpringBoot入门(7)- 配置热部署devtools工具
46 1
SpringBoot入门(7)- 配置热部署devtools工具
|
1月前
|
缓存 IDE Java
SpringBoot入门(7)- 配置热部署devtools工具
SpringBoot入门(7)- 配置热部署devtools工具
44 2
 SpringBoot入门(7)- 配置热部署devtools工具

推荐镜像

更多