一 什么是IOC
IOC(Inversion of Contorl)控制反转:所谓控制反转,就是把我们原先代码中需要实现的对象创建,反转给容器来实现,必然的我们需要创建一个容器,同样需要一种描述让容器知道需要创建对象与对象之间的关系,这个描述具体的表现就是我们可配置的文件。DI(Dependency Injection)依赖注入:就是对象是被动接受依赖类而不是自己主动去寻找,换而言之就是对象不是从容器中查找它的依赖类而是在容器实例化的时候主动将它的依赖类注入给它。
我们从宏观的设计视角来俯瞰这个架构考虑,如果我们是IOC的设计者,我们该怎么解决如下的问题:
对象和对象的关系怎么表示?
可以用xml,properties文件等语义化配置文件表示。描述对象关系的文件放在哪里?
可能是classpath,filesystem,或者是url网络资源,servletContext等等。有了配置文件,我们还需要对配置文件进行解析。
不同的配置文件对对象描述不一样,如标准的,自定义声明的,如何统一?在内部需要有一个统一的关于对象的定义,所以外部的描述都必须转化成统一的描述定义。如何对不同的配置文件进行解析?需要对不同的配置文件语法,采取不同的解析器。
二 Spring IOC体系架构
2.1 Beanfactory Spring Bean 的创建是典型的工厂模式,这一系列的Bean工厂,就就是IOC容器为开发人员管理对象之间依赖关系提供了很多便利和基础服务,在Spring’中有许多IOC容器的实现供用户选择和使用,其相互关系如下: 其中BeanFactory作为最顶层的一个接口类,它定义了IOC的基本功能规范。BeanFactory有三个子类:ListableBeanFactory,HierarchicalBeanfactory,AutowireCapableBeanFactory。由上图可知,最终默认的实现类是DefaultListableBeanFactory,这个类实现了所有的接口。那么我们为什么要定义这么多接口呢?主要是每个接口都有他使用的场景,区分了Spring内部对象传递和转化过程中,对对象的数据访问所做的限制。例如ListableBeanFactory表示这些Bean是可列表的,而HierachicalBeanFactory表示这些Bean都是由继承关系,每个Bean可能有父Bean。AutowireCapableBeanFactory接口定义了Bean的自动装配规则。这四个接口共同定义了Bean的集合,Bean之间的关系以及Bean行为。 最基础的IOC容器接口BeanFactory: 在BeanFactory中我们只对IOC容器的基本行为做了定义,根本不关心Bean是如何定义和加载的,正如我们只关心工厂模式中能得到什么产品,而不是关系这些产品是怎么产生的过程。 而如果要知道工厂是怎么产生对象的,我们需要具体的去看IOC容器的实现,Spring提供了许多IOC容器的实现。比如XmlBeanFactory,ClasspathXmlApplicationContext等等。其中XmlBeanFactory就是针对最基本的IOC容器的实现,这个IOC容器可以读取XML文件中定义的BeanDefinition(XML文件中对bean的描述)如果说XmlBeanFactory是容器中的低配版,nameApplicationContext就是容器中的高配版。 ApplicationContext是Spring容器提供的一个高级IOC容器,它能够提供IOC容器的基本功能之外,还为用户提供了如下的附加功能: 1 支持信息源,可以实现国际化。(实现MessageSource接口) 2 访问资源 (实现ResourcePatternResolver接口) 3 支持应用事件(实现了ApplicationEventpublisher接口) 2.2 BeanDefinition SpringIOC容器管理我们定义的各种Bean对象及其相互关系,Bean对象在Spring实现中是以BeanDefinition来描述的,其继承体系如下: Bean的解析过程非常复杂,功能被切分的很细,因为这里需要被扩展的地方很多,必须保证有足够的灵活性以应对可能的变化。Bean的解析主要就是对Spring配置文件的间隙,这个解析过程主要是通过如下图类完成 2.3 IOC容器的初始化 IOC容器的初始化包括BeanDefinition的Resource定位,载入,注册这三个基本过程。 我们以ApplicationContext为例讲解,ApplicationContext系列的容器是我们最常使用的,web项目中的XmlApplicationContext就是属于这个继承体系还有ClasspathXmlApplicationContext。其继承图如下: ApplicationContext允许上下文嵌套,通过保持父上下文可以维持一个上下文体系。对于Bean的查找可以在这个上下文体系中发生,首先检查当前上下文,其次是父上下文,逐级向上,这样为不同改的Spring应用提供一个共享的Bean定义环境。下面分别看下俩种IOC容器创建的过程
2.3.1 XmlBeanFactory
通过XmlBeanFactory源码可以发现调用的全过程还原,定位,载入,注册
2.3.2 FileSystemXmlApplicationContext 先看这个类的构造函数 首先,调用了父类容器的构造方法super(parent)为容器设置好Bean资源加载器然后再调用父类abstractRefreshableConfigApplicationContext的
setCongifLocaltions(configLocaltions)方法来设置Bean定义资源文件的定位路径通过追踪FileSystemXmlApplicationContext的继承体系,发现其父类的父类
AbstractApplicationContext中初始化IOC容器所做的主要源码如下: AbstractApplicationContext构造方法中调用PathMatchingResourcePatternResolver的构造方法中创建了Spring资源加载器 在设置容器的资源加载器之后,接下来的FileSystemXmlApplicationContext执行setConfigLocations方法通过调用其父类AbstractRefreshableConfigApplicationContext的方法进行对Bean资源文件的定位,方法如下: 通过上面的方法源码我们可以看出,我们既可以使用一个字符串来配置多个Spring Bean定义资源文件,也可以使用字符串数组: ClasspathResource res=new ClasspathResource(“a.xml,b.xml,c.cml…..”) 多个资源文件路径之间可以使用,;/t/n 分割 到这边,Spring IOC容器在初始化将配置的Bean定义资源文件定位为Spring封装的Resource。AbstractApplicationContext 的refresh函数载入Bean定义过程:
Spring IOC容器对bean定义资源的载入是从refresh()函数开始的,refresh()是一个模板方法。它的作用是:在创建IOC容器前,如果容器已经存在,则把已有的容器销毁和关闭,以保证在refresh之后使用新建立起来的IOC容器。Refresh的作用类似于IOC容器的重启,在新建立好的容器中对容器进行初始化,对Bean定义资源进行载入。 FileSystemXmlApplicationContext通过调用其父类AbstractApplicationContext的refresh函数启动整个IOC容器对于Bean定义的载入过程。 、// 容器初始化的过程,读入Bean定义资源,并解析注册 public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. // 调用容器准备刷新的方法,获取容器的当时时间,同时给容器设置同步标识 prepareRefresh(); // Tell the subclass to refresh the internal bean factory. // 告诉子类启动refreshBeanFactory()方法,Bean定义资源文件的载入从子类的refreshBeanFactory()方法启动 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. // 为BeanFactory配置容器特性,例如类加载器、事件处理器等 prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses. // 为容器的某些子类指定特殊的BeanPost事件处理器 postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context. // 调用所有注册的BeanFactoryPostProcessor的Bean invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. // 为BeanFactory注册BeanPost事件处理器. // BeanPostProcessor是Bean后置处理器,用于监听容器触发的事件 registerBeanPostProcessors(beanFactory); // Initialize message source for this context. // 初始化信息源,和国际化相关. initMessageSource(); // Initialize event multicaster for this context. // 初始化容器事件传播器 initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. // 调用子类的某些特殊Bean初始化方法 onRefresh(); // Check for listener beans and register them. // 为事件传播器注册事件监听器. registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. // 初始化所有剩余的单例Bean. finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. // 初始化容器的生命周期事件处理器,并发布容器的生命周期事件 finishRefresh(); } catch (BeansException ex) { // Destroy already created singletons to avoid dangling resources. // 销毁以创建的单态Bean destroyBeans(); // Reset 'active' flag. // 取消refresh操作,重置容器的同步标识. cancelRefresh(ex); // Propagate exception to caller. throw ex; } } }复制代码
Refresh方法主要为IOC容器的生命周期管理提供了条件,Spring IOC容器载入Bean定义资源文件从其子类容器中的refreshBeanFactory方法启动,所以整个refresh中的
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();这句以后代码的都是注册容器的信息源和生命周期,载入过程就是从这句代码启动的。 refresh的作用是:在创建IOC容器前,如果容器已经存在,则把已有的容器销毁和关闭,以保证在refresh之后使用新建立起来的IOC容器。Refresh的作用类似于IOC容器的重启,在新建立好的容器中对容器进行初始化,对Bean定义资源进行载入。 AbstractApplicationContext的obtainFreshBeanFactory方法调用子容器的refreshBeanFactory方法,启动容器载入Bean定义资源文件的过程,源码如下: 其实事实上调用的是AbstractRefreshableApplicationContext的refreshBeanFactory方法,源码如下: 这个方法中先判断了BeanFactory是否存在,如果存在先销毁beans并且关闭BeanFactory,接着创阿金DefaultListableBeanFactory,并且调用loadBeanDefinitions方法装载Bean定义。 这边的loadBeanDefinitions同样是抽象方法,容器真正调用的是他的子类AbstractXmlApplicationContext中的实现,源码如下: 然后在抽象父类AbstractBeanDefinitionReader中定义了其载入过程public int loadBeanDefinitions(String location, Set结合着ResourceLoader和ApplicationContext的继承关系,可以此时调用的是DefaultResourceLoader中的getResource方法定位Resource,因为FileSystemXmlApplicationContext本身就是DefaultResourceLoader的实现类,此时有回到了FileSystemXmlApplicationContext中。actualResources) throws BeanDefinitionStoreException { //获取在IoC容器初始化过程中设置的资源加载器 ResourceLoader resourceLoader = getResourceLoader(); if (resourceLoader == null) { throw new BeanDefinitionStoreException( "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available"); } if (resourceLoader instanceof ResourcePatternResolver) { // Resource pattern matching available. try { //将指定位置的Bean定义资源文件解析为Spring IOC容器封装的资源 //加载多个指定位置的Bean定义资源文件 Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location); //委派调用其子类XmlBeanDefinitionReader的方法,实现加载功能 int loadCount = loadBeanDefinitions(resources); if (actualResources != null) { for (Resource resource : resources) { actualResources.add(resource); } } if (logger.isDebugEnabled()) { logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]"); } return loadCount; } catch (IOException ex) { throw new BeanDefinitionStoreException( "Could not resolve bean definition resource pattern [" + location + "]", ex); } } else { // Can only load single resources by absolute URL. //将指定位置的Bean定义资源文件解析为Spring IOC容器封装的资源 //加载单个指定位置的Bean定义资源文件 Resource resource = resourceLoader.getResource(location); //委派调用其子类XmlBeanDefinitionReader的方法,实现加载功能 int loadCount = loadBeanDefinitions(resource); if (actualResources != null) { actualResources.add(resource); } if (logger.isDebugEnabled()) { logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]"); } return loadCount; } }复制代码
资源加载器获取要读入的资源:
XmlBeanDefinitionReader通过调用其父类defaultResourceLoader的getResource方法获取加载资源。源码如下: 最后的getresourcebypath又回到了FileSystemXmlApplicationContext的类方法中,源码如下: 然后再进去FileSystemResource 在这边就可以从文件系统路径上对IOC配置文件进行加载,以上寻找resource的过程就是我们定位resource的干活成,这也只是加载的一部分。【resource就是为了找到一个路径】在我们Bean定义的resource得到之后继续回到XmlBeanDefinitionReader的loadBeanDefiniyions(Resource …)方法看到代表Bean文件资源定义后的载入过程
最核心的就是获取文件流开始读取过程,在Spring源码中带do开头的就是具体干活的。 我们看下这个doLoadBeanDefinitions源码最核心的就是获取文件流开始读取过程,在Spring源码中带do开头的就是具体干活的。 我们看下这个doLoadBeanDefinitions源码 然后在这边调用了loadDocument方法 获取document对象之后我们在进行 具体的需要看到registerBeanDefinitions方法 我们继续走 然后调用doregisterBeanDefinitions【终于找到了】