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

3 月,跳不动了?>>> hot3.png

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

  • DispatcherServlet 结构

                155214_93tg_2893466.png

  •    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 typeExplanation

    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 的执行过程,各个组件,以及特性做源码学习,分析。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值