文档章节

spring-webmvc 源码学习 —— DispatcherServlet 的初始化过程

j4love
 j4love
发布于 2017/06/03 17:02
字数 1494
阅读 28
收藏 0
点赞 0
评论 0

        很多文章中将 DispatcherServlet 称为 “前端控制器” , 我更愿意称它为 “请求分发器”(这也不算是文字游戏,只是觉得这样能更好的理解作者的设计思想,同时一个好的名字确实也很重要。)个人认为这种分发请求的方式也可以抽象成一个设计模式 ——“分发处理模式” , struts2 和 spring-mvc 都是用这种方式实现的,以一个 web 组件作为入口,进入这个入口后有另外一个天地,这时再更具某些因素决定选择该走哪条道路。(就像是我们进入了一个山洞,发现山洞后面通向了另一个世界,我们到达另一个世界的前题就是先进入这个山洞)。废话不多说,进入源码的世界,用代码表达思想,体会大师的思想,希望能与大师并肩前行。

  • DispatcherServlet 结构

                

  •    DispatcherServlet 的默认策略

            当 Servlet 容器启动的时候,DispatcherServlet 会初始化(load-on-stratup 大于 0 时),当 DispatcherServlet 被加载进内存时首先加载 static 的内容,也就是如下代码:
static {
   // Load default strategy implementations from properties file.
   // This is currently strictly internal and not meant to be customized
   // by application developers.
   try {
      // 指定的默认文件为 DispatcherServlet.properties,
      // 该文件位于 org.springframework.web.servlet 包下
      ClassPathResource resource = 
                    new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
      // 将 DispatcherServlet.properties 配置加载到 Properties 中
      defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
   }
   catch (IOException ex) {
      throw new IllegalStateException("Could not load 'DispatcherServlet.properties': " + ex.getMessage());
   }
}
  • 执行 Servlet 的 init 方法,做为 DispatcherServlet 的初始化入口 , 调用父类 org.springframework.web.servlet.HttpServletBean 的 init() 方法 :

  • public final void init() throws ServletException {
    		if (logger.isDebugEnabled()) {
    			logger.debug("Initializing servlet '" + getServletName() + "'");
    		}
    
    		// Set bean properties from init parameters.
    		try {
                // 获取 Servlet 的 init-param
    			PropertyValues pvs = 
                   new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
    			BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
    			ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
    			bw.registerCustomEditor(Resource.class, 
                     new ResourceEditor(resourceLoader, getEnvironment()));
    			initBeanWrapper(bw);
    			bw.setPropertyValues(pvs, true);
    		}
    		catch (BeansException ex) {
    			if (logger.isErrorEnabled()) {
    				logger.error("Failed to set bean properties on servlet '" +
                       getServletName() + "'", ex);
    			}
    			throw ex;
    		}
    
    		// Let subclasses do whatever initialization they like.
    		initServletBean();
    
    		if (logger.isDebugEnabled()) {
    			logger.debug("Servlet '" + getServletName() + "' configured successfully");
    		}
    	}

     

  • 初始化org.springframework.web.context.WebApplicationContext ,调用 org.springframework.web.servlet.FrameworkServlet 的 如下方法 :

    protected final void initServletBean() throws ServletException {
    		getServletContext().log("Initializing Spring FrameworkServlet '" +
                getServletName() + "'");
    		if (this.logger.isInfoEnabled()) {
    			this.logger.info("FrameworkServlet '" + 
                  getServletName() + "': initialization started");
    		}
    		long startTime = System.currentTimeMillis();
    
    		try {
                // 初始化 spring 容器
    			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;
    		}
    
    		if (this.logger.isInfoEnabled()) {
    			long elapsedTime = System.currentTimeMillis() - startTime;
    			this.logger.info("FrameworkServlet '" + getServletName() +  
                       "': initialization completed in " +
    					elapsedTime + " ms");
    		}
    	}
    protected WebApplicationContext initWebApplicationContext() {
    		WebApplicationContext rootContext =
    				WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    		WebApplicationContext wac = null;
    
    		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) {
    			// No context instance was injected at construction time -> see if one
    			// has been registered in the servlet context. If one exists, it is assumed
    			// that the parent context (if any) has already been set and that the
    			// user has performed any initialization such as setting the context id
    			wac = findWebApplicationContext();
    		}
    		if (wac == null) {
    			// No context instance is defined for this servlet -> create a local one
    			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.
    			onRefresh(wac);
    		}
    
    		if (this.publishContext) {
    			// Publish the context as a servlet context attribute.
    			String attrName = getServletContextAttributeName();
    			getServletContext().setAttribute(attrName, wac);
    			if (this.logger.isDebugEnabled()) {
    				this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
    						"' as ServletContext attribute with name [" + attrName + "]");
    			}
    		}
    
    		return wac;
    	}

    如果寻找了一遍发现没有 spring 容器的时候就会去创建一个:

    protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
    		Class<?> contextClass = getContextClass();
    		if (this.logger.isDebugEnabled()) {
    			this.logger.debug("Servlet with name '" + getServletName() +
    					"' will try to create custom WebApplicationContext context of class '" +
    					contextClass.getName() + "'" + ", using parent context [" + 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");
    		} 
            // 调用无参构造器创建一个 contextClass 类型的对象
    		ConfigurableWebApplicationContext wac =
    				(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
    
    		wac.setEnvironment(getEnvironment());
            // 设置父容器(有可能不存在父容器)
    		wac.setParent(parent);
            // 设置配置文件的位置
    		wac.setConfigLocation(getContextConfigLocation());
            // 对该容器进行配置,并执行刷新操作
    		configureAndRefreshWebApplicationContext(wac);
    
    		return wac;
    	}
    protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
    		if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
    			// The application context id is still set to its original default value
    			// -> assign a more useful id based on available information
    			if (this.contextId != null) {
    				wac.setId(this.contextId);
    			}
    			else {
    				// Generate default id...
    				wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
    						ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
    			}
    		}
            
            // 设置 ServletContext 以及其他 Servlet 组件
    		wac.setServletContext(getServletContext());
    		wac.setServletConfig(getServletConfig());
    		wac.setNamespace(getNamespace());
            
            // 给该容器添加容器监听器,监听容器的启动,刷新,停止等行为
    		wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
    
    		// 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(getServletContext(), getServletConfig());
    		}
    
    		postProcessWebApplicationContext(wac);
    		applyInitializers(wac);
    		wac.refresh();
    	}

    添加的监听器:

    private class ContextRefreshListener 
                 implements ApplicationListener<ContextRefreshedEvent> {
    
    	@Override
    	public void onApplicationEvent(ContextRefreshedEvent event) {
                
            // 在内容类中调用了 FrameworkServlet 的 onApplicationEvent 方法
    	    FrameworkServlet.this.onApplicationEvent(event);
    	}
    }
    	public void onApplicationEvent(ContextRefreshedEvent event) {
    		this.refreshEventReceived = true;
            
            // 调用 DispatcherServlet 的 onRefresh 方法
    		onRefresh(event.getApplicationContext());
    	}
        protected void onRefresh(ApplicationContext context) {
    		initStrategies(context);
    	}
    
    	/**
    	 * Initialize the strategy objects that this servlet uses.
    	 * <p>May be overridden in subclasses in order to initialize further strategy objects.
    	 */
    	protected void initStrategies(ApplicationContext context) {
    		initMultipartResolver(context);
    		initLocaleResolver(context);
    		initThemeResolver(context);
    		initHandlerMappings(context);
    		initHandlerAdapters(context);
    		initHandlerExceptionResolvers(context);
    		initRequestToViewNameTranslator(context);
    		initViewResolvers(context);
    		initFlashMapManager(context);
    	}

    接下来就是去设置 DispatcherServlet 的各个组件 : 

    Bean type Explanation

    HandlerMapping

    Maps incoming requests to handlers and a list of pre- and post-processors (handler interceptors) based on some criteria the details of which vary by HandlerMapping implementation. The most popular implementation supports annotated controllers but other implementations exists as well.

    HandlerAdapter

    Helps the DispatcherServlet to invoke a handler mapped to a request regardless of the handler is actually invoked. For example, invoking an annotated controller requires resolving various annotations. Thus the main purpose of a HandlerAdapter is to shield the DispatcherServlet from such details.

    HandlerExceptionResolver

    Maps exceptions to views also allowing for more complex exception handling code.

    ViewResolver

    Resolves logical String-based view names to actual View types.

    LocaleResolver & LocaleContextResolver

    Resolves the locale a client is using and possibly their time zone, in order to be able to offer internationalized views

    ThemeResolver

    Resolves themes your web application can use, for example, to offer personalized layouts

    MultipartResolver

    Parses multi-part requests for example to support processing file uploads from HTML forms.

    FlashMapManager

    Stores and retrieves the "input" and the "output" FlashMap that can be used to pass attributes from one request to another, usually across a redirect.

