三、IOC容器原理

    1、概述

        Sun ONE技术体系下的IOC容器有:轻量级的有Spring、Guice、Pico Container、Avalon、HiveMind;重量级的有EJB;不轻不重的有JBoss,Jdon等等。Spring框架作为Java开发中SSH(Struts、Spring、Hibernate)三剑客之一,大中小项目中都有使用,非常成熟,应用广泛,EJB在关键性的工业级项目中也被使用,比如某些电信业务。.Net技术体系下的IOC容器有:Spring.Net、Castle等等。Spring.Net是从Java的Spring移植过来的IOC容器,Castle的IOC容器就是Windsor部分。它们均是轻量级的框架,比较成熟,其中Spring.Net已经被逐渐应用于各种项目中。

        Spring的核心是容器,而容器并不唯一,框架本身就提供了很多个容器的实现,大概分为两种类型:一种是不常用的BeanFactory,这是最简单的容器,只能提供基本的DI功能;还有一种就是继承了BeanFactory后派生而来的应用上下文,应用上下文即是Spring容器抽象的一种实现,应用上下文的抽象接口也就是我们上面提到的ApplicationContext,ApplicationContext本质上说就是一个维护Bean定义以及对象之间协作关系的高级接口。它能提供更多企业级的服务,例如解析配置文本信息等等,这也是应用上下文实例对象最常见的应用场景。有了上下文对象,我们就能向容器注册需要Spring管理的对象了。对于上下文抽象接口,Spring也为我们提供了多种类型的容器抽象的实现:

public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver {...}

        (1)、AnnotationConfigApplicationContext:从一个或多个基于java的配置类中加载上下文定义,适用于java注解的方式;

        (2)、ClassPathXmlApplicationContext:从类路径下的一个或多个xml配置文件中加载上下文定义,适用于xml配置的方式;

        (3)、FileSystemXmlApplicationContext:从文件系统下的一个或多个xml配置文件中加载上下文定义,也就是说系统盘符中加载;

        (4)、AnnotationConfigWebApplicationContext:专门为web应用准备的,适用于注解方式;

        (5)、XmlWebApplicationContext:从web应用下的一个或多个xml配置文件加载上下文定义,适用于xml配置方式。

    2、依赖注入(Dependency Injection)和IOC(Inversion of Control)概述

        前提对象A依赖于对象B,那么对象A在初始化或者运行到某一点的时候,自己必须主动去创建对象B或者查找引用已经创建对象B。无论是创建还是查找引用对象B,控制权都在对象A自己手上。但是由于IOC容器的加入,对象A与对象B之间失去了直接联系,所以,当对象A运行到需要对象B的时候,IOC容器会主动创建一个对象B注入到对象A需要的地方。因此,对象A获得对象B的过程,由主动行为变为了被动行为,控制权颠倒过来了,这就是“控制反转”。所谓依赖注入,就是由IOC容器在运行期间,动态地将对象A和对象B的某种依赖关系注入到对象A之中,获得依赖对象的过程由自身管理变成了由IOC容器主动注入。所以,依赖注入(DI)和控制反转(IOC)是从不同的角度的描述的同一件事情,就是指通过引入IOC容器,利用依赖注入的方式,实现控制反转,进而实现对象之间的解耦。IOC容器就是一个对象制造工厂,你需要什么,它会给你送去,你直接使用就行了,而再也不用去关心你所用的东西是如何制成的,也不用关心最后是怎么被销毁的,这一切全部由IOC容器包办。

    3、体系结构

        (1)、BeanFactory

