(SpringMVC源码分析)Spring容器以及SpringMvc是怎么注入到Web环境的

1 篇文章 0 订阅
1 篇文章 0 订阅

Spring容器以及SpringMvc是怎么起作用的?

1.问题的提出

学习了一段时间的SSM框架的使用,但是对原理却一窍不通,最近在工作学习之余阅读了郝佳老师的《Spring源码深度解析 第2版》这本书,笔者头脑有些愚钝,书中仍有部分不太明白,但终究还是了解到了Spring与SpringMVC的大体上的原理,于是便考虑把自己的收获‘趁热’总结一下,记录出来。以下陈述均属于个人观点,但是难免因为我的个人认知能力问题导致所阐述信息与实际不符,如果您发现下面内容有哪里不对的地方,希望积极指出,谢谢!
在学习ssm框架的初期,我的大脑中常常会冒出来几个疑问,springMvc到底是怎么就到了我们的web环境中呢?为什么导入jar包以后写几个配置文件就能够直接使用Spring与SpringMvc了呢?
如果看官您也有着和我一样的疑问,那么请跟着我的思路,站在源码的角度一起来分析这几个疑问。

2.Spring容器的初始化

我们在JavaWeb项目中搭建SpringMVC环境时候首先做的就是在web.xml文件中添加一个ContextLoaderListener监听器和一对context-param

<context-param>
	<param-name>contextConfigLocation</param-name>
	<param-value>classpath:application.xml</param-value>
</context-param>
<listener>
	<listener-class>
		org.springframework.web.context.ContextLoaderListener
	</listener-class>
</listener>

正是这个ContextLoaderListener监听器帮我们在web环境中实例化了一个Spring容器,并且配置到了我们的web环境中。下面我们点开源码看一下这个监听器都做了什么。
首先使用IDEA的Ctrl + H查看一下该类的继承结构。如下图
ContextLoaderListener的继承结构

ContextLoaderListener继承ContextLoader类,实现ServletContextListener接口。
ServletContextListener接口中有一个contextInitialized(ServletContextEvent event)方法。

    /**
     * 接收正在启动web应用程序初始化过程的通知。
     * 在初始化web应用程序中的任何过滤器或servlet之前,所有servletcontextlistener都会收到上下文初始化
     * 的通知。
     * @param event 包含正在初始化的ServletContext的ServletContextEvent
     */
    public void contextInitialized(ServletContextEvent event){
		initWebApplicationContext(event.getServletContext());
	}

正是上面的initWebApplicationContex(ServletContextEvent event)t方法帮我们在web应用程序启动的时候初始化了一个Spring容器。而这个initWebApplicationContext方法是父类ContextLoader中的方法,我们进去看一下,下面代码已经把部分无关代码去除。关键代码上面加了注释。

public class ContextLoader {
	private WebApplicationContext context;
	public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
		try {
			if (this.context == null) {
				// 在这里生成的一个context
				this.context = createWebApplicationContext(servletContext);
			}
			if (this.context instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
				if (!cwac.isActive()) {
					// 在这里面将web.xml中param-name为contextConfigLocation的值取出来,配置到Spring容器中
					configureAndRefreshWebApplicationContext(cwac, servletContext);
				}
			}
			// 把我们的spring容器,添加到全局的servletContext内。没这一步,后面我们就根本找不到spring容器
			// key = org.springframework.web.context.WebApplicationContext.ROOT
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
			return this.context;
		}
		catch (RuntimeException | Error ex) {...}
	}

	public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";
	protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
		...
		// 获取到web.xml中param-name为contextConfigLocation的值取出来,配置到Spring容器
		String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
		if (configLocationParam != null) {
			wac.setConfigLocation(configLocationParam);
		}
		...
	}
}

上面代码大概做了三个事情:

  1. 在initWebApplicationContext方法中初始化了一个Spring容器,然后。
  2. 调用configureAndRefreshWebApplicationContext方法将我们在web.xml中配置的contextConfigLocation添加到了容器的属性中.
  3. 将初始化好的spring容器添加到了全局的servletContext的Attribute中,key为org.springframework.web.context.WebApplicationContext.ROOT

综上,Spring容器在JavaWeb中的初始化其实就是由我们自己配置的ContextLoaderListener监听器来完成,而ContextLoaderListener实现了ServletContextListener接口,在web程序初始化的时候会调用监听器的初始化方法,从而完成Spring容器的初始化操作。

3.SpringMVC容器的初始化