private void initLocaleResolver(ApplicationContext context) {
		try {
			this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
			if (logger.isDebugEnabled()) {
				logger.debug("Using LocaleResolver [" + this.localeResolver + "]");
			}
		}
		catch (NoSuchBeanDefinitionException ex) {
			// We need to use the default. 如果没有指定 spring-mvc 使用哪一个具体的组件,
            // 那么这时候就会选择默认的策略
			this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
			if (logger.isDebugEnabled()) {
				logger.debug("Unable to locate LocaleResolver with name '" + LOCALE_RESOLVER_BEAN_NAME +
						"': using default [" + this.localeResolver + "]");
			}
		}
	}

    在完成了上述步骤之后,WebApplicationContext , 以及 DispatcherServlet 就已经构建完成了。之后的 blog 会对 spring-webmvc 的执行过程,各个组件,以及特性做源码学习,分析。

© 著作权归作者所有

共有 人打赏支持
j4love
粉丝 47
博文 60
码字总数 62909
作品 0
东城
程序员
手把手教你搭建SpringMVC——最小化配置

为什么需要Spring MVC 最开始接触网页的时候,是纯的html/css页面,那个时候还是用Dreamweaver来绘制页面。 随着网站开发的深入,开始学习servlet开发,记得最痛苦的就是servlet返回网页的内...