哪些常用的IOC控制容器 ioc容器有哪些_spring

            BeanFactory是接口,提供了IOC容器最基本的形式,给具体的IOC容器的实现提供了规范,是一个典型的工厂模式(接口),它是负责生产和管理bean的一个工厂。在Spring中,BeanFactory是IOC容器的核心接口,它的职责包括:实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。BeanFactory只是个接口,并不是IOC容器的具体实现,但是Spring容器给出了很多种实现,如DefaultListableBeanFactory、XmlBeanFactory、ApplicationContext等。其中XmlBeanFactory就是常用的一个,该实现将以XML方式描述组成应用的对象及对象间的依赖关系。XmlBeanFactory类将持有此XML配置元数据,并用它来构建一个完全可配置的系统或应用。DefaultListableBeanFactory、XmlBeanFactory、ApplicationContext等具体的容器都是实现了BeanFactory,再在其基础之上附加了其他的功能。BeanFactory和ApplicationContext就是Spring框架的两个IOC容器,现在一般使用ApplicationContext,其不但包含了BeanFactory的作用,同时还进行更多的扩展。ApplicationContext以一种更面向框架的方式工作以及对上下文进行分层和实现继承,还提供了一些支持信息源、访问资源和支持应用事件等附加服务。

            BeanFactory:最顶层的一个接口类,定义了IOC容器的基本功能规范

            ListableBeanFactory:表示这些 Bean是可列表的,Bean的集合

            HierarchicalBeanFactory:表示的是这些Bean是有继承关系的,Bean之间的关系

            AutowireCapableBeanFactory:定义Bean的自动装配规则,Bean的行为

        (2)、BeanDefinition

            BeanDefinition是配置文件<bean>元素标签在容器中地内部表示。<bean>元素标签拥有class、scope、lazy-init等配置属性,BeanDefinition则提供了相应地beanClass、scope、lazyInit类属性,BeanDefinition就像<bean>的镜中人,二者是一一对应的。

            创建BeanDefinition主要包括两个步骤:

            ①、利用BeanDefinitionReader读取承载配置信息的Resource,通过XML解析器解析配置信息的DOM对象,简单地每个<bean>生成对应地BeanDefinition对象。但是这里生成的BeanDefinition可能是半成品,因为在配置文件中,可能通过占位符变量引用外部属性文件的属性,这些占位符变量在这一步里还没有被解析出来;

            ②、利用容器中注册的BeanFatoryPostProcessor对半成品的BeanDefinition进行加工处理,将以占位符表示的配置解析为最终的实际值,这样半成品的BeanDefinition就成为成品的BeanDefinition。

    4、Spring IOC容器初始化流程

哪些常用的IOC控制容器 ioc容器有哪些_ioc_02

        (1)、Web应用是部署在Web容器(Tomcat)中,Web容器启动时会读取项目中web.xml中的配置项来生成上下文对象ServletContext,ServletContext是一个全局的上下文环境,整个Web项目都能使用这个上下文,为后面的Spring IOC容器提供宿主环境。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
    <!-- Spring资源上下文定义,在指定地址找到Spring的xml配置文件 -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>
    <!-- Spring的上下文监听器 -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <!-- DispatcherServlet的配置,这个Servlet主要用于前端控制,这是SpringMVC的基础 -->
    <servlet>
        <servlet-name>springDispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
