springIoC容器的实现

1 IOC容器的概述

1.1 IOC容器和依赖反转模式

2004年,M.K就提出了“哪个方面的控制被反转了?”他的结论:依赖对象的获得被反转了。所以他提出了控制反转的别名:“依赖注入”。

举例:在我们实现一个应用的时候,都是需要多个类进行互相合作来完成业务逻辑或是功能的。例如:实现一个常见的登录功能。就需要我们在后台编写多个类来实现登录的各个部分。controller、service、dao常见的三层架构,这就至少编写三个类。每个对象都需要它依赖的对象的引用。例如controller层的类接收前端请求和数据,调用service层的类实现具体的业务,service层的类调用dao层的接口实现数据的增删改查。形成了层层引用的关系。这样的关系就是一种依赖。如果获取引用的过程要靠自身实现,代码就会高度耦合。

如何进行注入?怎样反转?

应用控制反转后,由一个IOC容器内的所有对象的外界实体将其所依赖的对象的引用传递给它。即依赖被注入到对象中。通过使用IOC容器, 对象依赖关系的管理被反转了,转到IOC容器中来了,对象之间相互依赖关系由IOC容器同一管理,并由IOC容器完成对象的注入。

管理对象的优势:

​ 1.把需要执行的诸如:新建对象、为对象引用赋值等操作由容器同一完成

​ 2.将用于处理数据不常发生变化对象、不常涉及数据和状态共享的对象进行管理

​ 3.一些依赖关系非常稳定的对象非常适合IOC容器进行管理

1.2 SpringIOC的应用场景

在spring中,Spring IOC提供了一个基本的JavaBean容器,通过IOC模式管理依赖关系,并通过依赖注入和AOP切面增强了为JavaBean这样的POJO对象赋予事务管理、生命周期管理等基本功能。IOC主动管理这些引用调用组件这种依赖关系,将这些依赖关系注入到组件中;

注入实现的方式:

  • 接口注入(Type 1 IOC)

  • setter注入(Type 2 IOC)

  • 构造器注入(Type 3 IOC)

2.IOC 的实现

在Spring IOC容器中,由两个主要的容器系列:

  • BeanFactory:接口,只实现容器的基本功能
  • ApplicationContext应用上下文:容器的高级形态,在基本容器的基础上增加了很多的高级特性,同时对很多环境进行了适配。

2.1 什么是容器?

我们通常说的IOC容器实际上代表着一系列功能各异的容器产品,只是容器的功能有大有小,有各自的特点;

举例:以水桶为例,商店卖的水桶有大有小,工艺、材料等都不相同,但是只要可以装水,具备水桶的基本特性,这就是水桶,可以供用户使用。

在Spring中同样如此,Spring有各种各样的IOC容器供用户选择和使用,使用什么样的容器由用户自己进行决定。其中IOC容器就好比水桶一样,BeanFactory接口类是容器的基本规范,就好比水桶能可以装水是制作水桶的基本规范。为了满足不同的功能需要,从基本规范出发,延伸出不同的容器形式。满足用户不同的需要。例如:顾客买水桶,有人需要大一点的水桶,有的人需要的是结实一点的水桶,用在不同的地方。

2.2 什么是BeanDefinition?

Spring通过定义BeanDefinition来管理基于Spring的应用中的各种对象以及他们之间的相互依赖关系。依赖反转功能都是围绕BeanDefinition的处理来完成的,BeanDefinition就像是容器中装的水,有了这些基本数据,容器才能发挥作用。想象一下,水桶如果是一个空的,那它只能称为水桶,我们也不能确定它实际可以用来装水还是盛饭。但是装了水后,水桶就会发挥它能装水的作用来。

2.3 IOC容器的设计

上面讲了什么是IOC容器,那么知道了什么是IOC容器了,接下来就要了解IOC容器长什么样。比如知道了水桶可以装水,但是水桶是圆的、方的还是什么样的?