青夜之衫 ⋅ 2017/12/05 ⋅ 0

SpringMVC源码剖析(三)- DispatcherServlet的初始化流程

在我们第一次学Servlet编程,学java web的时候,还没有那么多框架。我们开发一个简单的功能要做的事情很简单,就是继承HttpServlet,根据需要重写一下doGet,doPost方法,跳转到我们定义好的...

相见欢 ⋅ 2013/01/15 ⋅ 20

springmvc学习笔记(1)-框架原理和入门配置

springmvc学习笔记(1)-框架原理和入门配置 标签: springmvc [TOC] 本文主要介绍springmvc的框架原理,并通过一个入门程序展示环境搭建,配置以及部署调试。 springmvc是spring框架的一个模块...

brianway ⋅ 2016/03/08 ⋅ 0

网关 Spring-Cloud-Gateway 源码解析 —— 网关初始化

网关 Spring-Cloud-Gateway 源码解析 —— 网关初始化 Harries Blog™2017-12-135 阅读 SpringAppclasspathcatbeanAPIbuildbug 本文主要基于 Spring-Cloud-Gateway 2.0.X M4 摘要: 原创出处 ......

Harries Blog™ ⋅ 2017/12/13 ⋅ 0

SpringMVC+idea+maven搭建项目

创建Web项目 开发工具idea,工具的安装什么的就不说了,去百度。点击File——>Create project。我们需要先创建一个web项目。所有需要一个web模板。如下图所示,创建web工程模板。 设置maven的...

piz__ ⋅ 2016/12/08 ⋅ 0

Spring HTTP Invoker 学习小记

Spring HTTP Invoker是spring框架中的一个远程调用模型,执行基于HTTP的远程调用,也就是说,可以通过防火墙,并使用java的序列化机制在网络间传递对象。客户端可以很轻松的像调用本地对象一...

felixlv ⋅ 2013/04/05 ⋅ 5

Spring源码分析之WebMVC

作者: 一字马胡 转载标志 【2018-01-07】 更新日志 日期 更新内容 备注 2018-01-07 创建分析文档 Spring源码分析系列文章(四) 导入 Spring源码分析系列文章索引: Spring源码分析之Bean的...

一字马胡 ⋅ 01/07 ⋅ 0

SpringMVC深度探险(三) —— DispatcherServlet与初始化主线 博客分类:

SpringMVC深度探险(三) —— DispatcherServlet与初始化主线 博客分类: SpringMVC 本文是专栏文章(SpringMVC深度探险)系列的文章之一,博客地址为:http://downpour.iteye.com/blog/13...

xiguashare ⋅ 2013/12/09 ⋅ 0

