• 从上帝视角认识SpringMVC预览


    前言

    SpringMVC提供了很多可拓展的组件,例如:参数解析器、拦截器、异常处理器等等。但是如果想要理解/找到这些组件工作的位置/时机,很多时候总是容易迷失在其层层调用的源码之中。因此想要写一些东西记录一下,愿景是从Spring的上下文启动到SpringMVC接受客户端请求的处理过程。由于涉及的内容比较多,在朋友的建议下,决定改成一个系列。
    在不断的了解更多SpringMVC的内部细节的过程中,发现要想理解DispatcherServlet完整的请求处理链路和逻辑,还需要了解其内部结构。因此决定调整系列文章的结构。

    DispatcherServlet

    相信大家都认识这东西,作用啥的也就不多说了。但请先暂时记住这一点: 他是javax.servlet.Servlet。

    WebApplicationContext

    什么是上下文

    还记得以前做阅读理解题目的时候吗?老师经常说的一句话是:联系上下文。又比如,你跟淘宝客服聊天,有时候我们总是先把商品发给对方,然后再说这个有没有其他颜色之类的。而这些推动事情继续发展的“背景知识”就可以叫做上下文。
    回到Spring,像环境配置、bean对象在哪里、事件发布等等,都属于上下文信息。即使,不是直接的进行参与,至少也要能够“联系”到对应的人来处理。例如,BeanFactory。如果从这个角度看的话,还能这样理解,他就是一个信息机构,有点像情报机构。你想要的东西他都能给你找来。实际上,我们在工作中,临时接入某个事情的时候,都需要进行交接/学习“上下文”。在了解事情的背景知识之后,我们才能进一步开展工作。
    在这里插入图片描述
    不过,由于Spring的上下文,是整个应用运行的上下文,因此Spring的上下文需要具备的背景知识比较庞杂,理解起来不容易。但我们可以从他的继承关系来理解他的能力。这里就不扩展了。

    父子上下文

    在这里插入图片描述
    SpringMVC把上下文分为Servlet容器的上下文,和根应用上下文。这点可能有不少人知道。但是,为什么要这样分呢?先看下两个上下文的职责分工。

    • Servlet上下文包含控制器、试图解析器,以及其他web相关的bean
    • Root上下文,则包含中间层服务,数据源等。

    可能有同学会问,为什么要多此一举呢?只使用一个上下文不行吗?还更容易理解。但是,有的情况下,我们的web应用可能不止提供http服务。例如:定时任务。定时任务跟Servlet有关系吗?
    从设计原则上说,这种划分可能也是基于最少知识原则,降低系统的耦合,也为其可维护性埋下伏笔。简而言之,web上下文只管web相关的bean,应用上下文管其他与web无关的bean。

    Root ApplicationContext怎么加载呢?

    DispatcherServlet通过自己的上下文来提供web服务。并且该web上下文还有个父亲。我们很容易想到在DispatcherServlet创建/初始化时,创建一个web上下文并扫描相关的webBean。但是Root ApplicationContext增加加载呢?怎么衔接起来呢?
    DispatcherServlet不就是一个servlet吗?通过ServletContext传递不就好了。但是什么时候创建呢?于是我们就需要到tomcat里面来了。
    tomcat启动的时候,也有自己的上下文:ServletContext。这个时候,可能有同学要晕车。。其实,你回过头再理解一下上下文,或许就好了。这就类似于我们现实生活中,做不同的事情,所需要了解的背景知识不一样。如果你用SpringCloud,还有个Bootstrap ApplicationContex,可以自己理解一下,它又是干啥的。
    当ServletContext初始化完成后,就会发布一个ServletContextEvent。这时只要我们实现ServletContextListener,即可收到通知。于是,我们便可借此机会初始化Root ApplicationContext,并放到ServletContext里面。
    在这里插入图片描述

    DispatcherServlet启动

    DispatcherServlet有一个Web上下文,并且由他管理着所有的web相关的bean。这意味着,在DispatcherServlet具备提供服务的能力之前,我们需要先将这个上下文准备好。而之前说的,只是Root Application。
    如果是你,你会怎么做呢?很容易想到就是在创建DispatcherServlet的时候对上下文进行初始化。只不过我们的DispatcherServlet本质上是Servlet,因此只需要跟随Servlet的生命周期函数就好。所以选择在javax.servlet.Servlet#init方法进行初始化。
    实际的实现在:org.springframework.web.servlet.FrameworkServlet#initServletBean
    要找到他的实际创建和刷新上下文,还是有点绕的,调用链路贴一下:
    在这里插入图片描述
    在串联上面的过程前,先把DispatcherServlet的配置也贴一下:
    在这里插入图片描述

    现在我们来串一下上面说的过程:

    1. 初始化Root ApplicationContext:通过ServletContextListener刷新Root上下文,并设置到ServletContext里面。
    2. 初始化Web ApplicationContext:在DispatcherServlet启动时,初始化web上下文。从ServletContext中获取Root上下文设置到web上下文的parent属性,绑定父子关系。
      注意,配置上面已经贴过了:Spring的监听器就是大家在web.xml里面配置的org.springframework.web.context.ContextLoaderListener。
    初始化Root ApplicationContext
    
    	/**
    	 * Initialize the root web application context.
    	 * 实例化root上下文.
    	 * 该方法的代码来自:org.springframework.web.context.ContextLoaderListener
    	 */
    	@Override
    	public void contextInitialized(ServletContextEvent event) {
    		initWebApplicationContext(event.getServletContext());
    	}
    
    	// 下方所有代码来自:org.springframework.web.context.ContextLoader
    	/**
    	 * 为servletContext实例化上下文
    	 */
    	public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    		// 省略...
    		try {
    			if (this.context == null) {
    				// 1. 创建上下文
    				this.context = createWebApplicationContext(servletContext);
    			}
    			if (this.context instanceof ConfigurableWebApplicationContext) {
    				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
    				if (!cwac.isActive()) {
    					// 省略...
    					// 2. 这个方法会获取我们在web.xml中配置的applicationContext.xml交给上下文对象,最终实现上下文的初始化
    					configureAndRefreshWebApplicationContext(cwac, servletContext);
    				}
    				// 3. 将root上下文设置到servlet上下文中
    				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;
    		}
    	/**
    	 * 1. 推断root上下文
    	 * 2. 通过反射构建root上下文
    	 */
    	protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
    		// 推断上下文Class
    		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);
    	}
    	
    	/**
    	 * 推断root上下文
    	 */
    	protected Class<?> determineContextClass(ServletContext servletContext) {
    		// 从servletContext获取contextClass
    		String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
    		if (contextClassName != null) {
    			// 省略...
    		}
    		else {
    			// 没有配置contextClass,使用默认的配置:来自ClassLoader.properties
    			// 该配置里面为:XmlWebApplicationContext
    			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);
    			}
    		}
    	}
    	// 好了,到这里我们看到了怎么找到contextClass,以及怎么实例化的。
    	// 但是还没看到使用我们在web.xml里面配置applicationContext.xml
    	// 让我们再次回到initWebApplicationContext(ServletContext servletContext)方法
    	// 他调用了我在上面埋下伏笔的备注:获取applicationContext.xml
    	
    	protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
    		// 省略...
    		wac.setServletContext(sc);
    		// 获取contextConfigLocation,这就是我们配置在web.xml中的applicationContext.xml了
    		String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
    		if (configLocationParam != null) {
    			wac.setConfigLocation(configLocationParam);
    		}
    		// 省略...
    		// 刷新root上下文
    		wac.refresh();
    	}
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    初始化Web ApplicationContext

    这部分就是DispatcherServlet的初始化过程了,跟着javax.servlet.Servlet#init方法往下看

    // 首先是org.springframework.web.servlet.HttpServletBean#init
    	@Override
    	public final void init() throws ServletException {
    		// 省略...
    		initServletBean();
    	}
    // 接着是org.springframework.web.servlet.FrameworkServlet#initServletBean
    	@Override
    	protected final void initServletBean() throws ServletException {
    	 	// 省略...
    		this.webApplicationContext = initWebApplicationContext();
    	 	// 省略...
    	}
    	
    	protected WebApplicationContext initWebApplicationContext() {
    		// 1. 从ServletContext中,把root上下文拿出来
    		WebApplicationContext rootContext =
    				WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    		WebApplicationContext wac = null;
    
    		if (this.webApplicationContext != null) {
    			// 如果直接设置了在DispatcerServlet的构造器中传了上下文,进行配置和刷新
    			// 这里省略...,不关注这种情况,因为通常情况下,我们并不会直接设置他的上下文
    		}
    		if (wac == null) {
    			// 并没有通过DispatcherServlet的构造器设置上下文,则假设在servletContext中已经初始化了一个web上下文,在这里拿出来。
    			// 很显然这里也是没有web上下文的
    			wac = findWebApplicationContext();
    		}
    		if (wac == null) {
    			// 创建默认的web上下文
    			wac = createWebApplicationContext(rootContext);
    		}
    
    		if (!this.refreshEventReceived) {
    			// 上下文已经刷新了,则初始化web相关组件:九大组件
    			synchronized (this.onRefreshMonitor) {
    				onRefresh(wac);
    			}
    		}
    		// 省略...
    		return wac;
    	}
    	// 创建web上下文
    	protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
    		// 1. 获取contextClass,这里默认是XmlWebApplicationContext
    		Class<?> contextClass = getContextClass();
    		// 2. 通过反射实例化web上下文
    		ConfigurableWebApplicationContext wac =
    				(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
    
    		wac.setEnvironment(getEnvironment());
    		// 3. 设置root上下文,到此。我们把root上下文跟web上下文绑定了父子关系
    		wac.setParent(parent);
    		// 4. 从web.xml拿到DipatcherServlet的ContextConfigLocation
    		// 并设置到上下文中
    		String configLocation = getContextConfigLocation();
    		if (configLocation != null) {
    			wac.setConfigLocation(configLocation);
    		}
    		// 5. 配置并刷新上下文
    		configureAndRefreshWebApplicationContext(wac);
    
    		return wac;
    	}
    	
    	// 配置并刷新上下文
    	protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
    		if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
    			// 设置上下文ID
    			// 省略
    		}
    		
    		wac.setServletContext(getServletContext());
    		wac.setServletConfig(getServletConfig());
    		wac.setNamespace(getNamespace());
    		wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
    		
    		ConfigurableEnvironment env = wac.getEnvironment();
    		if (env instanceof ConfigurableWebEnvironment) {
    			((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
    		}
    		// 提供扩展方法,允许子类在上下文实例化后进行配置
    		postProcessWebApplicationContext(wac);
    		// 执行ApplicationContextInitializer
    		applyInitializers(wac);
    		// 刷新上下文
    		wac.refresh();
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89

    到这里DispatcherServlet的web上下文就已经准备就绪了。

    小结

    DispatcherServlet本质上还是javax.servlet.Servlet,他的web上下文的启动依赖于Servlet的加载机制(Servlet#init方法)。同时,为了实例化和刷新root上下文,利用了tomcat的监听器机制。再通过ServletContext作为桥梁,将web上下文和root上下文绑定父子关系。

    后续

    了解SpringMVC的Web ApplicationContext之后,在讨论DispatcherServlet之前,需要先弄清楚DispatcherServlet的内部结构,也就是:九大组件。接下来,会有好几篇文章专门讲这个。

    注意:

    1. webmvc的版本为:5.3.22
    2. 为了简化阅读,源码中省略了一些无关代码,便于各位客官阅读。

    下一篇:
    探索SpringMVC-九大组件

  • 相关阅读:
    【哈士奇赠书活动 - 41期】- 〖产品设计软技能:创业公司篇〗
    部署LVS-DR集群+keepalived(主-备)
    堆排序详解
    利用dom4j组装xml
    11月22日星期三今日早报简报微语报早读
    node.js:《接口实现文件的上传和下载》
    Maven的安装与配置(详细版)
    线程池源码解析 2.工作原理与内部结构
    警惕U盘、FTP等传统文件摆渡方式的7大弊端
    【计算机网络三】数据链路层
  • 原文地址:https://blog.csdn.net/Evan_L/article/details/128061149