栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Tomcat 什么时候加载 Spring 的容器?

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Tomcat 什么时候加载 Spring 的容器?

目录

前言

ServletContextListener

ContextLoaderListener

Spring MVC 容器

总结


前言

        我们前面在测试 Spring 的时候都是手动实例化上下文对象,然后指定配置类或者配置文件来启动容器进而实例化配置的 bean 对象。可是等逻辑代码执行完了,程序就终止了。这跟我们用 Spring boot 的时候完全不一样,问题就在于 Spring boot 有了 tomcat 的帮助,Tomcat 进行了 Spring 容器的实例化以及各个 bean 的实例化。并且只要 tomcat 不停止,Spring 容器就不会被销毁,可以反复使用,这样跟 Spring MVC 再结合起来才有意义,不然跑一次程序就终止了,根本就没有实际意义。那么本文就看一下 Tomcat 如何加载实例化 Spring 容器的。

ServletContextListener
public interface ServletContextListener extends EventListener {

    // 容器启动的过程中,实例化 ServletContext 的时候会调用该方法
    default public void contextInitialized(ServletContextEvent sce) {}

    // ServletContext 被关闭的时候会调用该方法
    default public void contextDestroyed(ServletContextEvent sce) {}
}

        这是一个监听器接口,用来监听 ServletContext ,当 ServletContext 初始化后会调用 ContextInitialized 方法,当容器关闭或者应用关闭的时候又会调用 contextDestroyed 方法,而 ServletContext 又是整个应用范围的对象,所以在这里管理加载 Spring 容器简直是再不好不过了。

