思考,输出,沉淀。用通俗的语言陈述技术,让自己和他人都有所收获。
作者:毅航😜
在之前的几个月中笔者对SpringMVC
和Mybatis
的相关源码进行了分析,感兴趣的读者可翻阅专栏 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
关键字构建了一个类型
为ClassPathXmlApplicationContext
的context
变量;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
的逻辑。这部分调用逻辑如下:
(注:重点关注调用逻辑!请忽略掉相关容器名称,这个后期笔者会单独分析)
不难发现,其最终会调用AbstractRefreshableApplicationContext
中的refreshBeanFactory
方法
protected final void refreshBeanFactory() throws BeansException {
// 构建一个beanFactory
DefaultListableBeanFactory beanFactory = createBeanFactory();
// 加载bean定义
loadBeanDefinitions(beanFactory);
}
可以看到refreshBeanFactory
主要完成如下两点工作:
refreshBeanFactory
方法会创建一个DefaultListableBeanFactory
实例,它是Spring
容器的核心部分。DefaultListableBeanFactory
用于注册Bean
定义、解析依赖关系以及管理Bean
的生命周期。loadBeanDefinitions
方法接受一个BeanFactory
然后根据之前配置路径信息
加载配置文件并进行解析,进一步,解析出的bean
都将存放于传入的BeanFactory
之中。此处,笔者
就不展开分析loadBeanDefinitions
的内部逻辑了,其内部逻辑一言以概之无非就是 解析配置文件,加载配置bean
相关信息。
至此,我们来对ClassPathXmlApplicationContext
进行一个简单总结,其无非就是Spring
框架中用于从类路径(Classpath)
加载XML
配置文件并初始化bean
的一个上下文。
总结
经过上述分析,那如果这时我这样问你,你能通过自己编码
的方式来实现一个ClassPathXmlApplicationContext
吗?
你心里肯定会想这有啥难的呢!通过文章之前的分析,利用xml
解析结合HashMap
就能实现。具体来看,将配置文件中的bean
信息进行解析,解析完毕后通过反射构建一个bean
,然后将bean
存放到一个HashMap
到时候用的时候去Map
结构中去取不就完了吗?
通过上述的分析,你觉得Spring
还难吗?我想答案肯定是否定的!那能独立实现上述需求吗?我想肯定可以啦!没有思路也别着急,笔者将在下一遍亲自操刀
来实现,到时候不妨跟着笔者
的思路来仿写一个!