Spring MVC 中web容器和IOC容器启动过程

【本文是为了梳理知识的总结性文章,总结了一些自认为相关的重要知识点,只为巩固记忆以及技术交流,忘批评指正。其中参考了很多前辈的文章,包括图片也是引用,如有冒犯,侵删。】

1 概念

web容器:可以部署多个WEB应用程序的环境,例如Tomcat、weblogic、JBoss等。

IOC容器:Inversion of Control 容器,是Spring的两大基本功能之一(还有AOP),用来管理bean以及其依赖关系。

容器上下文:所谓上下文,它是用来存储系统的一些初始化信息。例如web容器上下文ServletContext,是一个全局的储存信息的空间。服务器启动,其就存在,服务器关闭,其才释放。所有用户共用一个ServletContext。所以,为了节省空间,提高效率,ServletContext中,要放必须的、重要的、所有用户需要共享的线程又是安全的一些信息。

在Web环境中,我们的程序要部署到web容器上才能工作,而我们的程序如果使用了Spring来进行bean及其依赖关系管理,则必须使用IOC容器。Spring IoC是一个独立的模块,它并不是直接在Web容器中发挥作用的,那么Spring loC容器是如何在Web环境中被载入并起作用的呢?

如果要在Web环境中使用loC容器,需要Spring为loC设计一个启动过程,把IoC容器导入,并在Web容器中建立起来。具体说来,这个启动过程是和Web容器的启动过程集成在一起的。在这个过程中,一方面处理Web容器的启动,另一方面通过设计特定的Web容器拦截器,将IoC容器载入到Web环境中来,并将其初始化。在这个过程建立完成以后,IoC容器才能正常工作,而Spring MVC是建立在loC容器的基础上的,这样才能建立起MVC框架的运行机制,从而响应从Web容器传递的HTTP请求。

2 ContextLoaderListener

在web.xml中配置Spring时我们经常会使用以下部署描述:

<listener>
    <listener-class>
        org.springframework.web.context.ContextLoaderListener
    </listener-class>
</listener>

ContextLoaderListener是Spring提供的类,是为在Web容器中建立loC容器服务的。ContextLoaderListener 是一个监听器,这个监听器是与Web容器的生命周期相关联的,由ContextLoaderListener监听器负责完成loC容器在Web环境中的启动工作。loC容器的启动过程就是建立上下文的过程,该上下文是与ServletContext相伴而生的。

在ContextLoaderListener中,实现了ServletContextListener接口,这个接口是在ServletAPI中定义的,这个接口里的函数会结合Web容器的生命周期被调用。因为ServletContextListener是ServletContext的监听者,如果ServletContext发生变化,会触发出相应的事件,而监听器一直在对这些事件进行监听,如果接收到了监听的事件,就会做出预先设计好的响应动作。由于ServletContext的变化而触发的监听器的响应具体包括:在服务器启动时,ServletContext被创建的时候;服务器关闭时,ServletContext将被销毁的时候等。
对应这些事件及Web容器状态的变化,在监听器中定义了对应的事件响应的回调方法。比如在服务器启动时,ServletContextListener 的contextlnitialized()方法被调用,服务器将要关闭时,ServletContextListener的 contextDestroyed()方法被调用。这样我们就可以跟随这web容器的启动而创建IOC容器,关闭时销毁IOC容器,实现Web容器和IOC容器的共同启动。