ContextLoaderListener
    
        
            org.springframework.web.context.ContextLoaderListener
        
    

    
        contextConfigLocation
        classpath:spring-core.xml
    

        一般在 Spring MVC 项目中都会在 web.xml 中添加这两个配置,ContextLoaderListener 是 ServletContextListener 的字类,同时又通过 contextConfigLocation 配置了 Spring 的配置文件,所以可以猜测,这个监听器就是用来加载 Spring 容器的。

    public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
	// 构造函数
	public ContextLoaderListener() {
	}

	// 构造函数
	public ContextLoaderListener(WebApplicationContext context) {
		super(context);
	}

	// 重写初始化方法
	@Override
	public void contextInitialized(ServletContextEvent event) {
		initWebApplicationContext(event.getServletContext());
	}

	// 重写销毁方法
	@Override
	public void contextDestroyed(ServletContextEvent event) {
		closeWebApplicationContext(event.getServletContext());
		ContextCleanupListener.cleanupAttributes(event.getServletContext());
	}
}

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!");
		}

		servletContext.log("Initializing Spring root WebApplicationContext");
		Log logger = LogFactory.getLog(ContextLoader.class);
		if (logger.isInfoEnabled()) {
			logger.info("Root WebApplicationContext: initialization started");
		}
		long startTime = System.currentTimeMillis();

		try {
			// Store context in local instance variable, to guarantee that
			// it is available on ServletContext shutdown.
			if (this.context == null) {
                // 创建 Spring 容器,默认为 XmlWebApplicationContext 类型的上下文对象, 在 ContextLoader.properties 文件中配置
				this.context = createWebApplicationContext(servletContext);
			}
			if (this.context instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
				if (!cwac.isActive()) {
					// The context has not yet been refreshed -> provide services such as
					// setting the parent context, setting the application context id, etc
					if (cwac.getParent() == null) {
						// The context instance was injected without an explicit parent ->
						// determine parent for root web application context, if any.
						ApplicationContext parent = loadParentContext(servletContext);
						cwac.setParent(parent);
					}
                    // 通过 servletContext 获取到的 contextConfigLocation 路径来初始化上下文
					configureAndRefreshWebApplicationContext(cwac, servletContext);
				}
			}
// 将 Spring 的上下文对象保存到 servletContext 对象中,实现全局共享
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);   
			//。。删除部分代码   
			return this.context;
		} catch (RuntimeException | Error ex) {
			logger.error("Context initialization failed", ex);
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
			throw ex;
		}
	}

    protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
		//... 删除部分代码
        
        // Spring 上下文设置 ServletContext 信息
		wac.setServletContext(sc);
        // 读取在 web.xml 中配置的 Spring 配置文件信息 contextConfigLocation
		String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
		if (configLocationParam != null) {
			wac.setConfigLocation(configLocationParam);
		}

		// The wac environment's #initPropertySources will be called in any case when the context
		// is refreshed; do it eagerly here to ensure servlet property sources are in place for
		// use in any post-processing or initialization that occurs below prior to #refresh
		ConfigurableEnvironment env = wac.getEnvironment();
		if (env instanceof ConfigurableWebEnvironment) {
			((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
		}

		customizeContext(sc, wac);
        // 调用 Spring 上下文的 refresh 方法,实例化 Spring 中的各个 bean
		wac.refresh();
	}

        通过上面的分析可以得出结论,Spring 上下文是在 ServletContext 实例化之后才实例化的,默认创建的上下文对象为 XmlWebApplicationContext 类型。实例化上下文对象之后还会放入 ServletContext 对象中保存,实现应用全局共享。

Spring MVC 容器

         Spring 和 Spring MVC 整合的时候,不止会有上述的 Spring 容器,还会有 Spring MVC 的容器,它负责管理 Spring MVC 的 bean,下面看下它的上下文对象是什么时候初始化的。

        我们知道当 Tomcat 启动的时候如果 Servlet 设置了 load-on-startup = 1 会在启动容器的时候就加载 Servlet 实例,而加载 Servlet 实例的时候会调用 Servlet 的 init 方法。

        因为 DispatcherServlet 设置了启动即加载,所以最终会调用到 FramworkServlet.initWebApplicationContext 方法,前面讲 Controller 方法执行流程的时候已经分析过,这里就不多言了。所以我们来观察一下 initWebApplicationContext 方法。

protected WebApplicationContext initWebApplicationContext() {
		WebApplicationContext rootContext =
		
// 在这里就是从 ServletContext 中获取到 Spring 的上下文。也就是上部分分析创建的上下文对象		    WebApplicationContextUtils.getWebApplicationContext(getServletContext());
		WebApplicationContext wac = null;

        // 除非自定义给 DispatcherServlet 指定上下文,否则这里会为空
		if (this.webApplicationContext != null) {
			// A context instance was injected at construction time -> use it
			wac = this.webApplicationContext;
			if (wac instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
				if (!cwac.isActive()) {
					// The context has not yet been refreshed -> provide services such as
					// setting the parent context, setting the application context id, etc
					if (cwac.getParent() == null) {
						// The context instance was injected without an explicit parent -> set
						// the root application context (if any; may be null) as the parent
						cwac.setParent(rootContext);
					}
					configureAndRefreshWebApplicationContext(cwac);
				}
			}
		}
		if (wac == null) {
			// 这也是需要自己指定才能获取到上下文对象
			wac = findWebApplicationContext();
		}
		if (wac == null) {
			// 默认情况下会在这里创建一个 Spring 上下文对象
			wac = createWebApplicationContext(rootContext);
		}

		if (!this.refreshEventReceived) {
			// Either the context is not a ConfigurableApplicationContext with refresh
			// support or the context injected at construction time had already been
			// refreshed -> trigger initial onRefresh manually here.
			synchronized (this.onRefreshMonitor) {
				onRefresh(wac);
			}
		}

		if (this.publishContext) {
			// Publish the context as a servlet context attribute.
			String attrName = getServletContextAttributeName();
            // 将创建好的上下文对象保存到 ServletContext 中
			getServletContext().setAttribute(attrName, wac);
		}

		return wac;
	}

// 创建 Spring MVC 上下文对象跟创建 Spring 上下文对象类似,还会设置 Spring 上下文的对象为 Spring MVC 上下文的父亲
protected WebApplicationContext createWebApplicationContext(@Nullable WebApplicationContext parent) {
		return createWebApplicationContext((ApplicationContext) parent);
	}

protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
		Class contextClass = getContextClass();
		if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
			throw new ApplicationContextException(
					"Fatal initialization error in servlet with name '" + getServletName() +
					"': custom WebApplicationContext class [" + contextClass.getName() +
					"] is not of type ConfigurableWebApplicationContext");
		}
		ConfigurableWebApplicationContext wac =
				(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

		wac.setEnvironment(getEnvironment());
        // 设置 Spring 的上下文对象为 Spring MVC 上下文对象的父亲
		wac.setParent(parent);
        // 获取 Spring-MVC 的配置文件, 通过 dispatcherServlet.contextConfigLocation 指定
		String configLocation = getContextConfigLocation();
		if (configLocation != null) {
			wac.setConfigLocation(configLocation);
		}
		configureAndRefreshWebApplicationContext(wac);

		return wac;
	}

         上面创建 Spring MVC 容器的过程跟创建 Spring 容器的过程很是类似,主要是借助 DispatcherServlet 的特性来实现的。一般在 web.xml 文件中配置

    
        dispatcherServlet
        org.springframework.web.servlet.DispatcherServlet
        
        
            contextConfigLocation
            classpath:spring-mvc.xml
        
        
        1
    

总结

        Spring 容器和 Spring MVC 容器的创建和实例化都是借助了 Tomcat 的特性,Spring 容器的创建时机为 ServletContext 实例化之后,ContextLoaderListener 监听器会调用 contextInitialized 方法来触发 Spring 容器的创建。而 Spring MVC 容器的创建则是借助 Servlet.init 方法,且 Spring 容器为 Spring MVC 的父容器,即 Spring MVC 可以访问父容器的 bean,反过来则不可以。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/324701.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号