Spring MVC源码学习一之初始化

一、简单提下使用入门(依赖于注解方式) 1、通过在web.xml中添加配置,引入spring mvc框架,类似于Struts2引入需要在web.xml中配置过滤器StrutsPrepareAndExecuteFilter一样,但是Spring MVC是...

silence88 ⋅ 2016/12/20 ⋅ 1

带着问题学 Spring MVC 源码: 一、概述

摘要: 原创出处:www.bysocket.com 泥瓦匠BYSocket 希望转载,保留摘要,谢谢! 简单就好,生活可以很德国 Q:什么是 Spring MVC ? ※ Spring MVC 是 Spring Web 的一个重要模块。Spring 支持...

泥沙砖瓦浆木匠 ⋅ 2016/10/23 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

6. Shell 函数 和 定向输出

Shell 常用函数 简洁:目前没怎么在Shell 脚本中使用过函数,哈哈,不过,以后可能会用。就像java8的函数式编程,以后获取会用吧,行吧,那咱们简单的看一下具体的使用 Shell函数格式 linux ...

AHUSKY ⋅ 5分钟前 ⋅ 0

MySQL 内核深度优化

MYSQL数据库适用场景广泛,相较于Oracle、DB2性价比更高,Web网站、日志系统、数据仓库等场景都有MYSQL用武之地,但是也存在对于事务性支持不太好(MySQL 5.5版本开始默认引擎才是InnoDB事务...

OSC_cnhwTY ⋅ 12分钟前 ⋅ 0

单片机软件定时器

之前写了一个软件定时器,发现不够优化,和友好,现在重写了 soft_timer.h #ifndef _SOFT_TIMER_H_#define _SOFT_TIMER_H_#include "sys.h"typedef void (*timer_callback_function)(vo...

猎人嘻嘻哈哈的 ⋅ 13分钟前 ⋅ 0

好的资料搜说引擎

鸠摩搜书 简介:鸠摩搜书是一个电子书搜索引擎。它汇集了多个网盘和电子书平台的资源,真所谓大而全。而且它还支持筛选txt,pdf,mobi,epub、azw3格式文件。还显示来自不同网站的资源。对了,...

乔三爷 ⋅ 22分钟前 ⋅ 0

Debian下安装PostgreSQL的表分区插件pg_pathman

先安装基础的编译环境 apt-get install build-essential libssl1.0-dev libkrb5-dev 将pg的bin目录加入环境变量,主要是要使用 pg_config export PATH=$PATH:/usr/lib/postgresql/10/bin 进......

玛雅牛 ⋅ 23分钟前 ⋅ 0

inno安装

#define MyAppName "HoldChipEngin" #define MyAppVersion "1.0" #define MyAppPublisher "Hold Chip, Inc." #define MyAppURL "http://www.holdchip.com/" #define MyAppExeName "HoldChipE......

backtrackx ⋅ 52分钟前 ⋅ 0

Linux(CentOS)下配置php运行环境及nginx解析php

【part1:搭建php环境】 1.选在自己需要安装的安装包版本,wget命令下载到服务器响应目录 http://php.net/releases/ 2.解压安装包 tar zxf php-x.x.x 3.cd到解压目录执行如下操作 cd ../php-...

硅谷课堂 ⋅ 58分钟前 ⋅ 0

Nginx服务架构初探(四):nginx服务器的rewrite功能

nginx服务器的rewrite功能 1.nginx后端服务器组的配置 1>upstream name {…} name是给服务器组限的组名 2>server address [parameters]; address为服务器地址 parame......

余温灬未存 ⋅ 今天 ⋅ 0

layer.prompt使文本框为空的情况下也能点击确定

最近一直在使用layui,但是用到弹出层layer.prompt时,如果文本框是空的话点击确定没有反应,不能向下执行。 但是我又需要空值,看看我原来的代码。 123456789 layer.prompt...

孟飞阳 ⋅ 今天 ⋅ 0

Linux普通文件压缩工具gzip、Bzip2、xz

第六章 文件压缩和打包 6.1 压缩打包介绍 Linux环境常见压缩文件类型: .zip,.gz,.bz2,.xz, .tar.gz,.tar.bz2,.tar.xz 压缩打包的目的 方便文件传输 节省磁盘空间 减少传输花费的时间 ...

弓正 ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部