Marco's Java【SpringMVC番外篇 之 映射器及适配器运行原理源码解析】

番外篇前言

大家有没有一个疑问,就是在第二节讲完SpringMVC配置之后做了一个Demo,演示从数据库中提取数据到页面上显示,我们当时的sprinmvc.xml配置中并没有配置什么映射器、适配器和视图解析器,但是依然可以正常的运行?

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
	
	<bean id="listUserController" class="com.marco.servlet.ListUserController" name="/user.action"></bean>
</beans>

在没有了解原理之前,我是觉得很神奇的,了解原理之后… 哦,就这样?
那么本节番外篇,我们就来一探究竟,为什么不用配置这些模块也能正常运行!
在这里插入图片描述

源码跟进

DispatcherServlet是整个SpringMVC的一个核心,那么此次我们就把DispatcherServlet当作切入点来寻根究底
这次我们要找的根源是为什么不用配置映射器、适配器和视图解析器这些模块也能正常运行,因此源码我不会全部分析,但是会重点分析上面的问题以及映射器、适配器的工作原理,其他部分在后面的章节我会专门拿出来讲。

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;
		boolean multipartRequestParsed = false;

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
		try {
			ModelAndView mv = null;
			Exception dispatchException = null;

			try {
				//processedRequest实质上就是原始的request对象
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);
				//通过getHandler获取处理器(控制器)
				// Determine handler for the current request.
				mappedHandler = getHandler(processedRequest);
				//当mappedHandler对象为空,或者处理器(控制器)为空
				if (mappedHandler == null || mappedHandler.getHandler() == null) {
					//返回没有找到处理器(控制器)的response对象
					noHandlerFound(processedRequest, response);
					return;
				}
				//通过getHandlerAdapter方法根据控制器获取匹配的适配器HandlerAdapter对象
				// Determine handler adapter for the current request.
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				// Process last-modified header, if supported by the handler.
				String method = request.getMethod();
				//判断当前的请求是否是get请求,下面只要碰到logger字样的都是log4j日志打印
				boolean isGet = "GET".equals(method);
				if (isGet || "HEAD".equals(method)) {
					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
					if (logger.isDebugEnabled()) {
						logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
					}
					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}

				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}
				//这里注意了,handle方法并不是控制器调用的,而是适配器调用的,因为只有适配上了接口,才能在Spring大环境中运行
				// Actually invoke the handler.
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
			
				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}

				applyDefaultViewName(processedRequest, mv);
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			} //下面都是一些异常处理,就不用看下去啦
			catch (Exception ex) {
				dispatchException = ex;
			}
			catch (Throwable err) {
				// As of 4.3, we're processing Errors thrown from handler methods as well,
				// making them available for @ExceptionHandler methods and other scenarios.
				dispatchException = new NestedServletException("Handler dispatch failed", err);
			}
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
		catch (Exception ex) {
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		}
		catch (Throwable err) {
			triggerAfterCompletion(processedRequest, response, mappedHandler,
					new NestedServletException("Handler processing failed", err));
		}
		finally {
			if (asyncManager.isConcurrentHandlingStarted()) {
				// Instead of postHandle and afterCompletion
				if (mappedHandler != null) {
					mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
				}
			}
			else {
				// Clean up any resources used by a multipart request.
				if (multipartRequestParsed) {
					cleanupMultipart(processedRequest);
				}
			}
		}
	}

那么根据上面的代码分析,我们可以得出一个结论,就是我们通过getHandler()方法获取到了mappedHandler对象,再通过mappedHandler对象拿到了控制器之后,会去调用getHandlerAdapter()查找相应的适配器。
那么我们先点开getHandler()方法,看看怎么获取到mappedHandler对象的

/**
 * Return the HandlerExecutionChain for this request.
 * <p>Tries all handler mappings in order.
 * @param request current HTTP request
 * @return the HandlerExecutionChain, or {@code null} if no handler could be found
 */
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	//遍历handlerMappings对象,也就是我们的注册的所有HandlerMapping信息(花名册)
	//然后构建出一个HandlerExecutionChain,它包含了handler和HandlerMapping本身的一些拦截器,如下 
	for (HandlerMapping hm : this.handlerMappings) {
		if (logger.isTraceEnabled()) {
			logger.trace(
					"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
		}
		//调用getHandler()方法通过request对象获取当前handler对象
		HandlerExecutionChain handler = hm.getHandler(request);
		if (handler != null) {
			return handler;
		}
	}
	return null;
}

通过上面的代码我们发现通过遍历handlerMappings对象,也就是我们的注册的所有HandlerMapping信息(花名册springmvc.xml) 然后构建出一个HandlerExecutionChain(处理器执行链),它包含了handler和HandlerMapping本身的一些拦截器,那么怎么获取我们想要的handler对象呢,我们接着点开getHandler()方法继续查看

