三、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
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容器初始化流程
(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。
①、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。