从一行简单的配置开始,重新来认识Spring的上下文环境

2023年 10月 13日 114.6k 0

思考,输出,沉淀。用通俗的语言陈述技术,让自己和他人都有所收获。
作者:毅航😜

在之前的几个月中笔者对SpringMVCMybatis的相关源码进行了分析,感兴趣的读者可翻阅专栏 SpringMVC流程分析和 Mybatis源码分析进行查看。

在接下来很长的一段时间内笔者将开始对Spring源码进行深入分析并根据源码仿写一个简化版的Spring,在这一过程中笔者将以理论+实践的方式来讲述Spring源码,即先分析框架的实现,再自己模拟实现,双管齐下真正意义上攻克Spring框架源码。感兴趣的话不妨点个关注,现在上车,以后就是老司机啦~~~

前言

相信对于Java开发者而言,日常开发接触最多的框架一定是Spring。就是这样一个每天都接触的框架你有深入研究它过吗?或许,你对于Spring框架源码的深入研究都集中在求职面试期间,每次都在快面试的时候临时抱一抱佛脚,面试完就开始将相关内容束之高阁。然后,每次面试都不断重复这一过程。

我想这其实是大部分Java开发者面试的常态,也是笔者之前的面试时的常态。但正如笔者在编程学习的这些坑别在踩了所说的那样,这其实一种自慰式学习,你只是看似很努力,但实际却还在原地踏步。

所以,为了避免每次都进行这样无效的学习,不妨跟着笔者的思路来对重新对Spring框架源码进行一次重新梳理,坚持跟完笔者本专栏的内容,相信你一定会对Spring框架源码有一个更深层的认识!

从配置文件开始重新认识Spring

SpringBoot的出现,极大的简化了Spring上手难度,日常开发中通过几个简单的注解就能将类信息注入到容器中。或许,你已经忘却原生的Spring该如何使用。接下来,不妨跟着笔者思路来对Spring的原生用法进行一个简单的回顾。

applicationContext.xml



   
    
    
    

(注:为了节省篇幅,上述内容中的xmlns内容进行了省略~)

正如上述applicationContext.xml配置文件所示,在Spring中你可以使用XML配置文件来定义Bean。进一步,你可以在应用程序中通过如下代码来加载配置文件,并获取相关的bean

public class MyApplicationContext {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        // 从容器中获取Bean
        Car car = context.getBean("car", Car.class);

        // 使用car对象的相关方法
        car.doSomething();
    }
}

看到上述代码,是不是会有种恍若隔世的感觉?毫无疑问,每个学习Spring的初学者一定写过上述代码,其就如学习编程语言时写的Hello Word一样,这是Spring界的Hello Word

走进ClassPathXmlApplicationContext

不知你是否考过这样一个问题,上述简单的三行代码到底做了哪些工作呢? 从初学者的角度来看,其无非做了如下两件事:

  • 通过new关键字构建了一个类型ClassPathXmlApplicationContextcontext变量;
  • 调用ClassPathXmlApplicationContext相关API,获取一个类型为Car.class的变量,然后,访问其内部方法。
  • 进一步,那如果我要你ClassPathXmlApplicationContext内部完成了那些工作你能回答上来吗?回答上来也不要着急,不妨跟着笔者思路来进行分析!

    首先,我们来看ClassPathXmlApplicationContext的构造方法。此时,你可能会疑惑,为啥一上来要看ClassPathXmlApplicationContext的构造方法?答案其实很简单,因为我们会通过new关键字来构造一个ClassPathXmlApplicationContext的实例对象。

    
    public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
       this(new String[] {configLocation}, true, null);
    }
    
    // 
    public ClassPathXmlApplicationContext(
          String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
          throws BeansException {
       // 调用父类构造
       super(parent);
       // 设定配置文件路径信息
       setConfigLocations(configLocations);
       // 刷新容器操作
       refresh();
       
    }
    
    

    可以看到,在其内部最终会调用处所示的构造方法,那不妨来看其会完成哪些工作:

  • 首先,不断向上调用父类的构造器。具体来看,此处最终会初始化一个ResourcePatternResolver的成员变量,该类提供了一种查找和加载资源的强大机制,主要用于处理类路径下的资源和基于通配符的资源查找。你可以简单理解为此处通过构造器来初始化一个资源加载器,进而方便后续配置文件的加载。而有关资源加载的内容,笔者会在后续会进行分析!

  • 设定配置文件路径信息,此处也很好理解,既然配置了一个资源加载器,那必然需要知道要到何处去加载配置信息,这也是处代码主要完成的工作。

  • 容器刷新

  • 点进refresh方法后,你会看到如下内容:

    public void refresh() throws BeansException, IllegalStateException {
       synchronized (this.startupShutdownMonitor) {
         
          prepareRefresh();
          // 构架容器
          ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
    
         // .... 省略其他无关代码
         
      
    }
    

    出于篇幅考虑,此处笔者略去了refresh中调用的十多个方法。本次我们先简单看一下obtainFreshBeanFactory的逻辑。这部分调用逻辑如下:

    image.png

    (注:重点关注调用逻辑!请忽略掉相关容器名称,这个后期笔者会单独分析)

    不难发现,其最终会调用AbstractRefreshableApplicationContext中的refreshBeanFactory方法

    protected final void refreshBeanFactory() throws BeansException {
       
          // 构建一个beanFactory
          DefaultListableBeanFactory beanFactory = createBeanFactory();
          // 加载bean定义
          loadBeanDefinitions(beanFactory);
         
    }
    

    可以看到refreshBeanFactory主要完成如下两点工作:

  • 创建Bean工厂:首先,refreshBeanFactory方法会创建一个DefaultListableBeanFactory实例,它是Spring容器的核心部分。DefaultListableBeanFactory用于注册Bean定义、解析依赖关系以及管理Bean的生命周期。
  • 加载XML配置文件:loadBeanDefinitions方法接受一个BeanFactory然后根据之前配置路径信息加载配置文件并进行解析,进一步,解析出的bean都将存放于传入的BeanFactory之中。
  • 此处,笔者就不展开分析loadBeanDefinitions的内部逻辑了,其内部逻辑一言以概之无非就是 解析配置文件,加载配置bean相关信息。

    至此,我们来对ClassPathXmlApplicationContext进行一个简单总结,其无非就是Spring框架中用于从类路径(Classpath) 加载XML配置文件并初始化bean的一个上下文。

    总结

    经过上述分析,那如果这时我这样问你,你能通过自己编码的方式来实现一个ClassPathXmlApplicationContext吗?

    你心里肯定会想这有啥难的呢!通过文章之前的分析,利用xml解析结合HashMap就能实现。具体来看,将配置文件中的bean信息进行解析,解析完毕后通过反射构建一个bean,然后将bean存放到一个HashMap到时候用的时候去Map结构中去取不就完了吗?

    通过上述的分析,你觉得Spring还难吗?我想答案肯定是否定的!那能独立实现上述需求吗?我想肯定可以啦!没有思路也别着急,笔者将在下一遍亲自操刀来实现,到时候不妨跟着笔者的思路来仿写一个!

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论