如图,IOC容器的接口设计框图:

在这里插入图片描述

BeanFactory是我们使用IOC容器应该遵守的最底层和最基本的编程规范,在Spring代码的实现中,BeanFactory只是一个接口类,没有给出具体的实现,对于DefaultListableBeanFactory,XmlBeanFactory等就是容器附加某种功能的具体的实现。

2.4 区分BeanFactory和FactoryBean

答:一个是Factory,也就是IOC容器或对象工厂;一个是Bean。在Spring中,所用的Bean都是由BeanFactory(IOC容器)来管理的,FactoryBean不是简单的Bean,而是一个能够产生或是修饰对象生成的工厂Bean,它的实现类似于设计模式中的工厂模式和修饰器模式。

2.5 编程式使用IOC容器

ClassPathResource res = new ClassPathResource("beans.xml");
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinition(res);

在使用IOC容器的时候,需要以下几个步骤:

  • 创建IOC配置文件的抽象资源,这个抽象资源包含了BeanDefinition的定义信息;
  • 创建一个BeanFactory,这里使用DefaultListableBeanFactory;
  • 创建一个载入BeanDefinition的读取器,这里使用XmlBeanDefinitionReader来载入XML文件形式的BeanDefinition,通过一个回调配置给BeanFactory;
  • 从定义好的资源位置读入配置信息,具体的解析过程由XmlBeanDefinitionReader来完成。完成整个载入和注册Bean定义之后,需要的IOC容器就建立起来了。就可以直接使用IOC容器了。

2.6 ApplicationContext的应用

ApplicationContext具备的高级特性:

  • 支持不同的信息源;例如支持国际化的表现
  • 访问资源;这个特性体现在对ResourceLoader和Resource的支持上,这样我们可以从不同地方得到Bean定义资源;
  • 支持应用事件;继承了接口ApplicationEventPublisher,从而在上下文引入事件机制
  • 在ApplicationContext中提供附加服务

3.IOC容器的初始化过程

简单的说,IOC容器的初始化过程是由refresh()方法来启动的,这个方法标志着IOC容器的正式启动;

启动过程:

  • BeanDefinition的Resource定位,载入和注册三个基本过程

Spring是将以上三个过程分开并使用不同的模块来完成,方便进行扩展。

过程一:BeanDefinition的Resource定位

这个Resource指的是BeanDefinition的资源定位,它是由ResourceLoader通过统一 的Resource接口完成的,Resource对各种形式的BeanDefinintion都提供了统一接口。

Bean的定义就是我们常用的,如在文件系统中Bean的定义信息使用FIleSystemResource来进行抽象

这个定位过程类似于容器寻找数据的过程,就像用水桶装水先找到水的过程一样。

过程二:BeanDefinition的载入

载入过程就是找到Bean之后,想办法将Bean装到IOC容器中,是把用户定义好的Bean表示成IOC容器内部的数据结构,而这个容器内部的数据结构就是BeanDefinition;

BeanDefinition的定义:这个BeanDefinition实际上就是POJO对象在IOC容器中的抽象,通过这个BeanDefinition定义的数据结构使得IOC容器能够方便的对POJO对象也就是Bean进行管理。

举例:IOC容器就好像是一个只能装正方形木头块的箱子。一个长方形的木头块送过来,这个箱子是不能装的,想装到箱子中必须将长方形木块改变成正方形的木块。这里的正方形就是IOC容器中特定的数据结构BeanDefinition。

过程三:向IOC容器中注册这些BeanDefinition的过程

完成注册的接口:BeanDefinitionRegister接口

这个注册过程就是把载入过程中解析得到的BeanDefinition向IOC容器中进行注册。在内部是将BeanDefinition注册到一个HashMap中,IOC容器就是通过这个HashMap来持有这些BeanDefinition数据的;