</web-app>

        (2)、容器将读取<context-param>Bean定义的配置并转化为键值对传给ServletContext,这时候Web项目还没有完全启动完成,这个动作会比所有的Servlet都要早。

        (3)、创建<listener></listener>中的类实例,即创建监听,节点中的监听器类必须实现ServletContextListener接口。

        (4)、Web容器启动时,会触发容器初始化事件,此时contextLoaderListener会监听到这个事件,其contextInitialized()方法会被调用。

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
    //初始化方法
    public void contextInitialized(ServletContextEvent event) {
        this.initWebApplicationContext(event.getServletContext());
    }
    //销毁方法,用于关闭应用前释放资源,比如说数据库连接的关闭等
    public void contextDestroyed(ServletContextEvent event) {
        this.closeWebApplicationContext(event.getServletContext());
        ContextCleanupListener.cleanupAttributes(event.getServletContext());
    }
}
/**
 * ContextLoader类的初始化Spring容器方法
 */
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    //SpringIOC容器的重复性创建校验
    if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
        throw new IllegalStateException("Cannot initialize context because there is already a root application context present - check whether you have multiple ContextLoader* definitions in your web.xml!");
    } else {
        ......
        try {
            //创建Spring容器实例
            if (this.context == null) {
                this.context = this.createWebApplicationContext(servletContext);
            }
            if (this.context instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)this.context;
                if (!cwac.isActive()) {
                    ...... 
                    //重点操作:配置并刷新容器
                    this.configureAndRefreshWebApplicationContext(cwac, servletContext);
                }
            }
            //将创建完整的Spring容器作为一条属性添加到Servlet容器中
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
            ......             
            return this.context;
        } catch (Error | RuntimeException var8) {
            ......
        }
    }
}

            在这个方法中,Spring会初始化一个启动上下文,这个上下文被称为根上下文,即WebApplicationContext,这是一个接口类,确切的说,其实际的实现类是XmlWebApplicationContext。这个就是Spring的IOC容器,其对应的Bean定义的配置由web.xml中的context-param标签指定。在这个IOC容器初始化完毕后,Spring以WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE为属性Key,将其存储到ServletContext中,便于获取。

        (5)、读取web.xml中的配置<servlet>初始化Servlet。

哪些常用的IOC控制容器 ioc容器有哪些_BeanFactory_03

哪些常用的IOC控制容器 ioc容器有哪些_BeanFactory_04

            ①、Servlet接口中定义了Servlet — init(ServletConfig var1)方法,该方法是DispatcherServlet类初始化入口,容器初始化Servlet会调用该方法。

            ②、抽象类GenericServlet实现了接口Servlet — init(ServletConfig config)方法,并在该方法中调用自己的GenericServlet — init()方法。

            ③、抽象类HttpServletBean重写了父类HttpServlet的父类GenericServlet — init()方法,并在该方法中调用自己的HttpServletBean — initServletBean()方法。

            ④、抽象类FrameworkServlet重写了父类HttpServletBean — initServletBean()方法,并在该方法中调用自己的FrameworkServlet — initWebApplicationContext()方法,然后又在initWebApplicationContext()方法中调用自己的FrameworkServlet — onRefresh(ApplicationContext context)方法。

                DispatcherServlet上下文在初始化的时候会建立自己的IOC上下文,用以持有Spring MVC相关的Bean。在建立DispatcherServlet自己的IOC上下文时,在方法initWebApplicationContext()中会利用WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,先从ServletContext中获取之前的根上下文(即WebApplicationContext)作为自己上下文的Parent上下文,再初始化自己持有的上下文。

            ⑤、类DispatcherServlet重写了父类FrameworkServlet — onRefresh(ApplicationContext context)方法,并在该方法中调用自己的DispatcherServlet — initStrategies(ApplicationContext context)方法。

protected void initStrategies(ApplicationContext context) {
    this.initMultipartResolver(context);
    this.initLocaleResolver(context);
    this.initThemeResolver(context);
    //初始化Handler映射关系
    this.initHandlerMappings(context);
    //初始化Handler适配器
    this.initHandlerAdapters(context);
    //Handler执行发生异常的异常解析器
    this.initHandlerExceptionResolvers(context);
    this.initRequestToViewNameTranslator(context);
    //解析View对象的视图解析器
    this.initViewResolvers(context);
    this.initFlashMapManager(context);
}

            DispatcherServlet初始化自己上下文的工作在initStrategies(ApplicationContext context)方法中,此Servlet自己持有的上下文默认实现类也是xmlWebApplicationContext。初始化完毕后,Spring以与Servlet的名字相关(此处不是简单的以servlet名为Key,而是通过一些转换,具体可自行查看源码)的属性为属性Key,也将其存到ServletContext中,以便后续使用。这样每个Servlet就持有自己的上下文,即拥有自己独立的bean空间,同时各个servlet共享相同的bean,即根上下文(第4步中初始化的上下文)定义的那些bean。