Spring MVC 第四篇 - DispatcherServlet 如何管理使用 HandlerMapping?

目录

前言

DispatcherServlet 如何寻找合适的 HandlerMapping?

DispatcherServlet 如何管理 HandlerMapping?

总结


前言

        我们前面文章聊了 RequestMappingHandlerMapping 是如何实例化,如果处理映射关系的,可以当一个请求到达 DispatcherServlet 之后,它是怎么知道要使用哪个 HandlerMapping 的呢?如果用哪个 HandlerMapping 都不知道的话,又怎么执行后面的流程呢,所以本文就来探讨 DispatcherServlet 是如何决定使用哪个 HandlerMapping 来处理请求的。当然还要讨论它是如何实例化保存管理的,如果连实例都没有又怎么谈使用哪一个呢?

DispatcherServlet 如何寻找合适的 HandlerMapping?

        在获取后端处理器的时候,需要首先找到 HandlerMapping, 然后借助 HandlerMapping 的能力来找到与请求匹配的处理器,所以可以从 DispatcherServlet 的 getHandler 方法入手

    protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		if (this.handlerMappings != null) {
			for (HandlerMapping mapping : this.handlerMappings) {
				HandlerExecutionChain handler = mapping.getHandler(request);
				if (handler != null) {
					return handler;
				}
			}
		}
		return null;
	}

    public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        // 调用各个 HandlerMapping 的 getHandlerInternal
		Object handler = getHandlerInternal(request);
		
	    //...省略后续跟本文无关的代码
	}

        看到这段代码其实就会发现,其实之前我们说的是不对的或者说是不严谨的,因为严格讲当一个请求到达 DispatcherServlet 的时候,并没有确切的寻找 HandlerMapping 这一步,真正的逻辑是,DispatcherServlet 会发出类似这样的命令:“你们这一堆 HandlerMapping 都给我出来,我不管这个请求是你们谁负责的,从头到尾一个个找,给我找到我想要的后端处理器“。那么本文的问题之一就结束了,就是 DispatcherServlet 并不会根据请求找到匹配的处理器映射器,而是项目里配置的 HandlerMapping 都有机会表现一把,只不过有的 HandlerMapping 得到了机会却没把握住而已。

DispatcherServlet 如何管理 HandlerMapping?

        从上面我们知道 DispatcherServlet 维护了一个 HandlerMapping 数组,可是它是什么时候对数组进行实例化的呢?它是利用的 Spring 的事件监听机制,如果对 Spring 的事件监听不熟悉的同学可以看下这个链接,我觉得他讲的还挺清楚的 Spring 事件监听机制, 总的来说就是需要发布一个事件,然后再有一个监听器来监听该事件,下面我们看下如何利用事件监听来初始化 DispatcherServlet 的变量信息。

        还记得之前我们讲过的关于 Spring 上下文最重要的一个方法吗?就是 refresh() 方法

