Spring源码分析(二)(IoC容器的实现)(1)

    Ioc(Inversion of Control)——“控制反转”,不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。理解好Ioc的关键是要明确“谁控制谁,控制什么,为何是反转(有反转就应该有正转了),哪些方面反转了”,那我们来深入分析一下:

谁控制谁,控制什么传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。

为何是反转,哪些方面反转了有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。

用图例说明一下,传统程序设计如图2-1,都是主动去创建相关对象然后再组合起来:

《Spring源码分析(二)(IoC容器的实现)(1)》

图2-1 传统应用程序示意图

当有了IoC/DI的容器后,在客户端类中不再主动去创建这些对象了,如图2-2所示:

 《Spring源码分析(二)(IoC容器的实现)(1)》

图2-2有IoC/DI容器后程序结构示意图

IoC能做什么:IoC不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。IoC很好的体现了面向对象设计法则之一—— 好莱坞法则:“别找我们,我们找你”;即由IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。

IoC和DI:DI—Dependency Injection,即“依赖注入”:是组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。

理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”,那我们来深入分析一下:

谁依赖于谁:当然是应用程序依赖于IoC容器;

为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源;

谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;

●注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。

 

    IoC和DI由什么关系呢?其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以2004年大师级人物Martin Fowler又给出了一个新的名字:“依赖注入”,相对IoC 而言,依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”。

原文:http://sishuok.com/forum/blogPost/list/2427.html

更多请参考:https://www.martinfowler.com/articles/injection.html

OK下面进入正题

IoC容器系列的设计与实现:BeanFactory和ApplicationContext

    在 Spring IoC容器的设计中 ,有两个主要的容器系列 ,一个是实现BeanFactory接口的简单容器系列,这系列容器只实现了容器的最基本功能 ,另一个是ApplicalionContext应用上下文,它作为容器的高级形态而存在,在简单容器的基础上,增加了许多面向框架的特性,同时对应用环境作了许多适配。

《Spring源码分析(二)(IoC容器的实现)(1)》

1.从接口BeanFactory到HierarchicalBeanFactory,再到ConfigurableBeanFactory,是一条主要的BeanFactory设计路径。

    在这条接口设计路径中,BeanFactory接口定义了基本的IOC容器规范。在这个接口定义中,BeanFactory包括了getBean()这样的IoC容器的基本方法(通过这个方法可以从容器中获取Bean)。而HierarchicalBeanFactory接口在继承了BeanFactory的基本接口之后,增加了getParentBeanFactory()的接口功能,使得BeanFactory具备了双亲IoC容器的管理功能。

    下面的ConfigurableBeanFactory主要定义了对BeanFactory的配置功能,比如setParentBeanFactory()设置双亲IoC容器,通过addBeanPostProcessor()配置Bean的后置处理器等。通过这些接口设计的叠加,定义了BeanFactory最简单的IOC容器的基本功能。

2.第二条设计主线是:以ApplicationContext应用上下文接口为核心的接口设计。

  这里涉及的主要设计接口有,从BeanFactory到ListableBeanFactory,再到ApplicationContext,再到常用的WebApplicationContext或者ConfigurableApplicationContext接口。我们常用的应用上下文基本都是ConfigurableApplicationContext或者WebApplicationContext的实现。

    在这个接口体系中,ListableBeanFactory和HierarchicalBeanFactory两个接口,连接BeanFactory接口定义和ApplicationContext应用上下文接口定义。

    在ListableBeanFactory接口中,细化了许多BeanFactory的接口功能,比如定义了setBeanDefinitionNames接口方法;

    对于ApplicationContext接口,它通过继承了MessageSource、ResourceLoader、ApplicationEventPublisher接口,在BeanFactory简单Ioc容器的基础上添加了许多对高级容器的特性的支持。

3.图中涉及的是主要的接口关系,而具体的Ioc容器都是在这个接口体系下实现的,比如DefaultListableBeanFactory,这个基本Ioc容器的实现就是实现了ConfigurableBeanFactory,从而成为了一个简单Ioc容器的实现。像其他的Ioc容器,比如XMLBeanFactory,都是在DefaultListableBeanFactory的基础上做扩展。同样,ApplicationContext的实现也是如此。