注意:以上三步的IOC容器的初始化过程不包括Bean依赖注入的实现,在Spring IOC设计中,Bean定义的载入和依赖注入是两个独立的过程。依赖注入一般发生在应用第一次通过getBean向容器索取Bean的时候。也可以通过lazyinit属性预加载;

过程一:BeanDefinition的Resource定位

首先就是让spring找到我们写的xxx.xml文件或是其他形式的文件

下面是以XML文件为例:

BeanDefinition在过程中的FileSystemXMLApplicationContext的实现:

public class FileSystemXmlApplicationContext extends AbstractXmlApplicationContext {

	 
	public FileSystemXmlApplicationContext() {
	}

	 
	public FileSystemXmlApplicationContext(ApplicationContext parent) {
		super(parent);
	}
    // 这个构造函数的configLocation包含的是BeanDefinition所在的文件路径
	public FileSystemXmlApplicationContext(String configLocation) throws BeansException {
		this(new String[] {configLocation}, true, null);
	}

	 // 这个构造函数的configLocation包含的是多个BeanDefinition所在的文件路径
	public FileSystemXmlApplicationContext(String... configLocations) throws BeansException {
		this(configLocations, true, null);
	}

	 // 可以指定双亲IOC容器
	public FileSystemXmlApplicationContext(String[] configLocations, ApplicationContext parent) throws BeansException {
		this(configLocations, true, parent);
	}

	public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh) throws BeansException {
		this(configLocations, refresh, null);
	}

	/**
	* 在对象的初始化过程中,调用refresh函数载入BeanDefinition,这个函数启动了载入过程
    */
	public FileSystemXmlApplicationContext(
			String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
			throws BeansException {

		super(parent);
		setConfigLocations(configLocations);
		if (refresh) {
			refresh();
		}
	}

	@Override
	protected Resource getResourceByPath(String path) {
		if (path.startsWith("/")) {
			path = path.substring(1);
		}
		return new FileSystemResource(path);
	}
}

在上述类的构造函数中,实现了对configuration进行处理的过程,让所有的文件系统中的以XML文件形式存在的BeanDefinition都能够得到有效处理。

注意:refresh()方法是实现容器初始化的一个重要入口。

总结资源定位的逻辑:1.调用refreshBeanFactory()------>getResourceByPath()对path进行解析—返回—>FileSystemResource对象—>文件在系统中定位的BeanDefinition。

在这里插入图片描述

如果是其他的ApplicationContext,那么会对应生成其他种类的Resource,比如:ClassPathResource、ServletContextResource.

在BeanDefinition定位完成的基础上就可以通过返回的Resource对象进行BeanDefinition的载入了。在定位完成后,为BeanDefinition的载入创造了I/O操作的条件,就是找到了我们平时写的mybatis.xml、springContext.xml等文件了,但是文件中的数据还没有读,读取文件中的数据就在BeanDefinition的载入和解析中完成。

过程二:BeanDefinition的载入

什么是BeanDefinition的载入?这个载入的过程相当于把定义的BeanDefinition在IOC容器中转化成一个spring内部表示的数据结构的过程。平时我们实现的IOC容器对Bean的管理和依赖注入功能的实现,是通过对其持有的BeanDefinition进行各种相对应的操作完成的,IOC容器通过使用HashMap对BeanDefinition的数据进行保存。

找到某种固定的形式的文件的BeanDefinition的载入流程图:
在这里插入图片描述

在读取器中得到代表XML文件的Resource,得到XML文件对象,就可以按照spring的Bean定义规则来对这个XML文档进行解析了。

解析过程方法调用流程图:
在这里插入图片描述

上面说的对Bean元素进行解析的过程就是BeanDefinition依据XML中的定义被创建的过程。这个BeanDefinition可以看成是对的抽象。在上面的方法parseBeanDefinitionElement()就完成了对元素属性、构造函数设置等的处理。

