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

简介: 你知道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();
    }
}
相关文章
|
6月前
|
Java 关系型数据库 MySQL
Spring Boot自动配置:魔法背后的秘密
Spring Boot 自动配置揭秘:只需简单配置即可启动项目,背后依赖“约定大于配置”与条件化装配。核心在于 `@EnableAutoConfiguration` 注解与 `@Conditional` 系列条件判断,通过 `spring.factories` 或 `AutoConfiguration.imports` 加载配置类,实现按需自动装配 Bean。
|
6月前
|
缓存 安全 Java
Spring Security通用权限管理模型解析
Spring Security作为Spring生态的核心安全框架,结合RBAC与ACL权限模型,基于IoC与AOP构建灵活、可扩展的企业级权限控制体系,涵盖认证、授权流程及数据库设计、性能优化等实现策略。
452 0
|
6月前
|
缓存 安全 Java
Spring Security权限管理解析
Spring Security是Spring生态中的核心安全框架,采用认证与授权分离架构,提供高度可定制的权限管理方案。其基于过滤器链实现认证流程,通过SecurityContextHolder管理用户状态,并结合RBAC模型与动态权限决策,支持细粒度访问控制。通过扩展点如自定义投票器、注解式校验与前端标签,可灵活适配多租户、API网关等复杂场景。结合缓存优化与无状态设计,适用于高并发与前后端分离架构。
465 0
|
5月前
|
XML Java 数据格式
《深入理解Spring》:AOP面向切面编程深度解析
Spring AOP通过代理模式实现面向切面编程,将日志、事务等横切关注点与业务逻辑分离。支持注解、XML和编程式配置,提供五种通知类型及丰富切点表达式,助力构建高内聚、低耦合的可维护系统。
|
5月前
|
前端开发 Java 应用服务中间件
《深入理解Spring》 Spring Boot——约定优于配置的革命者
Spring Boot基于“约定优于配置”理念,通过自动配置、起步依赖、嵌入式容器和Actuator四大特性,简化Spring应用的开发与部署,提升效率,降低门槛,成为现代Java开发的事实标准。
|
5月前
|
前端开发 Java 微服务
《深入理解Spring》:Spring、Spring MVC与Spring Boot的深度解析
Spring Framework是Java生态的基石,提供IoC、AOP等核心功能;Spring MVC基于其构建,实现Web层MVC架构;Spring Boot则通过自动配置和内嵌服务器,极大简化了开发与部署。三者层层演进,Spring Boot并非替代,而是对前者的高效封装与增强,适用于微服务与快速开发,而深入理解Spring Framework有助于更好驾驭整体技术栈。
|
6月前
|
Java 数据库 数据安全/隐私保护
Spring Boot四层架构深度解析
本文详解Spring Boot四层架构(Controller-Service-DAO-Database)的核心思想与实战应用,涵盖职责划分、代码结构、依赖注入、事务管理及常见问题解决方案,助力构建高内聚、低耦合的企业级应用。
1289 1
|
6月前
|
缓存 Java 应用服务中间件
Spring Boot配置优化:Tomcat+数据库+缓存+日志,全场景教程
本文详解Spring Boot十大核心配置优化技巧,涵盖Tomcat连接池、数据库连接池、Jackson时区、日志管理、缓存策略、异步线程池等关键配置,结合代码示例与通俗解释,助你轻松掌握高并发场景下的性能调优方法,适用于实际项目落地。
1121 5
|
算法 测试技术 C语言
深入理解HTTP/2:nghttp2库源码解析及客户端实现示例
通过解析nghttp2库的源码和实现一个简单的HTTP/2客户端示例,本文详细介绍了HTTP/2的关键特性和nghttp2的核心实现。了解这些内容可以帮助开发者更好地理解HTTP/2协议,提高Web应用的性能和用户体验。对于实际开发中的应用,可以根据需要进一步优化和扩展代码,以满足具体需求。
1149 29
|
前端开发 数据安全/隐私保护 CDN
二次元聚合短视频解析去水印系统源码
二次元聚合短视频解析去水印系统源码
487 4

推荐镜像

更多
  • DNS