文档章节

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

j4love
 j4love
发布于 2017/06/03 17:02
字数 1494
阅读 28
收藏 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
粉丝 49
博文 66
码字总数 62909
作品 0
东城
程序员
手把手教你搭建SpringMVC——最小化配置

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

青夜之衫
2017/12/05
0
0
Spring源码分析之WebMVC

作者: 一字马胡 转载标志 【2018-01-07】 更新日志 导入 Spring源码分析系列文章索引: Spring源码分析之Bean的解析 Spring源码分析之Bean的加载 Spring源码分析之AOP 本文是系列文章的第四...

疼丸李白
01/07
0
0
SpringMVC深度探险(三) —— DispatcherServlet与初始化主线 博客分类:

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

xiguashare
2013/12/09
0
0
SpringMVC源码剖析(三)- DispatcherServlet的初始化流程

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

相见欢
2013/01/15
0
21
SpringMVC源码解析(一)——初始化

前言 本系列文章顺延“Spring源码解析”,是在“父容器”创建完成后,对“子容器”(SpringMVC)创建,以及请求处理的解析。 源码解读 说起 SpringMVC,DispatcherServlet 应该是最熟悉的类之...

MarvelCode
06/26
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

流量劫持是如何产生的?

流量劫持,这种古老的攻击沉寂了一段时间后,最近又开始闹的沸沸扬扬。众多知名品牌的路由器相继爆出存在安全漏洞,引来国内媒体纷纷报道。只要用户没改默认密码,打开一个网页甚至帖子,路由...

谢思华
17分钟前
0
0
Hadoop Client无法使用maven下载源码

最近在学习hadoop,使用maven的时候想看一下源码的注释,结果IDEA一直提示无法下载 搞得我一度以为maven坏掉了。 但是通过搜索,发现在maven仓库里确实没有源码.... 而2.8.1以及之前的版本是...

Iceberg_XTY
19分钟前
0
0
为什么程序员千万不要重写代码?

你所做的事情,也许暂时看不到成果,但不要灰心或焦虑,你不是没有成长,而是在扎根。 图片来自网络 0 前言 程序员都有一颗工程师的心,所以当他们到一片新的场地想做的第一件事就是,将旧的...

Java小铺
20分钟前
0
0
VUE集成AdminLte

1. 安装需要到插件 npm i admin-lte -Snpm i jquery -Snpm i axios -Snpm i vue-router -S 2. 配置webpack.config.js 2.1 module.exports.module.rules修改字体loader: {test: /\.(p......

Pasenger
59分钟前
0
0
Spring Aop原理之切点表达式解析

在前面的文章(Spring AOP切点表达式详解)中,我们总结了Spring Aop切点表达式的用法,而在上文(Spring Aop原理之Advisor过滤)中我们讲到,切点表达式的解析主要是在PatternParser.parse...

爱宝贝丶
今天
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部