要分析Spring 源码,首先就要从 Spring 最为熟悉的 IOC 容器入手。既然要分析 Spring IOC 源码
,那么我们就先来讨论以下几个问题:①什么是 IOC? ② IOC 能帮我们做哪些事情?
1.什么是 IOC
控制反转(Inversion of Control,缩写为IOC)
,是面向对象编程中的一种设计原则。可以用来降低代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI)
所谓控制反转,就是把原先我们代码里面需要实现的对象创建、依赖的代码,反转过来让容器帮我们实现这个过程。
那么我们则需要创建一个容器,同时需要一种描述来让容器知道我们需要创建的对象,以及每个对象之间的关系。那么这个描述,最具体的表现就是我们可配置的文件(Spring 中 为 applicationContext.xml)
对象与对象之间的关系,我们可以通过 xml
、properties
等配置文件来表示。那么这些文件存放的位置该怎么描述?我们可以通过classpath
、filesystem
或者URL
地址等来获取这些配置文件。
2.IOC 容器的功能
BOP 编程
:Spring 是基于 Bean 来开发的,即:一切都是以 Bean 为主,这就是所谓的 BOP(Bean Oriented Programming)编程。
Spring 在项目启动阶段,会通过读取配置文件的方式,通过预设规则,去顺序的加载或识别需要对接的 Bean(反射,通过类全名字符串可以找到并创建一个Bean的实例)。然后将生成的 Bean 对象存储在 IOC 容器中。所以:IOC容器,主要是用来存放由容器帮我们生成的Java Bean对象。
Spring 中 IOC 的实现,是通过ConcurrentHashMap
的方式实现的。
!!!注意:
Map<String, BeanDefinition> beanDefinitionMap
是读取配置文件数据存储位置【中间过程】,默认大小为 256。真正的 IOC容器是 DI 依赖注入后的对象,即 Map<String, Object> factoryBeanObjectCach,默认大小为16。
依赖注入源码分析,请参考:Spring 依赖注入(DI)源码解析
/** Map of bean definition objects, keyed by bean name */
//存储注册信息的BeanDefinition
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
/** Cache of singleton objects created by FactoryBeans: FactoryBean name --> object */
//依赖注入之后,返回 Map<String,Object>, factoryBeanObjectCache就是真正的IOC容器
private final Map<String, Object> factoryBeanObjectCache = new ConcurrentHashMap<>(16);
3. 源码分析从何入手
我们知道: Spring 是通过加载配置文件的方式启动。 IOC 实现原理:主要是获取配置文件,解析配置文件中信息,然后根据配置信息来帮助我们完成 Bean 对象的创建。所以我们应该从 IOC如何读取配置文件入手。
我们在学习 Spring 的使用时,都是通过如下一段代码,开启 Spring 的学习之路。
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
User user = (User)applicationContext.getBean("user");
所以我们便可以从ClassPathXmlApplicationContext
类入手,开启我们学习 Spring 源码的大门。
4.Spring IOC 源码时序图
【】。你也可以直接访问链接获取:https://www.processon.com/view/5e66edace4b0fbd1b08882bb
5.IOC 源码分析开始
既然我们已经知道了ClassPathXmlApplicationContext
类是入口。从结构图分析该类,我们发现它所有的继承实现关系,最终指向的都是一个 **BeanFactory**
接口。所以,Spring 的一切都是从Bean
的。
5.1 BeanFactory接口
Spring Bean 的创建,就是典型的工厂模式。
字面意思,它就是用来为我们创建 Bean 类的。工厂模式的使用,从而满足 IOC 容器为开发者管理对象间的依赖关系提供了很多便利和基础服务。在 Spring 中的实现,有许多 IOC 容器的实现供我们用户选择和使用。如下为BeanFactory的关系结构图:
BeanFactory 作为顶级接口,它定义了 IOC 容器的最基本规范。
从结构图我们可以看到:BeanFactory 有三个子类,分别是HierarchicalBeanFactory
、AutowireCapableBeanFactory
和ListableBeanFactory
。但是他们最终的实现类工厂都是 DefaultListableBeanFactory。
既然所有的工作都由 DefaultListableBeanFactory
来完成,那为什么还要定义这么多层的接口呢?其实每个接口都有它的使用的场合,主要是为了区分在 Spring 内部操作过程中对象的传递和转化过程中,对对象的数据访问所做的限制。
例如:①ListableBeanFactory 接口表示这些 Bean 是可列表的;②HierarchicalBeanFactory 表示的是这些 Bean 是有继承关系的,也就是每个 Bean 有可能有父 Bean;③AutowireCapableBeanFactory 接口定义 Bean 的自动装配规则
。这几个接口就定义了Bean的集合、Bean之间关系、以及 Bean的行为。
public interface BeanFactory {
//对FactoryBean的转义定义,因为如果使用bean的名字检索FactoryBean得到的对象是工厂生成的对象,
//如果需要得到工厂本身,需要转义
String FACTORY_BEAN_PREFIX = "&";
//根据bean的名字,获取在IOC容器中得到bean实例
Object getBean(String name) throws BeansException;
//根据bean的名字和Class类型来得到bean实例,增加了类型安全验证机制。
<T> T getBean(String name, @Nullable Class<T> requiredType) throws BeansException;
//提供对bean的检索,看看是否在IOC容器有这个名字的bean
boolean containsBean(String name);
//根据bean名字得到bean实例,并同时判断这个bean是不是单例
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
//得到bean实例的Class类型
@Nullable
Class<?> getType(String name) throws NoSuchBeanDefinitionException;
//得到bean的别名,如果根据别名检索,那么其原名也会被检索出来
String[] getAliases(String name);
}
在 BeanFactory 接口中它只对 IOC 容器作了定义,并不关心 Bean 是如何定义怎样加载的。 正如我们只关心工厂里得到什么的产品对象,至于工厂是怎么生产这些对象的,这个基本的接口并不关心。
而要知道工厂是如何产生对象的,我们需要看具体的 IOC 容器实现,Spring 提供了许多 IOC 容器的 实现。比如 XmlBeanFactory,ClasspathXmlApplicationContext 等。此处我们通过 ClasspathXmlApplicationContext 来分析,这个 IOC 容器可以读取 XML 文件定义的 BeanDefinition(XML 文件中对 bean 的描述)。
5.2 BeanDefinition 接口
Spring IOC 容器管理了我们定义的各种 Bean 对象,以及Bean对象之间的相互关系。Bean对象之间的关系,我们是通过 xml 文件的方式来配置的。IOC 既然要帮助我们完成对象的创建以及每个对象之间的关系,那么 IOC 容器则需要通过读取 xml 配置文件来获取具体配置的信息。
Spring源码中,对 xml 文件中 <bean>
标签的配置解析,使用的是 BeanDefinition
类来表示的。
Bean 类的解析过程相对来说比较复杂,它会对 Spring 的 xml 配置文件中所有配置进行一一解析,包括常见的<import>
、<alias>
、<beans>
、<bean>
等标签进行解析,还会对标签中的所有属性进行解析。针对每个标签以及部分属性等,都是==单独定义一个方法==来完成这个操作,功能上来说,分的比较细致,因为它要保证 Spring 框架的可扩展性、灵活性。
5.3 IOC容器初始化
IOC容器的初始化,分为以下三个过程:1.定位
2.加载
3.注册
,接下来详细介绍一下 IOC 容器初始化的这几个过程。
5.3.1 定位
1.什么是定位?
即:通过用户配置的信息,使用 classpath、filesystem、url 链接等加载文件方式,获取到资源,最终将资源解析成一个 Resource 类型文件的过程
2.如何定位?
XmlBeanDefinitionReader 通过调用其父类 DefaultResourceLoader 的 getResource() 方法获取要加载的资源的过程
3.定位实现的简单步骤
1.获取配置文件名称
2.通过不同类型的 resourceLoader 加载器加载文件
3.调用 DefaultResourceLoader 类中的 getSource() 方法定位 Resource
4.获取Resource类型文件(内容实际是获取自己配置 xml 的信息)
5.3.2 加载
1.什么是加载?
即:通过对定位获取到的 xml 文件,进行每一步细致的解析,然后获取到 BeanDefinition 类的过程
2.什么时候加载?
Spring IOC 容器对 Bean 定义资源的载入,是从 refresh() 函数开始的,refresh()是一个模板方法
(refresh()方法见本文附录)
3.加载实现的简单步骤
1.使用 resourceLoader.getResource(location) 获取要加载的资源
2.使用 JAXP 将 Resource 类型的 xml 文件流转换成为 Document 对象
3.根据 Document 对象获取所有的 node 子节点
4.根据子节点来判断,是否是 <import> 标签、<alias> 标签、<bean> 标签、<beans>标签
5.针对每个不同的标签以及标签中的不同属性参数,使用不同的方法做处理
6.将 xml 文件中所有的配置的 bean 信息设置到 BeanDefination 类中进行保存
5.3.3 注册
1.什么是注册?
即:
将 BeanDefinition 类根据 key (key可以是xml文件中配置的 id、name、alias等配置的属性,只要唯一即可),put 到 Map 的过程。从注册这个过程,你会发现:IOC容器其实就是一个Map(实际上是一个ConcurrentMap)
2.注册实现的简单步骤?
1.注册的过程中,使用了
synchronized
锁,保持线程同步,从而保证数据的一致性
2.将 BeanDefinition 类中的 id、name、alias属性(只要唯一即可)等来充当key,将BeanDefinition 类 put到 Map中,即表示注册完成。
6.IOC容器一定是单例的
IOC容器初始化,定位完成后。会读取配置文件,此时会调用一个refresh()方法,来保证 IOC 容器必须是唯一的。
refresh()方法的作用
:refresh()方法使用 synchronized 修饰。
在创建 IOC 容器前,如果已经有容器存在,则需要把已有的容器销毁和关闭,以保证在 refresh 之后使用的是新建立起来的 IOC 容器。
refresh 的作用类似于:①对 IOC 容器的重启;②在新建立好的容器中对容器进行初始化,对 Bean 定义资源进行载入。
7.附:refresh()方法实现
1.refresh() 方法解析
//1.容器初始化的过程,读入Bean定义资源
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
//调用容器准备刷新的方法,获取容器的当时时间,同时给容器设置同步标识
prepareRefresh();
//告诉子类启动 refreshBeanFactory()方法,Bean 定义资源文件的载入从子类的 refreshBeanFactory()方法启动
//*********注意:此处使用委派模式,委派子类完成对IOC容器唯一性判断!!!(如下附obtainFreshBeanFactory()方法)
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
//为 BeanFactory 配置容器特性,例如类加载器、事件处理器等
prepareBeanFactory(beanFactory);
try {
//为容器的某些子类指定特殊的 BeanPost 事件处理器
postProcessBeanFactory(beanFactory);
//调用所有注册的 BeanFactoryPostProcessor 的 Bean
//getBean()方法就是在此处调用,来完成bean的初始化操作的(即:Spring DI流程,参考:https://blog.csdn.net/lzb348110175/article/details/104778491)
invokeBeanFactoryPostProcessors(beanFactory);
//为 BeanFactory 注册 BeanPost 事件处理器.BeanPostProcessor 是 Bean 后置处理器,用于监听容器触发的事件
registerBeanPostProcessors(beanFactory);
//初始化信息源,和国际化相关.
initMessageSource();
//初始化容器事件传播器.
initApplicationEventMulticaster();
//调用子类的某些特殊 Bean 初始化方法
onRefresh();
//为事件传播器注册事件监听器.
registerListeners();
//初始化所有剩余的单例 Bean.
finishBeanFactoryInitialization(beanFactory);
//初始化容器的生命周期事件处理器,并发布容器的生命周期事件
finishRefresh();
}catch (BeansException ex) {
//销毁以创建的单态 Bean
destroyBeans();
//取消 refresh 操作,重置容器的同步标识.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
}
}
2.obtainFreshBeanFactory()方法
AbstractApplicationContext 的 obtainFreshBeanFactory() 方法调用子类容器的 refreshBeanFactory() 方法, 配置文件解析模块,在 obtainFreshBeanFactory() 方法中实现
启动容器载入 Bean 定义资源文件的过程,代码如下:
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
//这里使用了委派设计模式,父类定义了抽象的 refreshBeanFactory()方法,具体实现调用子类容器的refreshBeanFactory()方法
refreshBeanFactory();
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}
3.子类容器的具体实现refreshBeanFactory()方法
AbstractApplicationContext 类中只抽象定义了 refreshBeanFactory()方法,容器真正调用的是子类 AbstractRefreshableApplicationContext 实现的 refreshBeanFactory()方法,源码如下:
@Override
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {{//如果已经有容器,销毁容器中的 bean,关闭容器
destroyBeans();
closeBeanFactory();
}
try {
//创建 IOC 容器
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
//对 IOC 容器进行定制化,如设置启动参数,开启注解的自动装配等
customizeBeanFactory(beanFactory);
//调用载入 Bean 定义的方法,主要这里又使用了一个委派模式,在当前类中只定义了抽象的 loadBeanDefinitions 方法,具体的实现调用子类容器
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
4.继续调用 loadBeanDefinitions() 方法
此处不再过多一步步介绍源码,你可以按照 4.Spring IOC 源码时序图
,打开源码来进一步分析,此处粘贴过多代码无多大意义。附 spring-framework-5.0.2.RELEASE (中文注释)版本,直接解压 IDEA 打开即可
。
地址: 1.spring-framework-5.0.2.RELEASE (中文注释)版本
2.网盘地址:spring-framework-5.0.2.RELEASE (中文注释)版本(提取码:uck4 )
恭喜您,枯燥源码看到这里。 Spring IOC 源码就介绍到此为止
博主写作不易,来个关注呗
求关注、求点赞,加个关注不迷路 ヾ(◍°∇°◍)ノ゙
博主不能保证写的所有知识点都正确,但是能保证纯手敲,错误也请指出,望轻喷 Thanks♪(・ω・)ノ
网友评论