/**
 * Look up a handler for the given request, falling back to the default
 * handler if no specific one is found.
 * @param request current HTTP request
 * @return the corresponding handler instance, or the default handler
 * @see #getHandlerInternal
 */
@Override
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	//getHandlerInternal(request)是个抽象方法,大家点开AbstractUrlHandlerMapping其实可以发现
	//它本质上就是解析我们的request中的url,通过url匹配上我们映射文件中的处理器,找到它的路径,细节上我们就不展开了
	Object handler = getHandlerInternal(request);//
	
	if (handler == null) {
		//如果handler为空,则调用默认的控制器
		handler = getDefaultHandler();
	}
	if (handler == null) {
		//如果默认的handler也为空,则返回空
		return null;
	}
	// Bean name or resolved handler?
	//如果handler不为空,查看这个handler是不是字符串?(回想BeanNameUrlHandlerMapping的用法)
	if (handler instanceof String) {
		String handlerName = (String) handler;
		//转型成字符串之后从ApplicationContext中通过handlerName获取handler 对象
		handler = getApplicationContext().getBean(handlerName);
	}
	//在getHandlerExecutionChain()方法中为handler加上拦截器包装成HandlerExecutionChain对象
	HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
	//CorsConfiguration 是具体封装跨域配置信息的pojo,以下都是关于跨域的设置
	if (CorsUtils.isCorsRequest(request)) {
		CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
		CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
		CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
		executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
	}
	return executionChain;
}

我么发现这里的getHandlerInternal(request)是个抽象方法,它是由具体的HandlerMapping来实现的,如果获取到的处理器对象为空,则获取默认配置的handler,如果handler为String类型,则表示这个则会去Spring容器里面去找这样名字的bean(是不是就是我们之前讲到的BeanNameUrlHandlerMapping的原理)。

我们再点开getHandlerAdapter()这个方法可以看到通过遍历handlerAdapters对象查看当前的处理器是哪个Apdapter的实例,如果查到了,就返回这个HandlerAdapter,否则抛出异常,返回
"No adapter for handler"

/** List of HandlerAdapters used by this servlet */
//handlerAdapters是DispatcherServlet的一个成员变量,是一个List集合
private List<HandlerAdapter> handlerAdapters;
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
		//循环遍历handlerAdapters
		for (HandlerAdapter ha : this.handlerAdapters) {
			if (logger.isTraceEnabled()) {
				logger.trace("Testing handler adapter [" + ha + "]");
			}
			//ha.supports(handler)方法主要是判断这个处理器是哪个Apdapter的实例
			if (ha.supports(handler)) {
				return ha;
			}
		}
		throw new ServletException("No adapter for handler [" + handler +
				"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
	}

那讲到这里,相信大家应该已经对文章最前面化的那张图的执行过程已经有所认知了,那么我们接着看我们之前提到的那个问题

“为什么不用配置映射器、适配器和视图解析器这些模块也能正常运行?”

答案就在DispatcherServlet 的initStrategies()方法上,我们先来看注释,注释的意思就是初始化策略对象(可以理解为为了成功执行servlet所需的对象) 供servlet使用,初始化init方法可以在子类中被重写,以便进一步初始化策略对象(不同的子类的方法执行过程不一样,因此初始化任务也不同)

/**
 * 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);//FlashMap管理
}

我这里简单的介绍一下上面的初始化方法

MultipartResolver:文件上传解析器,用于支持文件上传;
LocalResover:本地化解析器,因为Spring支持国际化,因此LocalResover解析客户端的Locale信息从而方便进行国际化;
ThemeResovler:主题解析器,通过它来实现一个页面多套风格,即常见的类似于软件皮肤效果;
HandlerMapping:处理器映射,如果映射成功返回一个HandlerExecutionChain对象(包含一个Handler处理器(页面控制器)对象、多个HandlerInterceptor拦截器)对象;如BeanNameUrlHandlerMapping将URL与Bean名字映射,映射成功的Bean就是此处的处理器;
HandlerAdapter:处理器适配器,HandlerAdapter将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器;如HttpRequestHandlerAdapter将对实现了HttpRequestHandler接口的Bean进行适配,并且调用处理器的handleRequest方法进行功能处理;
HandlerExceptionResolver:处理器异常解析器,可以将异常映射到相应的统一错误界面,从而显示用户友好的界面(而不是给用户看到具体的错误信息);
RequestToViewNameTranslator:当处理器没有返回逻辑视图名等相关信息时,自动将请求URL映射为逻辑视图名;
FlashMapManager:用于管理FlashMap的策略接口,FlashMap用于存储一个请求的输出,当进入另一个请求时作为该请求的输入,通常用于重定向场景;
ViewResolver:ViewResolver视图解析器将把逻辑视图名解析为具体的View,通过这种策略模式,很容易更换其他视图技术;如InternalResourceViewResolver将逻辑视图名映射为jsp视图。

终于看到两个我们熟悉的东西HandlerMapping、HandlerAdapter,那么这两个对象的初始化就是我们问题的关键所在!我们继续看下去

/**
* Initialize the HandlerMappings used by this class.
 * <p>If no HandlerMapping beans are defined in the BeanFactory for this namespace,
 * we default to BeanNameUrlHandlerMapping.
 */