经过以上这样层层解析过程,我们在XML文件中定义的BeanDefinition就被整个载入到了IOC容器中,并在IOC容器中建立了数据映射。在IOC容器中建立了对应的数据结构。经过以上的载入过程,IOC容器大致完成了管理Bean对象的数据准备工作(初始化的过程)。现在在IOC容器BeanDefinition中存在的还只是一些静态的配置信息,也就是说着时候的容器还没有完全起作用,还需要完成数据向容器的注册。

过程三:向IOC容器中注册BeanDefinition

前面已经分析过BeanDefinition在IOC容器中的载入和解析的过程。在载入和解析完成后,用户自己定义的BeanDeFinition信息已经在IOC容器内建立起自己的数据结构和相应的数据表示了。但是这些数据还不能被IOC容器使用,需要将这些BeanDefinition向IOC容器注册。

注册过程:

在这里插入图片描述

在DefaultListableBeanFactory类实现了BeanDefinitionRegister接口,这个接口完成了BeanDefinition向容器的注册。注册过程就是讲解析得到的BeanDefinition设置到hashMap中。

注意:在registerBeanDefinition()方法中,如果遇到同名的beanDefinition但又不允许覆盖就会抛出异常。

正常注册的过程就是把Bean的名字存入到beanDefinitionName的同时,把beanName作为Map的key,把beanDefinition作为Value存入到IOC容器持有的beanDefinitionMap中。

4.IOC容器的依赖注入

上面的初始化过程完成的主要工作就是在IOC容器中建立起BeanDefinition数据映射,但是在此过程中并没有对Bean的依赖关系进行注入。IOC容器是怎样对Bean的依赖关系进行注入的呢?

4.1 依赖注入的原理

1.当前IOC容器已经完成了初始化的过程(载入了用户定义的Bean信息)

2.依赖注入是什么时候发生的?在用户第一次向IOC容器索要Bean时触发的。当然可以在BeanDefinition信息中通过设置lazy-init 属性来让容器完成对Bean的预先实例化,预实例化是在初始化过程中完成的依赖注入。

依赖注入过程:从getBean() 方法出发:

在这里插入图片描述

上面就是实例化Bean的过程,在实例化Bean对象生成的基础上,也就是Bean对象生成后,Spring是怎样把Bean对象的依赖关系设置好的?从而完成整个依赖注入的过程。

4.2 依赖关系处理的过程

依据:就是已经解析得到的BeanDefinition。过程从上图的populateBean()方法开始。通过使用BeanDefinitionResolve来对BeanDefinition进行解析,调用方法BeanWrapperImpl中完成Bean的属性值的注入。

4.3 总结

在Bean的创建和对象依赖注入的过程中,需要依据之前对BeanDefinition的载入的信息来递归的完成依赖注入。这些递归过程都是以getBean() 为入口的。一个递归是在上下文体系中查找需要的bean和创建bean 的递归调用。另一个是依赖注入时,通过递归调用容器中的getBean方法,得到当前Bean依赖的Bean,同时也触发对依赖的Bean的创建和注入。

在对Bean的属性进行解析的过程也是递归的过程,根据依赖关系一层层的实现Bean的创建和注入。最后就完成了当前Bean的创建。

5.IOC容器其他相关特性的设计和实现

5.1 IOC容器中的Bean的生命周期

  • Bean实例的创建
  • 为Bean实例设置属性
  • 调用Bean的初始化方法
  • 应用可以通过IOC容器使用Bean
  • 当容器关闭时,调用Bean的销毁方法

5.2 lazy-init属性和预实例化

从之前的refresh方法看起,在该方法中调用了finishBeanFactoryInitialization(beanFactory)方法对init属性进行了处理。之后调用的是preInstantiateSignaletons方法完成处理。preInstantiateSignaletons方法中调用getBean()完成接下来的初始化过程。和之前的分析初始化过程就一样的了。不同的就是时间和调用getBean方法的位置不同。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值