欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring5源码分析(二):Spring IOC 源码解析

程序员文章站 2022-07-12 11:23:00
...

        要分析Spring 源码,首先就要从 Spring 最为熟悉的 IOC 容器入手。既然要分析 Spring IOC 源码,那么我们就先来讨论以下几个问题:①什么是 IOC?  ② IOC 能帮我们做哪些事情?

1.什么是 IOC

        控制反转(Inversion of Control,缩写为IOC),是面向对象编程中的一种设计原则。可以用来降低代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI)

       所谓控制反转,就是把原先我们代码里面需要实现的对象创建、依赖的代码,反转过来让容器帮我们实现这个过程。那么我们则需要创建一个容器,同时需要一种描述来让容器知道我们需要创建的对象,以及每个对象之间的关系。那么这个描述,最具体的表现就是我们可配置的文件(Spring 中 为 applicationContext.xml)

        对象与对象之间的关系,我们可以通过 xmlproperties等配置文件来表示。那么这些文件存放的位置该怎么描述?我们可以通过classpathfilesystem或者URL地址等来获取这些配置文件。

2.IOC 容器的功能

        BOP 编程:Spring 是基于 Bean 来开发的,即:一切都是以 Bean 为主,这就是所谓的 BOP(Bean Oriented Programming)编程。

        Spring 在项目启动阶段,会通过读取配置文件的方式,通过预设规则,去顺序的加载或识别需要对接的 Bean(反射,通过类全名字符串可以找到并创建一个Bean的实例)。然后将生成的 Bean 对象存储在 IOC 容器中。所以:IOC容器,主要是用来存放由容器帮我们生成的Java Bean对象。Spring 中 IOC 的实现,是通过ConcurrentHashMap的方式实现的。

3. 源码分析从何入手

        我们知道: Spring 是通过加载配置文件的方式启动。 IOC 实现原理:主要是获取配置文件,解析配置文件中信息,然后根据配置信息来帮助我们完成 Bean 对象的创建。所以我们应该从 IOC如何读取配置文件入手。

        我们在学习 Spring 的使用时,都是通过如下一段代码,开启 Spring 的学习之路。

ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
User user = (User)applicationContext.getBean("user");

        所以我们便可以从ClassPathXmlApplicationContext类入手,开启我们学习 Spring 源码的大门。

4.IOC 源码分析开始

        既然我们已经知道了ClassPathXmlApplicationContext类是入口。从结构图分析该类,我们发现它所有的继承实现关系,最终指向的都是一个 BeanFactory接口。
Spring5源码分析(二):Spring IOC 源码解析

4.1 BeanFactory接口

        Spring Bean 的创建,就是典型的工厂模式。字面意思,它就是用来为我们创建 Bean 类的。工厂模式的使用,从而满足 IOC 容器为开发者管理对象间的依赖关系提供了很多便利和基础服务。在 Spring 中的实现,有许多 IOC 容器的实现供我们用户选择和使用。如下为BeanFactory的关系结构图:
Spring5源码分析(二):Spring IOC 源码解析
        BeanFactory 作为*接口,它定义了 IOC 容器的最基本规范。从结构图我们可以看到:BeanFactory 有三个子类,分别是HierarchicalBeanFactoryAutowireCapableBeanFactoryListableBeanFactory。但是他们最终的实现类工厂都是 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 的描述)。

4.2 BeanDefinition 接口

       Spring IOC 容器管理了我们定义的各种 Bean 对象,以及Bean对象之间的相互关系。Bean对象之间的关系,我们是通过 xml 文件的方式来配置的。IOC 既然要帮助我们完成对象的创建以及每个对象之间的关系,那么 IOC 容器则需要通过读取 xml 配置文件来获取具体配置的信息。

        Spring源码中,对 xml 文件中 <bean> 标签的配置解析,使用的是 BeanDefinition 类来表示的。

        Bean 类的解析过程相对来说比较复杂,它会对 Spring 的 xml 配置文件中所有配置进行一一解析,包括常见的<import><alias><beans><bean>等标签进行解析,还会对标签中的所有属性进行解析针对每个标签以及部分属性等,都是单独定义一个方法来完成这个操作,功能上来说,分的比较细致,因为它要保证 Spring 框架的可扩展性、灵活性。

4.3 IOC容器初始化

        IOC容器的初始化,分为以下三个过程:1.定位   2.加载   3.注册 ,接下来详细介绍一下 IOC 容器初始化的这几个过程。

4.3.1 定位

   1.什么是定位?

        即:通过用户配置的信息,使用 classpath、filesystem、url 链接等加载文件方式,获取到资源,最终将资源解析成一个 Resource 类型文件的过程

   2.如何定位?

        XmlBeanDefinitionReader 通过调用其父类 DefaultResourceLoader 的 getResource() 方法获取要加载的资源的过程

   3.定位实现的简单步骤

1.获取配置文件名称
2.通过不同类型的 resourceLoader 加载器加载文件
3.调用 DefaultResourceLoader 类中的 getSource() 方法定位 Resource
4.获取Resource类型文件(内容实际是获取自己配置 xml 的信息)

4.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 类中进行保存

4.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中,即表示注册完成。

相关标签: Spring5 源码