Spring MVC 启动源码分析

Spring 和 Spring MVC 的 IOC 之间容器关系

概述

上图是引用 Spring 官方网站的图片。Spring MVC 的 IOC 容器是在 Spring 的 IOC 容器延深展开来的。

首先分析配置文件 web.xml

<web-app>
	<!-- Spring 配置 -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/root-context.xml</param-value>
    </context-param>
	<!-- Spring MVC 配置 -->
    <servlet>
        <servlet-name>app1</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/app1-context.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>app1</servlet-name>
        <url-pattern>/app1/*</url-pattern>
    </servlet-mapping>

</web-app>

上面是经典的 Spring 和 Spring MVC 在 web.xml 文件。

Spring 容器初始化

为了配置 Spring 的初始化,指定了一个 ContextLoaderListener 监听器,它的原本也是在 ServletContextListener 实现上来的,可以查看源码

ContextLoaderListener.java

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
	public ContextLoaderListener() {
	}
	public ContextLoaderListener(WebApplicationContext context) {
		super(context);
	}
	//初始化加载方法
	public void contextInitialized(ServletContextEvent event) {
		initWebApplicationContext(event.getServletContext());
	}
	//销毁加载方法
	public void contextDestroyed(ServletContextEvent event) {
		closeWebApplicationContext(event.getServletContext());
		ContextCleanupListener.cleanupAttributes(event.getServletContext());
	}
}

我们知道,在监听器中 contextInitialized 方法和 contextDestroyed 方法是必须执行的。而 ContextLoaderListener 就实现了这两个方法,并且在初始化方法 contextInitialized 调用了 initWebApplicationContext 方法来初始化 WebApplicationContext 容器。我们坠入 initWebApplicationContext 一看究竟。

	public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
		...
		try {
			//如果 context 为空,那么创建 xmlApplicationContext
			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.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);
			}
		...
		}
	}

该方法是由 ContextLoaderListener 的父类 ContextLoader 实现。那么为什么是 xmlApplicationContext。查看 createWebApplicationContext 方法,内部又调用了 determineContextClass 方法

	protected Class<?> determineContextClass(ServletContext servletContext) {
		//用来获取自定义的 applicationContext 的类,没有就从 defaultStrategies 读取配置文件内容
		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);
			}
		}
	}

我们在看看 defaultStrategies 如何初始化

	private static final Properties defaultStrategies;

	static {
		try {
			ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
            //加载了 resource 的配置文件
			defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
		}
		catch (IOException ex) {
			throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
		}
	}

它加载了一个配置文件,这里来就直接给配置文件的内容 ContextLoader.properties

org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext

到这里懂了吧,如果没有指定 contextClass 的话,会加载 ContextLoader.properties 资源文件的 webApplicationContext 所指定的类。这也就是为什么默认是 xmlwebApplicationContext 的实现类,而不是其他的。

回到最开始的 initWebApplicationContext 的方法,其中有一个 configureAndRefreshWebApplicationContext 方法来加载配置文件内容,追进去查看

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
		...
		//加载配置文件的参数
		String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
		if (configLocationParam != null) {
			wac.setConfigLocation(configLocationParam);
		}
		...
	}

这里就不多说了,通过 MockServletContext 实现类的 getInitParameter 方法获取了 配置文件的信息。

到这一步就懂了吧,我们在 web.xml 配置的信息,init-param 中的 contextConfigLocation 就是告诉 ContextLoaderListener ,Spring 的配置文件在哪,而我们没有配置 contextClass,所以就默认采用了 XMLWebApplicationContext。

Spring MVC 容器初始化

说完了的 Spring,那么 Spring MVC 呢?

在 web.xml 还配置了 DispatcherServlet,而 DispatcherServlet 都干了啥?我们知道 Spring MVC 的 DispatcherServlet 拦截了所有的 Servlet 请求。那么 DispatcherServlet 本质是啥?

可以清楚的看到,它是继承了 HttpServlet,所以本质就是一个 Servlet , 它有着 Servlet 所应有的功能。通过断点追踪,发现代码会先走到 HttpServletBean 的 inti 方法。在 Servlet 的生命周期中,知道 init 的方法是初始化的方法,在生成对象的时候会执行,并且只执行一次,而 DispatcherServlet 是继承 HttpServletBean,查看 HttpServletBean 的 init 源码

public final void init() throws ServletException {
		if (logger.isDebugEnabled()) {
			logger.debug("Initializing servlet '" + getServletName() + "'");
		}

		// Set bean properties from init parameters.
		PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
		.....

		// 初始化 SerlvetBean
		initServletBean();

		if (logger.isDebugEnabled()) {
			logger.debug("Servlet '" + getServletName() + "' configured successfully");
		}
	}

断点继续进入 initServletBean,该方法由 FrameworkServlet 类实现

protected final void initServletBean() throws ServletException {
		getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
		...
		try {
			// 初始化 initWebApplication
			this.webApplicationContext = initWebApplicationContext();
			initFrameworkServlet();
		}
		catch (ServletException ex) {
			this.logger.error("Context initialization failed", ex);
			throw ex;
		}
		catch (RuntimeException ex) {
			this.logger.error("Context initialization failed", ex);
			throw ex;
		}
		...
		}
	}

这里断点跟踪又进入了 initWebApplicationContext,而它正是由我们的 DispatchedServlet 实现的。进入查看

	protected WebApplicationContext initWebApplicationContext() {
		//获取根容器
		WebApplicationContext rootContext =
				WebApplicationContextUtils.getWebApplicationContext(getServletContext());
		WebApplicationContext wac = null;

		if (this.webApplicationContext != null) {
			///在构造时如果注入一个上下文实例->使用它
			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
						//把跟容器设置给 Spring MVC 当父容器
						cwac.setParent(rootContext);
					}
					configureAndRefreshWebApplicationContext(cwac);
				}
			}
		}
		if (wac == null) {
			
			wac = findWebApplicationContext();
		}
		if (wac == null) {
			// 这个 servlet 如果没有定义上下文实例,那么创建一个本地实例,把父容器传入
			wac = createWebApplicationContext(rootContext);
		}

		if (!this.refreshEventReceived) {
			onRefresh(wac);
		}
	...

		return wac;
	}

查看断点内容,如果你只是配置了一个 Spring MVC 内容,没有监听器,rootContext 就为 null,

如果配置了监听器,那么就不是 null 了

之后,断点进入了 createWebApplicationContext ,注意这里的 IOC 容器就是 Spring MVC 的,跟 Spring (也就是前面讲的)IOC 容器是不同的,它是基于 Spring 的 IOC 容器之上,所以它们是包含关系(Spring 包含 Spring MVC 的容器)。查看 createWebApplicationContext 源码(由父类 FrameworkServlet 实现)

	protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
	...
		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());
		// 设置父容器
		wac.setParent(parent);
		// 获取配置文件的位置
		String configLocation = getContextConfigLocation();
		if (configLocation != null) {
			wac.setConfigLocation(configLocation);
		}
		//配置并且刷新 WebApplicationContext
		configureAndRefreshWebApplicationContext(wac);

		return wac;
	}

到这里也就懂了把,如果没有 contexloaderListener的情况下,parent是空的。在有 contexloaderListener的情况下,发现parent不是空的。也就完成了包含关系。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值