了解了Web容器中监听器的工作原理,下面看看服务器启动时ContextLoaderListener的调用完成了什么。在这个初始化回调中,创建了ContextLoader,同时会利用创建出来的ContextLoader来完成IoC容器的初始化。而ContextLoader本身就是ContextLoaderListener的基类,所以可以直接赋值this。

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {

	private ContextLoader contextLoader;

	public ContextLoaderListener() {
	}

	public ContextLoaderListener(WebApplicationContext context) {
		super(context);
	}

	/**
	 * Initialize the root web application context.
	 */
	public void contextInitialized(ServletContextEvent event) {
		this.contextLoader = createContextLoader();
		if (this.contextLoader == null) {
			this.contextLoader = this;
		}
		this.contextLoader.initWebApplicationContext(event.getServletContext());
	}

	/**
	 * Create the ContextLoader to use. Can be overridden in subclasses.
	 */
	@Deprecated
	protected ContextLoader createContextLoader() {
		return null;
	}

	/**
	 * Return the ContextLoader used by this listener.
	 */
	@Deprecated
	public ContextLoader getContextLoader() {
		return this.contextLoader;
	}


	/**
	 * Close the root web application context.
	 */
	public void contextDestroyed(ServletContextEvent event) {
		if (this.contextLoader != null) {
			this.contextLoader.closeWebApplicationContext(event.getServletContext());
		}
		ContextCleanupListener.cleanupAttributes(event.getServletContext());
	}

}

3 ContextLoader

具体的载入IoC容器的过程是由ContextLoaderListener交由ContextLoader来完成的。在这个初始化过程中,需要完成根上下文在Web容器中的创建。这个根上下文是作为Web容器中唯一的实例而存在的,如果初始化过程中,发现根上下文已经被创建了,则会抛出异常提示创建失败。根上下文创建成功以后,会被存到Web容器的ServletContext中去,供需要时使用。存取这个根上下文的路径是由Spring预先设置好的,在ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE的属性中定义这个路径,这个路径默认被设置为:

ROOT_WEB_APPLICATTON_CONTEXT_ATTRIBUTE=WebApplicationContext.class.getName()+".RooT"

 

	public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
	// 判断根上下文是否存在 
		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!");
		 }
		long startTime = System.currentTimeMillis();

		try {
			// 创建WebApplicationContext
			if (this.context == null) {
				this.context = createWebApplicationContext(servletContext);
			}
			if (this.context instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
				if (!cwac.isActive()) {
					if (cwac.getParent() == null) {
						ApplicationContext parent = loadParentContext(servletContext);
						cwac.setParent(parent);
					}
					configureAndRefreshWebApplicationContext(cwac, servletContext);
				}
			}
		// 保存上下文到servletContext			 
		servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
			ClassLoader ccl = Thread.currentThread().getContextClassLoader();
			if (ccl == ContextLoader.class.getClassLoader()) {
				currentContext = this.context;
			}
			else if (ccl != null) {
				currentContextPerThread.put(ccl, this.context);
			}

			return this.context;
		}
		catch (RuntimeException ex) {
			throw ex;
		}
		catch (Error err) {
			throw err;
		}
	}

具体的根上下文的创建如下代码所示,使用什么样的类作为上下文是在determineContextClass方法中确定的。

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
		Class<?> contextClass = determineContextClass(sc);
		if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
			throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
					"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
		}
		return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
	}

在确定使用何种IoC容器的过程中可以看到,应用可以在部署描述符中指定使用什么样的IoC容器,这个指定操作是通过CONTEXT_CLASS_PARAM参数的设置完成的。如果没有指定特定的loC容器,将使用默认的IoC容器,也就是XmlWebApplicationContext对象作为在Web环境中使用的IoC容器。

protected Class<?> determineContextClass(ServletContext servletContext) {
		String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
		if (contextClassName != null) {
			try {
				return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
			}
			catch (ClassNotFoundException ex) {
				throw new ApplicationContextException(
						"Failed to load custom context class [" + contextClassName + "]", ex);
			}
		}
		else {
			contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
			try {
				return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
			}
			catch (ClassNotFoundException ex) {
				throw new ApplicationContextException(
						"Failed to load default context class [" + contextClassName + "]", ex);
			}
		}
	}

4 配置自定义IOC容器

如果需要使用自定义的IOC容器类,可以再Web.xml中进行如下配置:

<context-param>
    <param-name>contextClass</param-name>
	<param-value>
		com.bussiness.framework.springexp.CustomizeXmlApplicationContext
	</param-value>
</context-param>

那么spring启动的时候就会使用自定义类进行IOC容器初始化了。

5 参考

  1. 《Spring技术内幕》
  2. Spring framework 3.2.6
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值