同上配置spring一样,我们在web环境配置mvc时候,首先也是去web.xml中做些‘手脚’,通常我们都会配置一个servlet到web环境,并且将‘/’所代表的全部请求路径交给这个servlet处理。在实例化servlet时候,还给出了一个init-param,key为contextConfigLocation,value就是配置文件的路径。

	<servlet>
		<servlet-name>dispatcherServlet</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>classpath:springMVC.xml</param-value>
		</init-param>
	</servlet>
	<servlet-mapping>
		<servlet-name>dispatcherServlet</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>

接下来让我们去看一下这个特殊的servlet中到底做了什么。
第一步还是先看DispatcherServlet的继承结构。
DispatcherServlet的继承结构从上图可以看出DispatcherServlet继承自FrameworkServlet,间接继承了HttpServletBean和GenericServlet。那我们就从GenericServlet开始说起。

public abstract class GenericServlet implements Servlet, ServletConfig,
		java.io.Serializable {
    /**
     * 由servlet容器(例如tomcat内的servlet容器)调用,以指示servlet正在将servlet放入服务中
     */
    @Override
    public void init(ServletConfig config) throws ServletException {
        this.config = config;
        this.init();
    }
    public void init() throws ServletException {
        // NOOP by default
    }
}

在GenericServlet 中的init()方法是重点,在servlet容器(可以暂时理解成tomcat)初始化servlet的时候,会调用这个init(ServletConfig config)方法,而这个有参方法会调用其重载的无参init()方法,在GenericServlet 类中,这个方法内是空方法体,以供子类去重写该方法来实现更多内容。我们就顺藤摸瓜看看是谁Override了这个init方法。

public abstract class HttpServletBean extends HttpServlet 
		implements EnvironmentCapable, EnvironmentAware {

	@Override
	public final void init() throws ServletException {
		...
		// Let subclasses do whatever initialization they like.
		initServletBean();
	}
}

果不其然,在HttpServletBean类中Override了init()方法,并且在最后又调用了initServletBean()方法,在该类中initServletBean()方法也是个空方法体的方法,仍然是留给子类去重写的,我们继续看是谁Override了这个initServletBean方法。

public abstract class FrameworkServlet extends HttpServletBean implements
		ApplicationContextAware {
	/**
	 * Overridden method of {@link HttpServletBean}, invoked after any bean properties
	 * have been set. Creates this servlet's WebApplicationContext.
	 */
	@Override
	protected final void initServletBean() throws ServletException {
		...
		try {
			// 这里初始化了MVC容器
			this.webApplicationContext = initWebApplicationContext();
			initFrameworkServlet();
		}
		...
	}

	protected WebApplicationContext initWebApplicationContext() {
		// 这里就是从当前的servletContext中获取Spring容器。
		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()) {
					if (cwac.getParent() == null) {
						// 将Spring容器设置为当前web容器的父容器
						cwac.setParent(rootContext);
					}
					configureAndRefreshWebApplicationContext(cwac);
				}
			}
		}
		...
		return wac;
	}

}
public abstract class WebApplicationContextUtils {
	/**
	* 从ServletContext 中获取key为org.springframework.web.context.WebApplicationContext.ROOT的对象
	*/
	public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
		return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
	}
}

从上面代码中我们可以看到,FrameworkServlet 类的initServletBean()方法又调用了initWebApplicationContext()方法,在initWebApplicationContext方法中,通过WebApplicationContextUtils.getWebApplicationContext(getServletContext());获取到了我们之前配置到servletContext中的Spring容器,并且把他设置为了我们当前MVC容器的父容器。

3.总结

综合上面两个部分的内容,我们可以了解到,其实SpringMvc的核心就是一个特殊的Servlet,在其初始化的时候,经过层层调用,最终在其父类FrameworkServlet的 initWebApplicationContext()方法中实将配置好的Spring容器取了出来作为SpringMvc容器(本文并没有讲解mvc容器的细节)的父容器。而这一操作对于Spring容器来说是无感知的,他并不知道我们把它作为了自己的父容器,但是mvc容器却能够知道父容器的存在,于是,便有了‘子容器可以访问父容器的bean,父容器不能访问子容器的bean’的这一说法。
经过了上面的这几个步骤,我们的web环境中,就已经搭建好了Spring与SpringMvc环境,并且SpringMvc已经接管了所有的url请求。至于SpringMvc是如何处理请求的,以及它的原理,我将会在以后更新在我的博客。
对于本文如果您发现我哪里与实际情况有所出入,欢迎在下面评论区指出,我会仔细斟酌并加以改正,谢谢。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值