public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// Prepare this context for refreshing.
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.
			prepareBeanFactory(beanFactory);

			try {
				// Allows post-processing of the bean factory in context subclasses.
				postProcessBeanFactory(beanFactory);

				// Invoke factory processors registered as beans in the context.
				invokeBeanFactoryPostProcessors(beanFactory);

				// Register bean processors that intercept bean creation.
				registerBeanPostProcessors(beanFactory);

				// Initialize message source for this context.
				initMessageSource();

				// Initialize event multicaster for this context.
				initApplicationEventMulticaster();

				// Initialize other special beans in specific context subclasses.
				onRefresh();

				// Check for listener beans and register them.
				registerListeners();

				// Instantiate all remaining (non-lazy-init) singletons.
				finishBeanFactoryInitialization(beanFactory);

				// 在这,在这,就在这,快往这里看呀~
				finishRefresh();
			}

			catch (BeansException ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}

				// Destroy already created singletons to avoid dangling resources.
				destroyBeans();

				// Reset 'active' flag.
				cancelRefresh(ex);

				// Propagate exception to caller.
				throw ex;
			}

			finally {
				// Reset common introspection caches in Spring's core, since we
				// might not ever need metadata for singleton beans anymore...
				resetCommonCaches();
			}
		}
	}

        上述代码可以看到,在实例化所有的 bean 之后,会调用 finishRefresh 方法,该方法会发布一个 ContextRefreshedEvent 类型的事件,finishRefresh 方法如下 

    protected void finishRefresh() {
		// Clear context-level resource caches (such as ASM metadata from scanning).
		clearResourceCaches();

		// Initialize lifecycle processor for this context.
		initLifecycleProcessor();

		// Propagate refresh to lifecycle processor first.
		getLifecycleProcessor().onRefresh();

		// 发布一个 ContextRefreshedEvent 类型的事件
		publishEvent(new ContextRefreshedEvent(this));

		// Participate in LiveBeansView MBean, if active.
		LiveBeansView.registerApplicationContext(this);
	}

        现在事件已经发布了,谁来监听呢?还记得 FrameworkServlet 吗?它是 DispatcherServlet d的父类,FrameworkServlet 还有一个内部类 ContextRefreshListener, 它就是负责监听 ContextRefreshedEvent 类型的事件。

    private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {

		@Override
		public void onApplicationEvent(ContextRefreshedEvent event) {
			FrameworkServlet.this.onApplicationEvent(event);
		}
	}

        那么这个监听器又是什么时候实例化的呢?在创建 Spring MVC 的上下文之后,在调用 refresh 方法之前,不知道大家还记不记得 configureAndRefreshWebApplicationContext 方法,在讲 Tomcat 如何加载 Spring 上下文的时候讲过。

    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());
			}
		}

		wac.setServletContext(getServletContext());
		wac.setServletConfig(getServletConfig());
		wac.setNamespace(getNamespace());
        // 这里,这里,看这里呀,这里添加了 ContextRefreshListener 类型的监听器
		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();
	}

        由此可以大概理清监听器的工作流程了,即实例化 Spring 上下文对象之后,refresh 之前先添加一个 ContextRefreshListener 类型的事件监听器,然后在 refresh 方法中发布一个事件,发布之后就会被对应的监听器监听到,执行监听器的方法,看下下面的流程图。

         由上图可以,最终会调用 DispatcherServlet 的 initStrategies 方法来完成 DispatcherServlet 的初始化,下面就看一下 initStrategies 方法 

    protected void initStrategies(ApplicationContext context) {
		initMultipartResolver(context);
		initLocaleResolver(context);
		initThemeResolver(context);
		initHandlerMappings(context);
		initHandlerAdapters(context);
		initHandlerExceptionResolvers(context);
		initRequestToViewNameTranslator(context);
		initViewResolvers(context);
		initFlashMapManager(context);
	}

        可以看到该初始化方法做了很多事情,因为本文探究的是 HandlerMapping 所以这里只看 initHandlerMappings 方法,其他的小伙伴可以自己去看一看。

    private void initHandlerMappings(ApplicationContext context) {
		this.handlerMappings = null;
        
        // 默认获取所有的 handlerMapping
		if (this.detectAllHandlerMappings) {
			// 获取包括父级容器中所有的 HandlerMapping
			Map<String, HandlerMapping> matchingBeans =
					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
			if (!matchingBeans.isEmpty()) {
                // 将获取到的 handlerMapping 保存到 DispatcherServlet 局部变量中
				this.handlerMappings = new ArrayList<>(matchingBeans.values());
				// We keep HandlerMappings in sorted order.
				AnnotationAwareOrderComparator.sort(this.handlerMappings);
			}
		}
		// ...删除一些特殊自定义配置的逻辑
	}

        分析完上面的代码,我们应该已经知道 DispatcherServlet 就是直接把 HandlerMapping 类型的 bean 从容器中拿了出来放入自己的变量中保存起来,关于 HandlerMapping 什么时候初始化和放进容器可以看下之前的文章。

总结

        本文其实算是跟之前的文章都打通了,看完本文之后应该就知道 Spring MVC 到底是什么东西和执行流程了,虽然有些东西没有讲,比如说处理器适配器和视图解析器等,但是相信看了这几篇文章,大家都已经有了自己分析代码的能力了。相关链接如下

关于 Servlet 你需要知道哪些?https://blog.csdn.net/paralysed/article/details/120591566?spm=1001.2014.3001.5501icon-default.png?t=L9C2https://blog.csdn.net/paralysed/article/details/120591566?spm=1001.2014.3001.5501Tomcat 什么时候加载 Spring 的容器?https://blog.csdn.net/paralysed/article/details/120687431?spm=1001.2014.3001.5501icon-default.png?t=L9C2https://blog.csdn.net/paralysed/article/details/120687431?spm=1001.2014.3001.5501Spring MVC 第一篇 - Controller 的方法是如何被调用的?https://blog.csdn.net/paralysed/article/details/120643252?spm=1001.2014.3001.5501icon-default.png?t=L9C2https://blog.csdn.net/paralysed/article/details/120643252?spm=1001.2014.3001.5501Spring MVC 第二篇 - 关于后端处理器 HandlerMethodhttps://blog.csdn.net/paralysed/article/details/120756934?spm=1001.2014.3001.5501icon-default.png?t=L9C2https://blog.csdn.net/paralysed/article/details/120756934?spm=1001.2014.3001.5501Spring MVC 第三篇 - 关于处理器映射器 RequestMappingHandlerMappinghttps://blog.csdn.net/paralysed/article/details/120794590?spm=1001.2014.3001.5501icon-default.png?t=L9C2https://blog.csdn.net/paralysed/article/details/120794590?spm=1001.2014.3001.5501

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值