private void initHandlerMappings(ApplicationContext context) {
	this.handlerMappings = null;
	//detectAllHandlerMappings默认为true,检测所有的HandlerMappings处理器映射对象,可以在web.xml中配置
	if (this.detectAllHandlerMappings) {
		//在ApplicationContext容器中查找所有HandlerMappings对象,包含它的父类上下文对象
		// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
		Map<String, HandlerMapping> matchingBeans =
				BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);	//如果匹配上了,则放入到handlerMappings这个容器中,并给handlerMappings中的对象进行排序
		if (!matchingBeans.isEmpty()) {
			this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
			// We keep HandlerMappings in sorted order.
			AnnotationAwareOrderComparator.sort(this.handlerMappings);
		}
	}
	else { //否则去查找找id="handlerMapping"且实现了HandlerMapping的bean
		try {
			HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
			this.handlerMappings = Collections.singletonList(hm);
		}
		catch (NoSuchBeanDefinitionException ex) {
			// Ignore, we'll add a default HandlerMapping later.
		}
	}
	//如果上述都没有找到,会使用默认的handlerMappings 
	// Ensure we have at least one HandlerMapping, by registering
	// a default HandlerMapping if no other mappings are found.
	if (this.handlerMappings == null) {
		this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
		if (logger.isDebugEnabled()) {
			logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
		}
	}
}

上面代码的意思就是在ApplicationContext容器中查找所有HandlerMappings对象,包含它的父类上下文对象,如果找到了,就存储在DispatcherServlet的handlerMappings容器中,如果detectAllHandlerMappings为false,则会直接去ApplicationContext容器中找id="handlerMapping"且实现了HandlerMapping的bean。若都查询失败,则会去加载默认的HandlerMapping。

this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);

那我们再点开这个方法看看里面的内容,该方法的注释中已经给到我们答案了,意思就是使用DispatcherServlet.properties文件中的类作为默认参数配置以确定类名,并通过ApplicationContext容器中的BeanFactory初始化这些类生成它们的对象
The default implementation uses the "DispatcherServlet.properties" file (in the same package as the DispatcherServlet class) to determine the class names

/**
* Create a List of default strategy objects for the given strategy interface.
* <p>The default implementation uses the "DispatcherServlet.properties" file (in the same
* package as the DispatcherServlet class) to determine the class names. It instantiates
* the strategy objects through the context's BeanFactory.
* @param context the current WebApplicationContext
* @param strategyInterface the strategy interface
* @return the List of corresponding strategy objects
*/
@SuppressWarnings("unchecked")
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
String key = strategyInterface.getName();//这里的key就是我们要找的组件的名字,比如说
//org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping
String value = defaultStrategies.getProperty(key);//通过key提取文件DispatcherServlet.properties中的内容
if (value != null) {
	//将获取到的字符串转为String[]数组
	String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
	List<T> strategies = new ArrayList<T>(classNames.length);
	for (String className : classNames) {
		try {
			//通过反射创建当前类的clazz
			Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
			Object strategy = createDefaultStrategy(context, clazz);
			//将所有的策略对象保存到strategies的List集合中
			strategies.add((T) strategy);
		}
		catch (ClassNotFoundException ex) {
			throw new BeanInitializationException(
					"Could not find DispatcherServlet's default strategy class [" + className +
							"] for interface [" + key + "]", ex);
		}
		catch (LinkageError err) {
			throw new BeanInitializationException(
					"Error loading DispatcherServlet's default strategy class [" + className +
							"] for interface [" + key + "]: problem with class file or dependent class", err);
		}
	}
	return strategies;
}
else {
	return new LinkedList<T>();
}
}

如果这还不够的话,接下来的这个参数DEFAULT_STRATEGIES_PATH的值DispatcherServlet.properties也足以印证我们的想法了,查看initHandlerAdapters()方法也能够得出一样的结论。

/**
* Name of the class path resource (relative to the DispatcherServlet class)
* that defines DispatcherServlet's default strategy names.
*/
private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";

因此,当你什么都没有配置时,默认会加载以上的配置,所以程序并不会报错,但是当你配置了其中任意一个Mapping或者Adapter而其对应的Adapter或者Mapping配错了,DispatcherServlet则会使用你配置的信息,如果不匹配,程序就会报错。

后语

好啦,今天的源码解析到此结束,所以说不管遇到什么问题都要透过现象看到问题的本质,等你弄明白了,就会恍然大悟,还是那句话,多看源码,多学习别人写的代码,如果能弄懂并运用到工作中,那将会是一笔巨大的财富!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值