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

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 你知道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();
    }
}
相关文章
|
5天前
|
安全 Java 数据库连接
后端框架的学习----mybatis框架(3、配置解析)
这篇文章详细介绍了MyBatis框架的核心配置文件解析,包括环境配置、属性配置、类型别名设置、映射器注册以及SqlSessionFactory和SqlSession的生命周期和作用域管理。
后端框架的学习----mybatis框架(3、配置解析)
|
3天前
|
XML Java 数据格式
Spring Cloud全解析:注册中心之zookeeper注册中心
使用ZooKeeper作为Spring Cloud的注册中心无需单独部署服务器,直接利用ZooKeeper服务端功能。项目通过`spring-cloud-starter-zookeeper-discovery`依赖实现服务注册与发现。配置文件指定连接地址,如`localhost:2181`。启动应用后,服务自动注册到ZooKeeper的`/services`路径下,形成临时节点,包含服务实例信息。
|
4天前
|
负载均衡 网络协议 应用服务中间件
如何配置 DNS 轮询负载均衡以实现高可用性
如何配置 DNS 轮询负载均衡以实现高可用性
16 4
|
3天前
|
运维 Java Nacos
Spring Cloud应用框架:Nacos作为服务注册中心和配置中心
Spring Cloud应用框架:Nacos作为服务注册中心和配置中心
|
6天前
|
JSON 图形学 数据格式
Json☀️ 一、认识Json是如何解析成类的
Json☀️ 一、认识Json是如何解析成类的
|
17天前
|
XML Android开发 UED
"掌握安卓开发新境界:深度解析AndroidManifest.xml中的Intent-filter配置,让你的App轻松响应scheme_url,开启无限交互可能!"
【8月更文挑战第2天】在安卓开发中,scheme_url 通过在`AndroidManifest.xml`中配置`Intent-filter`,使应用能响应特定URL启动或执行操作。基本配置下,应用可通过定义特定URL模式的`Intent-filter`响应相应链接。
45 12
|
4天前
|
安全 Nacos 数据库
【技术安全大揭秘】Nacos暴露公网后被非法访问?!6大安全加固秘籍,手把手教你如何保护数据库免遭恶意篡改,打造坚不可摧的微服务注册与配置中心!从限制公网访问到启用访问控制,全方位解析如何构建安全防护体系,让您从此告别数据安全风险!
【8月更文挑战第15天】Nacos是一款广受好评的微服务注册与配置中心,但其公网暴露可能引发数据库被非法访问甚至篡改的安全隐患。本文剖析此问题并提供解决方案,包括限制公网访问、启用HTTPS、加强数据库安全、配置访问控制及监控等,帮助开发者确保服务安全稳定运行。
11 0
|
4天前
|
安全 Nacos 数据安全/隐私保护
【技术干货】破解Nacos安全隐患:连接用户名与密码明文传输!掌握HTTPS、JWT与OAuth2.0加密秘籍,打造坚不可摧的微服务注册与配置中心!从原理到实践,全方位解析如何构建安全防护体系,让您从此告别数据泄露风险!
【8月更文挑战第15天】Nacos是一款广受好评的微服务注册与配置中心,但其连接用户名和密码的明文传输成为安全隐患。本文探讨加密策略提升安全性。首先介绍明文传输风险,随后对比三种加密方案:HTTPS简化数据保护;JWT令牌减少凭证传输,适配分布式环境;OAuth2.0增强安全,支持多授权模式。每种方案各有千秋,开发者需根据具体需求选择最佳实践,确保服务安全稳定运行。
16 0
|
7天前
|
Java Spring
Spring Boot Admin 授权配置
Spring Boot Admin 授权配置
13 0
|
7天前
|
监控 Java Spring
Spring Boot Admin 配置应用
Spring Boot Admin 配置应用
15 0

热门文章

最新文章

推荐镜像

更多