4.这个接口系统是以BeanFactory和ApplicationContext为核心的,而BeanFactory又是Ioc容器的最基本的接口,在ApplicationContext的设计中,一方面,可以看到他继承了HierarchicalBeanFactory等BeanFactory的接口,具备了BeanFactory Ioc容器的基本功能,另外一方面,通过继承MessageSource、ResourceLoader、ApplicationEventPublisher这些接口,BeanFactory为ApplicationContext赋予了更高级的Ioc容器特性。对于ApplicationContext而言,为了在Web环境中使用它,还设计了WebApplicationContext接口,而这个接口通过继承ThemeSource接口来扩充功能。

《Spring源码分析(二)(IoC容器的实现)(1)》

下面通过XmlBeanFactory源码看一下基本的IoC容器的实现:

《Spring源码分析(二)(IoC容器的实现)(1)》

    XmlBeanFactory继承自DefaultListableBeanFactory,DefaultListableBeanFactory这个类十分重要,是经常用到的一个IoC容器的实现,如在设计ApplicationContext时也用到了。DefaultListableBeanFactory包含了基本IoC容器所具有的重要功能。

    在XmlBeanFactory中初始化了一个XmlBeanDefinitionReader对象,通过这个对象那些以XML方式定义的BeanDefinition就有了处理的地方。

public class XmlBeanFactory extends DefaultListableBeanFactory {

	//XML Bean定义的Bean定义阅读器。将实际的XML文档读取委托给BeanDefinitionDocumentReader接口的实现。
	private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);

	public XmlBeanFactory(Resource resource) throws BeansException {
		this(resource, null);//Resource是Spring用来封装I/O的操作类
	}

	public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
		super(parentBeanFactory);
		this.reader.loadBeanDefinitions(resource);
	}

}

以编程的方式进行实现:

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

这就是通过factory对象来使用DefaultListableBeanFactory这个IoC容器。其步骤:

  1. 创建IoC配置文件的抽象资源,这个资源包含了BenaDefinition的定义信息。(BeanDefinition描述了bean实例,它具有属性值、构造函数参数值和具体实现提供的进一步信息。)
  2. 创建BeanFactory,这里用DefaultListableBeanFactory。
  3. 创建载入BeanDefinition的读取器,这里用XmlBeanDefinitionReader来载入XML文件形式的BeanDefinition,通过一个回调配置给BeanFactory。
  4. 从定义好的资源位置读入配置信息,具体解析由XmlBeanDefinitionReader来完成。完成整个载入和注册后IoC容器就建立起来了,这时候可以直接使用IoC容器。

ok下面看一下ApplicationContext容器的设计原理,这里用FileSystemXmlApplicationContext来说明:

《Spring源码分析(二)(IoC容器的实现)(1)》

    在FileSystemXmlApplicationContext的设计中,我们看到ApplicationContext应用上下文的主要功能已经在FileSystemXmlApplicationContext的基类AbstractXmlApplication中实现了,在FileSystemXmlApplicationContext中,作为一个具体的应用上下文,只需要实现和它自身设计相关的两个功能。

1.如果应用直接使用FileSystemXmlApplicationContext,对于实例化这个应用上下文的支持,同时启动IoC容器的refresh()过程

	public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
			throws BeansException {

		super(parent);
		setConfigLocations(configLocations);//设置此应用程序上下文的配置位置。如果没有设置,实现可能会在适当的时候使用默认值。
		if (refresh) {
			refresh();//AbstractApplicationContext的refresh
		}
	}

    这个refresh()过程会牵涉IoC容器启动的一系列复杂操作,同时,对于不同的容器实现,这些操作都是类似的,因此在基类中将它们封装好。所以,我们在FileSystemXmlApplicationContext的设计中看到的只是一个简单的调用。 

2.与FileSystemXmlApplicationContext设计具体相关的功能,这部分与怎样从文件系统中加载XML的Bean定义资源有关,通过这个过程可以为在文件系统中读取以XML形式存在的BeanDefinition做准备,因为不同的应用上下文实现对应不同的读取BeanDefinition的方式。

	protected Resource getResourceByPath(String path) {
		if (path != null && path.startsWith("/")) {
			path = path.substring(1);
		}
		return new FileSystemResource(path);//获取资源的定位
	}

通过调用这个方法可以得到FileSystemResource的资源定位。

参考《SPRING技术内幕》

    原文作者:Spring Boot
    原文地址: https://blog.csdn.net/qq_37598011/article/details/84261417
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