Spring 之 DispatcherServlet 和 @EnableWebMvc 源码分析及一些问题

一、DispatcherServlet 类解析

直接进入 DispatcherServlet 类,核心源码如下:


	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;
		// ...
		try {
    
    
			ModelAndView mv = null;
			try {
    
    
				// 获取映射关系
				mappedHandler = getHandler(processedRequest);
				// 获取可以处理的方法
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
				// 调用处理方法生成视图
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
			}
			// 主要是视图解析、渲染过程
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		} 
	}

接下来是一段非常长的分析过程,对上述四个方法:getHandler()、getHandlerAdapter()、handle()、processDispatchResult() 进行分析。

1、getHandler() 方法分析

一个请求过来,第一个进入 getHandler() 方法,通过这个方法找到对应映射关系,核心源码如下:

	@Nullable
	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;
	}

tips:为什么要将 HandlerMethod 或者类本身再次包装 HandlerExecutionChain 呢,因为 SpringMVC 有很多的拦截器,所以就需要将目标方法、拦截器包装到 HandlerExecutionChain 上下文中。

这里的 handlerMappings 集合在哪里初始化的呢?先分析完整个流程再回头看。继续进入 mapping.getHandler(request) 内部逻辑,核心源码如下:

	@Override
	@Nullable
	public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    
    

		Object handler = getHandlerInternal(request);
		if (handler == null) {
    
    
			handler = getDefaultHandler();
		}
}

这里返回的 handler 不一定是都是 HandlerMethod 类型,有可能是类的本身,只有通过 @RequestMapping 注解方式的才会返回 HandlerMethod 类型,返回类本身的比如通过实现 ControllerAbstractControllerHttpRequestHandler 接口的类,因为方法就只有一个,找到类就能确定由哪个方法去处理当前请求,所以此时返回的 handler 就是类本身。所以这就是为什么这里是通过 Object 类型来接受返回 handler 对象。

查看 getHandlerInternal() 方法是一个钩子方法,源码如下:

	@Nullable
	protected abstract Object getHandlerInternal(HttpServletRequest request) throws Exception;

这里有两个比较明显的子类实现分别是:AbstractHandlerMethodMappingAbstractUrlHandlerMapping 看名字可以知道第一个就是针对 @RequestMapping 注解的,第二个针对实现了 ControllerAbstractControllerHttpRequestHandler 接口的。

先分析简单 AbstractUrlHandlerMapping 类,进入核心源码如下:

	@Override
	@Nullable
	protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
    
    
		String lookupPath = initLookupPath(request);
		Object handler = lookupHandler(lookupPath, request);
		// ...
		return handler;
	}

initLookupPath() 根据请求 request 传过来的数据生成一个映射路径,相当于 key。根据 key 找到对应 handler。

比如访问下面这个 HelloHttpRequestHandler 类,key = /hello,handler 值就是 HelloHttpRequestHandler 本身的类实例。

@Controller("/hello")
public class HelloHttpRequestHandler implements HttpRequestHandler {
    
    

	@Override
	public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    

		response.getWriter().write("hello HttpRequestHandler.....");
	}
}

最终 lookupPath = /hello,调用 lookupHandler() 方法,核心源码如下:

	protected Object lookupHandler(String lookupPath, HttpServletRequest request) throws Exception {
    
    
		Object handler = getDirectMatch(lookupPath, request);
		if (handler != null) {
    
    
			return handler;
		}
}

继续进入 getDirectMatch() 核心源码如下:

	@Nullable
	private Object getDirectMatch(String urlPath, HttpServletRequest request) throws Exception {
    
    
		Object handler = this.handlerMap.get(urlPath);
		if (handler != null) {
    
    
			// Bean name or resolved handler?
			if (handler instanceof String) {
    
    
				String handlerName = (String) handler;
				handler = obtainApplicationContext().getBean(handlerName);
			}
			validateHandler(handler, request);
			return buildPathExposingHandler(handler, urlPath, urlPath, null);
		}
		return null;
	}

从上面这段源码有明显可发现,最终是从 handlerMap 中取查 handler 值,urlPath = /hello,所以这里可以总结出: 实现 ControllerAbstractControllerHttpRequestHandler 接口类型的 Handler 都是类本身实例,映射关系在 handlerMap 集合中有保存。对于这个 handlerMap 啥时候初始化赋值的呢?,分析完整个流程在回头看。

然后再分析 AbstractHandlerMethodMapping 类,进入核心源码如下:

	@Nullable
	protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
    
    
		List<Match> matches = new ArrayList<>();
		
		List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
		
		if (directPathMatches != null) {
    
    
			addMatchingMappings(directPathMatches, matches, request);
		}
		if (matches.isEmpty()) {
    
    

			addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
		}
		if (!matches.isEmpty()) {
    
    
			/**
			 * 一般情况下一个 url 就只会映射到一个 RequestMappingInfo,但是也不能保证只有一个,当一个 url 匹配到多个 RequestMappingInfo
			 * 时,就要去校验哪个最合适
			 */
			Match bestMatch = matches.get(0);

			return bestMatch.getHandlerMethod();
		}
	}

上面这段源码主要观察这句 this.mappingRegistry.getRegistrations(),进入核心源码如下:

	class MappingRegistry {
    
    

		private final Map<T, MappingRegistration<T>> registry = new HashMap<>();

		public Map<T, MappingRegistration<T>> getRegistrations() {
    
    
			return this.registry;
		}
}

可以发现 @RequestMapping 注解的映射关系也是保存在一个 Map 类型的 registry 容器中,解析 @RequestMapping 注解被封装成 RequestMappingInfo 对象,作为请求 registry 容器中的 key。

registry 容器的 value 值就是 HandlerMethodMapping#MappingRegistration 类型实例,MappingRegistration 封装着 HandlerMethod 类型从 Handler,也就是 Controller 中对应的具体方法+@RequestMapping 信息被封装成了一个 HandlerMethod 对象。所以可以总结出对于 @RequestMapping 这种形式的映射关系最终也是保存在一个 Map 容器中。

这里 registry 容器中的值是什么初始化进去的呢?分析完整个流程再回头分析。

小总结

分析到这里,可以知道 getHandler() 方法最终会返回两个类型值,一个是 HandlerMethod(使用 @RequestMapping 注解的返回这个类型)、一个是类的本身(实现 Controller、HttpRequestHandler 接口返回类本身实例)。

侧面说明通过 request 请求找到了一个可以处理当前请求的方法,接下来就是看怎么调用该方法,那就要看 getHandlerAdapter() 方法怎么去适配调用,因为每种类型的 Controller 里的方法形式都不一样,所以需要一层适配器封装调用。

2、getHandlerAdapter() 方法分析

接着看第二个大步骤 getHandlerAdapter() 方法,核心源码如下:

	protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    
    
		/**
		 * 采用策略模式处理不同类型的 handler
		 */
		if (this.handlerAdapters != null) {
    
    
			for (HandlerAdapter adapter : this.handlerAdapters) {
    
    
				if (adapter.supports(handler)) {
    
    
					return adapter;
				}
			}
		}
	}

可以看到提供很多 HandlerAdapter 适配器,对于handlerAdapters 初始化在哪里? 后面分析,用来适配处理不同形式 Controller 的方法调用,因为每一种类型的 Controller 形式有点不一样,所以需要适配器去适配处理,说白了就是中间包装了一层调用而已,这里分析举个常见的:

  • HandlerFunctionAdapter:处理函数式编程类型的 Controller 方法,暂时没用过。

  • HttpRequestHandlerAdapter:处理实现 HttpRequestHandler 接口的 Controller 方法调用,该方法无需要返回值,所以无需返回值时可以定义 HttpRequestHandler 类型的 Controller。

  • SimpleControllerHandlerAdapter:处理实现 Controller、AbstractController 接口的 Controller 方法调用,该方法需要返回值。

  • AbstractHandlerMethodAdapter:处理 @RequestMapping 注解形式 Controller 方法调用,可以有返回值也可以不需要,更像是 Controller 和 HttpRequestHandler 类型组合。

上面那种写法就是很典型的动态策略模式,非常具有参考意义。闲话少说,接着往下分析。

回到上面那个请求 http://localhost:9292/hello 请求的 Controller 类型为 HttpRequestHandler 类型,所以会被 这个 HttpRequestHandlerAdapter 适配器进行处理。具体看他怎么处理的,核心源码如下:

public class HttpRequestHandlerAdapter implements HandlerAdapter {
    
    

	@Override
	public boolean supports(Object handler) {
    
    
		/**
		 * 支持实现了 HttpRequestHandler 接口的 Controller
		 */
		return (handler instanceof HttpRequestHandler);
	}

	@Override
	@Nullable
	public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
    
    

		((HttpRequestHandler) handler).handleRequest(request, response);
		return null;
	}
}

从上面源码可得知,handler 此时是 HelloHttpRequestHandler,类型就是 HttpRequestHandler,所以会调用 handle() 处理方法,这个方法做了两件事,回调 HelloHttpRequestHandler 类的 handleRequest() 方法,然后返回一个 null 值。就是做了个简单的调用过程,HttpRequestHandlerAdapter 适配器就走了这两件事情。

在举一个例子,例如下面这个 Controller,代码如下:

@Controller
public class JspController {
    
    

	@RequestMapping("/toJsp")
	public String toJsp() {
    
    
		System.out.println(">>>>>>toJsp..");
		return "abc";
	}
}

在请求 http://localhost:9292/toJsp 的时候,就会被 AbstractHandlerMethodAdapter 适配器处理,适配器里面肯定也是封装怎么去调用 JspController 中的 toJsp() 方法。进入核心源码如下:

public abstract class AbstractHandlerMethodAdapter extends WebContentGenerator implements HandlerAdapter, Ordered {
    
    

	private int order = Ordered.LOWEST_PRECEDENCE;

	@Override
	public final boolean supports(Object handler) {
    
    

		return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
	}


	@Override
	@Nullable
	public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
    
    

		return handleInternal(request, response, (HandlerMethod) handler);
	}
}

从上述源码中可得知,JspController 是 @RequestMapping 形式的构成的,所以 supports() 方法中 handler 就是 HandlerMethod 类型,刚好 AbstractHandlerMethodAdapter 适配器就可以处理,然后调用到 handle() 处理方法,进入 handleInternal() 核心源码如下:

	protected ModelAndView handleInternal(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    
    

		ModelAndView mav;
		// ...
		mav = invokeHandlerMethod(request, response, handlerMethod);
		// ...
		return mav;
}

继续进入 invokeHandlerMethod() 方法内部,核心源码如下:

	@Nullable
	protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    
    
		/** mvc_tag: 包装成自己方便使用的 DTO */
		ServletWebRequest webRequest = new ServletWebRequest(request, response);
		try {
    
    
			// 1、请求过来的字段和 Person 里面的字段一一绑定,就是这个绑定器要干的事情
			WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
			ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

			ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
			// 2、 这几个参数都在 pre_params 标记处准备好了 
			if (this.argumentResolvers != null) {
    
    
				invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
			}
			if (this.returnValueHandlers != null) {
    
    
				invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
			}
			invocableMethod.setDataBinderFactory(binderFactory);

			// 3、parameterNameDiscoverer 获取一个方法上参数名称,Spring 封装的一个工具类
			invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

			// 4、 mvc_tag: 在一次请求中共享Model 和 View 数据的临时容器 
			ModelAndViewContainer mavContainer = new ModelAndViewContainer();
			mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));

			/**
			 * 5、调用 @ModelAttribute 注解修饰的方法(这是个公共方法),把公共逻辑抽取到这个方法,然后用这个 @ModelAttribute 注解修饰该方法
			 * 这样再其他方法中就不用手动调用,Spring 会反射调用后把返回结果封装到 ModelAndViewContainer 中的 ModelMap 属性,
			 * 具体没啥太多可用价值
			 */
			modelFactory.initModel(webRequest, mavContainer, invocableMethod);
			mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

			

			// 6、 开始去调用目标方法,并且把临时容器穿进去了
			invocableMethod.invokeAndHandle(webRequest, mavContainer);

			// 7、从临时容器中直接抽取出需要的 ModelAndView
			return getModelAndView(mavContainer, modelFactory, webRequest);
		}
		finally {
    
    
			webRequest.requestCompleted();
		}
	}

从上面源码发现做了很多事情:

  • 获取参数绑定工厂 binderFactory

  • 将 handlerMethod 再次封装成 ServletInvocableHandlerMethod

  • 设置参数解析器

  • 设置返回值解析器

  • 设置数据绑定工厂(@RequestBody 注解有用)

  • new 了个 ModelAndViewContainer 容器,类似于 Context

上面这些获取到的东西在哪里初始化的?全部回过头来再看,继续往后分析,进入 invokeAndHandle() 方法核心源码如下:

	public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
    
    

		Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);

		if (returnValue == null) {
    
    
			if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
    
    
				disableContentCachingIfNecessary(webRequest);
				mavContainer.setRequestHandled(true);
				return;
			}
		}
		else if (StringUtils.hasText(getResponseStatusReason())) {
    
    
			mavContainer.setRequestHandled(true);
			return;
		}

		mavContainer.setRequestHandled(false);
		
		try {
    
    
			this.returnValueHandlers.handleReturnValue(
					returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
		}
	}

可以分成以下几步:

  • 调用 Controller 中具体方法
  • 设置视图渲染开关 requestHandled,false 表示需要视图渲染,true 表示不用视图渲染。
  • Controller 中方法返回值处理

先看到 invokeForRequest() 方法,核心源码如下:

	@Nullable
	public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
    
    

		Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
		if (logger.isTraceEnabled()) {
    
    
			logger.trace("Arguments: " + Arrays.toString(args));
		}
		return doInvoke(args);
	}
	@Nullable
	protected Object doInvoke(Object... args) throws Exception {
    
    
		Method method = getBridgedMethod();
		ReflectionUtils.makeAccessible(method);
		try {
    
    
			if (KotlinDetector.isSuspendingFunction(method)) {
    
    
				return CoroutinesUtils.invokeSuspendingFunction(method, getBean(), args);
			}
			return method.invoke(getBean(), args);
		}
	}

可以发现这个方法中主要去解析参数值,并且通过反射调用 Controller 中的具体方法。看看他是怎么进行对参数解析的,进入 getMethodArgumentValues() 方法,核心源码如下:

	protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
    
    

		MethodParameter[] parameters = getMethodParameters();
		
		Object[] args = new Object[parameters.length];
		for (int i = 0; i < parameters.length; i++) {
    
    
			MethodParameter parameter = parameters[i];
			
			if (this.resolvers.supportsParameter(parameter)){
    
    
				args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
			}
		}
		return args;
	}

从上述源码可以得知,对每一个参数进行处理,且每个类型参数都会有一种策略能够进行处理,不能处理直接抛异常,请求流程结束。这也是典型的动态策略模式。举个例子:

@Controller
public class JspController {
    
    

	@RequestMapping("/toJsp")
	public String toJsp(
		@RequestBody Person,
		@RequestParam String name,
		@PathVariable Integer id,
		@RequestHeader String contentType,
		String name,Map<String,String> map) {
    
    
		
		System.out.println(">>>>>>toJsp..");
		return "abc";
	}
}

先这种每个参数类型都不一样,必然需要一种策略去处理它,所以对于参数解析器也是有一大堆目前至少>17种。
这里解析完参数值,最终就可以通过反射去调用具体方法,执行自定义逻辑处理。

然后根据返回值设置开关 requestHandled,有返回值设置成 false,表示需要视图渲染,若无则设置成 true,表示没有视图渲染。

然后最后就是返回值的处理,进入 handleReturnValue() 方法核心源码如下:

	@Override
	public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
    
    

		HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
		if (handler == null) {
    
    
			throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
		}
		handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
	}

	@Nullable
	private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
    
    
		boolean isAsyncValue = isAsyncReturnValue(value, returnType);
		for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
    
    
			if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
    
    
				continue;
			}
			if (handler.supportsReturnType(returnType)) {
    
    
				return handler;
			}
		}
		return null;
	}

也是一样需要一堆的策略处理不同类型的返回值。如下图示:

在这里插入图片描述

返回值处理完成之后,回到最上一层调用,继续进入 return getModelAndView(mavContainer, modelFactory, webRequest); 核心源码如下:

3、创建 ModelAndView 对象


	@Nullable
	private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
			ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
    
    

		modelFactory.updateModel(webRequest, mavContainer);

		/**
		 * 根据 ModelAndViewContainer 容器中的 requestHandled 属性来判断需不需要渲染视图
		 * 如果是 @ResponseBody 明显是不需要的此时 requestHandled = true,立马就 return 结束了
		 */
		if (mavContainer.isRequestHandled()) {
    
    
			return null;
		}
		ModelMap model = mavContainer.getModel();
		ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
		if (!mavContainer.isViewReference()) {
    
    
			mav.setView((View) mavContainer.getView());
		}
		if (model instanceof RedirectAttributes) {
    
    
			Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
			HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
			if (request != null) {
    
    
				RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
			}
		}
		return mav;
	}

从上述这段源码可以得知,requestHandled=true 时,就直接返回 null,表示不需要视图渲染,也就不需要执行下面这段 ModeAndView 的数据准备。如果是 false,就需要视图渲染,那么这里就会执行下面这一段代码,准备好 Mode 数据,指定好逻辑视图,如果有闪存也会准备好闪存数据。最终返回 ModelAndView 出去。那么继续往下执行就是视图解析流程了。

4、视图解析和渲染

进入 processDispatchResult() 核心源码如下:

	private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
			@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
			@Nullable Exception exception) throws Exception {
    
    

		if (mv != null && !mv.wasCleared()) {
    
    
			render(mv, request, response);
		}
	}

从上面这段小小的代码就可以看出,mv 也就是 ModelAndView 对象,如果为 null,就不需要视图渲染。什么时候 mv 为 null?上面已经分析过了,是通过一个 requestHandled 开关控制的,requestHandled=true 时,就直接返回 null,表示不需要视图渲染,也就不需要执行下面这段 ModeAndView 的数据准备。如果是 false,就需要视图渲染。

进入 render() 核心源码如下:

	protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    
		// 获取逻辑视图名称
		String viewName = mv.getViewName();
		// 通过视图解析器生成视图 View
		View view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
		// 视图再去渲染
		view.render(mv.getModelInternal(), request, response);
	}

这里举几个常见的视图解析器:

  • InternalResourceViewResolver:生成 JSP页面的 View 视图,Spring 不判断是否一定存在 JSP 页面,Tomcat 自行处理
  • FreeMarkerViewResolver:生成 .ftl 模版页面的 View 视图,Spring 会判断是否存 .ftl 文件资源
  • ThymeleafViewResolver:生成 .html 模版页面的 View 视图,Spring 会判断是否存 .html 文件资源

视图 View 生成成功,就要展示效果是啥样了,那就需要调用 View 视图里面的 render() 渲染方法去展示效果,让你肉眼可见。

最终整个 SpringMVC 调用流程就算结束。剩下就是去分析哪些 get() 到得数据是在哪里初始化的。

一个请求过来,第一个进入 getHandler() 方法,通过这个方法找到对应映射关系,核心源码如下:

二、HandlerMapping 初始化

	@Nullable
	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;
	}

看下面这段代码 handlerMappings 的初始化值在哪里呢?可以看到一段源码如下:

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

tips: 在容器启动时,initStrategies() 会被 ContextRefreshListener监听类触发调用到,从而初始化 webmvc 需要的组件。

可以发现在这里初始化 HandlerMapping、HandlerAdapter、ViewResolver,接下来先看一下 initHandlerMappings() 方法,核心源码如下:

	private void initHandlerMappings(ApplicationContext context) {
    
    
		this.handlerMappings = null;

		if (this.detectAllHandlerMappings) {
    
    
			// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
			Map<String, HandlerMapping> matchingBeans =
					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
			if (!matchingBeans.isEmpty()) {
    
    
				this.handlerMappings = new ArrayList<>(matchingBeans.values());
				// We keep HandlerMappings in sorted order.
				AnnotationAwareOrderComparator.sort(this.handlerMappings);
			}
		}
		else {
    
    
			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.
			}
		}

		// 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.isTraceEnabled()) {
    
    
				logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
						"': using default strategies from DispatcherServlet.properties");
			}
		}

		for (HandlerMapping mapping : this.handlerMappings) {
    
    
			if (mapping.usesPathPatterns()) {
    
    
				this.parseRequestPath = true;
				break;
			}
		}
	}

从上述源码可以看出从两个地方初始化:@EnableWebMvcDispatcherServlet.properties

1、@EnableWebMvc 注解方式

beansOfTypeIncludingAncestors(HandlerMapping.class) 方法可以获取到容器中配置的 HandlerMapping 实现类,一般是在 @EnableWebMvc 注解会引入配置类,在配置类中通过 @Bean 注入 HandlerMapping 实现类。

@EnableWebMvc 注解源码如下:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class) 
public @interface EnableWebMvc {
    
    

}

是通过 Spring 注解 @Import 引入配置类 DelegatingWebMvcConfiguration,核心源码如下:

@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
    
    

}

核心源码在父类 WebMvcConfigurationSupport,先看看初始化 HandlerMapping 核心源码如下:

public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {
    
    

	@Bean
	@SuppressWarnings("deprecation")
	public RequestMappingHandlerMapping requestMappingHandlerMapping(
			@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
			@Qualifier("mvcConversionService") FormattingConversionService conversionService,
			@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
    
    
		// ...
		RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
		// ...
		return mapping;
	}
	
	@Bean
	public BeanNameUrlHandlerMapping beanNameHandlerMapping(
			@Qualifier("mvcConversionService") FormattingConversionService conversionService,
			@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
    
    
		// ...
		BeanNameUrlHandlerMapping mapping = new BeanNameUrlHandlerMapping();
		// ...
		return mapping;
	}
	
	@Bean
	@Nullable
	public HandlerMapping viewControllerHandlerMapping(
			@Qualifier("mvcConversionService") FormattingConversionService conversionService,
			@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
    
    

		ViewControllerRegistry registry = new ViewControllerRegistry(this.applicationContext);
		addViewControllers(registry);

		AbstractHandlerMapping handlerMapping = registry.buildHandlerMapping();
		// ...
		return handlerMapping;
	}
	
	@Bean
	public RouterFunctionMapping routerFunctionMapping(
			@Qualifier("mvcConversionService") FormattingConversionService conversionService,
			@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
    
    

		RouterFunctionMapping mapping = new RouterFunctionMapping();
		// ...
		return mapping;
	}

	@Bean
	@Nullable
	public HandlerMapping resourceHandlerMapping(
			@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
			@Qualifier("mvcConversionService") FormattingConversionService conversionService,
			@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
    
    
		// ...
		AbstractHandlerMapping handlerMapping = registry.getHandlerMapping();
		// ...
		return handlerMapping
	}
	
	@Bean
	@Nullable
	public HandlerMapping defaultServletHandlerMapping() {
    
    
		Assert.state(this.servletContext != null, "No ServletContext set");
		DefaultServletHandlerConfigurer configurer = new DefaultServletHandlerConfigurer(this.servletContext);
		configureDefaultServletHandling(configurer);
		return configurer.buildHandlerMapping();
	}
}

2、DispatcherServlet.properties 配置方式

如果通过上述配置类都没有找到 HandlerMapping 实现类,则还会有兜底方案,getDefaultStrategies() 方法中会去找 DispatcherServlet.properties 配置文件中配置的 HandlerMapping 实现类。如下图示:

在这里插入图片描述

DispatcherServlet.properties 配置文件如下:

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
	org.springframework.web.servlet.function.support.RouterFunctionMapping

BeanNameUrlHandlerMapping: 处理实现 Controller、AbstractController、HttpRequestHandler 接口的 Controller 映射关系,其实底层维护的是一个 Map。

RequestMappingHandlerMapping:处理的是 @RequestMapping 形式的 Controller 关系映射,底层都是维护一个 Map 容器。

SimpleUrlHandlerMapping:简单的 url 关系映射,你可以自定义某个 url 然后绑定一个处理器即可,做扩展用的多,比如静态资源访问,Controller 跳转等。

3、SimpleUrlHandlerMapping 简单介绍

这个类的源码如下:

public class SimpleUrlHandlerMapping extends AbstractUrlHandlerMapping {
    
    

	private final Map<String, Object> urlMap = new LinkedHashMap<>();

	public SimpleUrlHandlerMapping() {
    
    
	}

	public SimpleUrlHandlerMapping(Map<String, ?> urlMap) {
    
    
		setUrlMap(urlMap);
	}

	public SimpleUrlHandlerMapping(Map<String, ?> urlMap, int order) {
    
    
		setUrlMap(urlMap);
		setOrder(order);
	}
	
	public void setMappings(Properties mappings) {
    
    
		CollectionUtils.mergePropertiesIntoMap(mappings, this.urlMap);
	}

}

发现里面维护了一个 urlMap 容器,肯定是用来维护 urlPath 和 handler 的映射关系的。用这个类的时候只需要将 urlPath 和 handler 关系存放到 urlMap 容器中,但是要注意一点,这里只支持 handler 是类本身的 Controller,因为注册关系的时候,只能填一个值,而这个值不能精确到某个方法,所以 @RequestMapping 这种形式的不能通过这个进行绑定。这个只支持实现 Controller、AbstractController、HttpRequestHandler 类型的 Controller。

举个使用案例如下:

@Configuration
public class SimpleUrlConfig {
    
    

	// @Bean
	public SimpleUrlHandlerMapping simpleUrlHandlerMapping() {
    
    
		/* 
		Map<String, String> urlMap = new HashMap<>();
		urlMap.put("area/index", "helloSimpleController");
		SimpleUrlHandlerMapping simpleUrlHandlerMapping = new SimpleUrlHandlerMapping(urlMap);
		或
		*/
		
		SimpleUrlHandlerMapping simpleUrlHandlerMapping = new SimpleUrlHandlerMapping();
		Properties properties = new Properties();
		// key 就是访问路径,你自己随便定义,这里写的是 area/index
		// value 值就是 Controller 类本身在 Spring 容器中的 beanName
		properties.put("area/index","helloSimpleController");
		simpleUrlHandlerMapping.setMappings(properties);
		return simpleUrlHandlerMapping;
	}
}

// HelloSimpleController 在 Spring 中 beanName = helloSimpleController
@Component
public class HelloSimpleController extends AbstractController {
    
    

	@Override
	protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    
		response.getWriter().write("ControllerController execute..");
		return null;
	}
}

这样就可以将某个 url 访问路径绑定到某个 Controller 上,在浏览器上访问:http://localhost:8887/area/index
就可以访问到 Controller。

然后再看到这个类的另外俩个使用案例如下:

@Configuration
@EnableWebMvc
public class MyWebMvcConfigure implements WebMvcConfigurer {
    
    

	@Override
	public void addResourceHandlers(ResourceHandlerRegistry registry) {
    
    

		registry.addResourceHandler("/dist/**").addResourceLocations("classpath:/static/dist/");
		registry.addResourceHandler("/theme/**").addResourceLocations("classpath:/static/theme/");
		registry.addResourceHandler("/boot/*").addResourceLocations("classpath:/static/");
		registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
	}

	@Override
	public void addViewControllers(ViewControllerRegistry registry) {
    
    
		registry.addViewController("/gotoJsp").setViewName("abc2");
	}
}

自定义 webmvc 功能大家应该都用过,通过 addResourceHandlers() 指定访问静态资源,通过 addViewControllers() 方法指定跳转到哪个页面。这两个功能的底层都是 SimpleUrlHandlerMapping 类完成的。

先来分析 addResourceHandlers() 添加静态资源访问方法,debug 跟踪最终调用在以下核心源码中:

public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {
    
    

	@Bean
	@Nullable
	public HandlerMapping resourceHandlerMapping(
			@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
			@Qualifier("mvcConversionService") FormattingConversionService conversionService,
			@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
    
    

		ResourceHandlerRegistry registry = new ResourceHandlerRegistry(this.applicationContext,
				this.servletContext, contentNegotiationManager, pathConfig.getUrlPathHelper());
		addResourceHandlers(registry);

		AbstractHandlerMapping handlerMapping = registry.getHandlerMapping();
		return handlerMapping;
	}	
	
	@Nullable
	protected AbstractHandlerMapping getHandlerMapping() {
    
    
		if (this.registrations.isEmpty()) {
    
    
			return null;
		}
		Map<String, HttpRequestHandler> urlMap = new LinkedHashMap<>();
		for (ResourceHandlerRegistration registration : this.registrations) {
    
    
			ResourceHttpRequestHandler handler = getRequestHandler(registration);
			for (String pathPattern : registration.getPathPatterns()) {
    
    
				urlMap.put(pathPattern, handler);
			}
		}
		return new SimpleUrlHandlerMapping(urlMap, this.order);
	}
}

从上述源码可知把传入的静态资源访问路径都封装到了 SimpleUrlHandlerMapping 类中的 urlMap 容器中。这就是你为什么写一行这样的代码 registry.addResourceHandler(“/**”).addResourceLocations(“classpath:/static/”) 就可以在浏览器上访问到静态资源,因为 SimpleUrlHandlerMapping 类帮你建立好了访问路径和 Controller 映射关系。这里的 Controller 就是 ResourceHttpRequestHandler。每一条路径对应一个新的 ResourceHttpRequestHandler 类。

在来分析 addViewControllers() 方法,debug 追踪到调用处,核心源码如下:

public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {
    
    

	@Bean
	@Nullable
	public HandlerMapping viewControllerHandlerMapping(
			@Qualifier("mvcConversionService") FormattingConversionService conversionService,
			@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
    
    

		ViewControllerRegistry registry = new ViewControllerRegistry(this.applicationContext);
		addViewControllers(registry);

		AbstractHandlerMapping handlerMapping = registry.buildHandlerMapping();
		return handlerMapping;
	}
	
	@Nullable
	protected SimpleUrlHandlerMapping buildHandlerMapping() {
    
    
		if (this.registrations.isEmpty() && this.redirectRegistrations.isEmpty()) {
    
    
			return null;
		}

		Map<String, Object> urlMap = new LinkedHashMap<>();
		for (ViewControllerRegistration registration : this.registrations) {
    
    
			urlMap.put(registration.getUrlPath(), registration.getViewController());
		}
		for (RedirectViewControllerRegistration registration : this.redirectRegistrations) {
    
    
			urlMap.put(registration.getUrlPath(), registration.getViewController());
		}

		return new SimpleUrlHandlerMapping(urlMap, this.order);
	}
}

public class ViewControllerRegistration {
    
    

	private final ParameterizableViewController controller = new ParameterizableViewController();
	
	protected ParameterizableViewController getViewController() {
    
    
		return this.controller;
	}
}

从上述代码中可以知道 SimpleUrlHandlerMapping 类将 urlPath 和 ParameterizableViewController 建立映射关系,这样就知道为什么你在 addViewControllers() 方法中添加一行 registry.addViewController(“/gotoJsp”).setViewName(“abc2”); 这样的代码就可以在浏览器上面进行访问。访问路径是:http://localhost:8887/gotoJsp

其中 abc.jsp 在 webapp 下面需要存在,不然 404 错误。

在这里插入图片描述

最终访问效果如下:

在这里插入图片描述

所以 SimpleUrlHandlerMapping 这个类是非常重要的,可以用这个类来做一些动态映射关系绑定等等。

三、HandlerAdapter 初始化

HandlerAdapter 和 HandlerMapping 基本类似,核心源码如下:

	private void initHandlerAdapters(ApplicationContext context) {
    
    
		this.handlerAdapters = null;

		if (this.detectAllHandlerAdapters) {
    
    
			// Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.
			Map<String, HandlerAdapter> matchingBeans =
					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
			if (!matchingBeans.isEmpty()) {
    
    
				this.handlerAdapters = new ArrayList<>(matchingBeans.values());
				// We keep HandlerAdapters in sorted order.
				AnnotationAwareOrderComparator.sort(this.handlerAdapters);
			}
		}
		else {
    
    
			try {
    
    
				HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
				this.handlerAdapters = Collections.singletonList(ha);
			}
			catch (NoSuchBeanDefinitionException ex) {
    
    
				// Ignore, we'll add a default HandlerAdapter later.
			}
		}

		// Ensure we have at least some HandlerAdapters, by registering
		// default HandlerAdapters if no other adapters are found.
		if (this.handlerAdapters == null) {
    
    
			this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
			if (logger.isTraceEnabled()) {
    
    
				logger.trace("No HandlerAdapters declared for servlet '" + getServletName() +
						"': using default strategies from DispatcherServlet.properties");
			}
		}
	}

一样也是两种方式 @EnableWebMvcDispatcherServletl.properties

1、@EnableWebMvc 注解方式

public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {
    
    
		
	@Bean
	public RequestMappingHandlerAdapter createRequestMappingHandlerAdapter() {
    
    
		return new RequestMappingHandlerAdapter();
	}
	
	@Bean
	public HandlerFunctionAdapter handlerFunctionAdapter() {
    
    
		HandlerFunctionAdapter adapter = new HandlerFunctionAdapter();
		return adapter;
	}

	@Bean
	public HttpRequestHandlerAdapter httpRequestHandlerAdapter() {
    
    
		return new HttpRequestHandlerAdapter();
	}
	
	@Bean
	public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter() {
    
    
		return new SimpleControllerHandlerAdapter();
	}
}

2、DispatcherServlet.properties 配置文件

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
	org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
	org.springframework.web.servlet.function.support.HandlerFunctionAdapter

四、ViewResolver 初始化

视图解析器初始化,进入核心源码如下:

	private void initViewResolvers(ApplicationContext context) {
    
    
		this.viewResolvers = null;

		if (this.detectAllViewResolvers) {
    
    
			// Find all ViewResolvers in the ApplicationContext, including ancestor contexts.
			Map<String, ViewResolver> matchingBeans =
					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false);
			if (!matchingBeans.isEmpty()) {
    
    
				this.viewResolvers = new ArrayList<>(matchingBeans.values());
				// We keep ViewResolvers in sorted order.
				AnnotationAwareOrderComparator.sort(this.viewResolvers);
			}
		}
		else {
    
    
			try {
    
    
				ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class);
				this.viewResolvers = Collections.singletonList(vr);
			}
			catch (NoSuchBeanDefinitionException ex) {
    
    
				// Ignore, we'll add a default ViewResolver later.
			}
		}

		// Ensure we have at least one ViewResolver, by registering
		// a default ViewResolver if no other resolvers are found.
		if (this.viewResolvers == null) {
    
    
			this.viewResolvers = getDefaultStrategies(context, ViewResolver.class);
			if (logger.isTraceEnabled()) {
    
    
				logger.trace("No ViewResolvers declared for servlet '" + getServletName() +
						"': using default strategies from DispatcherServlet.properties");
			}
		}
	}

可以发现视图解析的初始化和 HandlerAdapter 和 HandlerMapping 基本一个套路,但是这里需要注意的是 @EnableWebMvc 注解在注入视图解析器时有一些细节。如下所示:

1、@EnableWebMvc 注入视图解析器

进入源码如下:

	@Bean
	public ViewResolver mvcViewResolver(
			@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {
    
    
		ViewResolverRegistry registry =
				new ViewResolverRegistry(contentNegotiationManager, this.applicationContext);
		configureViewResolvers(registry);

		if (registry.getViewResolvers().isEmpty() && this.applicationContext != null) {
    
    
			String[] names = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
					this.applicationContext, ViewResolver.class, true, false);
			// 这里 ==1 指的是上来容器中就有一个,这个是指定此方法 @Bean mvcViewResolver 这个 bean
			// 此时视图解析器就是 InternalResourceViewResolver
			if (names.length == 1) {
    
    
				registry.getViewResolvers().add(new InternalResourceViewResolver());
			}
		}

		ViewResolverComposite composite = new ViewResolverComposite();
		composite.setOrder(registry.getOrder());
		composite.setViewResolvers(registry.getViewResolvers());
		if (this.applicationContext != null) {
    
    
			composite.setApplicationContext(this.applicationContext);
		}
		if (this.servletContext != null) {
    
    
			composite.setServletContext(this.servletContext);
		}
		return composite;
	}

仔细细品上述源码,通过 beanNamesForTypeIncludingAncestors() 方法从 Spring 容器中找到 ViewResolver 接口的实现类,如果你没有配置任何 ViewResolver 的实现类,那么现在容器中只会存在一个那就是当前 @Bean 修饰的 mvcViewResolver() 方法,必然找到只有1个毋庸置疑,此时,SpringMVC 就会帮我们内置一个视图解析器 InternalResourceViewResolver,这个视图解析器时默认解析 JSP 资源文件的。记得配置 suffix、prefix,如果不配,那就在访问路径上把前后缀带上 http://localhost:9292/toJsp?cx=abc2.jsp 即可,如果配置了直接访问 http://localhost:9292/toJsp?cx=abc2 ,反正就是要拼成一条完整路劲。

@Controller
public class ToJspController {
    
    

    @RequestMapping("/toJsp")
    public ModelAndView toJsp(String cx) {
    
    
        ModelAndView view = new ModelAndView();
        System.out.println(">>>>>>toJsp...");
        System.out.println("hnhds");
        view.addObject("name", "gwm");
        view.setViewName(cx);
        return view;
    }
}

除了默认加载外,还可以定制化,但是你需要实现一个接口 WebMvcConfigurer,然后重写相应的方法,举个例子:

@Configuration
@EnableWebMvc
public class MyWebMvcConfigure implements WebMvcConfigurer {
    
    

	@Override
	public void addInterceptors(InterceptorRegistry registry) {
    
    
		registry.addInterceptor(new MyHandlerInterceptor()).addPathPatterns("/*");
	}

	@Override
	public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    
    
		/**
		 * 这里不加 Jackson 也是有默认值存在的,如果重写了的话,默认的 Converts 都不会加载,所以要非常注意这个细节
		 * 推荐重写 extendMessageConverters() 方法
		 */
		// converters.add(new MappingJackson2HttpMessageConverter());
	}

	@Override
	public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    
    
		MappingJackson2HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();

		ObjectMapper objectMapper = new ObjectMapper();
		SimpleDateFormat smt = new SimpleDateFormat("yyyy-MM-dd");
		objectMapper.setDateFormat(smt);

		jackson2HttpMessageConverter.setObjectMapper(objectMapper);
		converters.add(jackson2HttpMessageConverter);
	}

	@Override
	public void configureViewResolvers(ViewResolverRegistry registry) {
    
    
		// InternalResourceViewResolver resolver = new InternalResourceViewResolver();
		// resolver.setSuffix(".jsp");
		// resolver.setPrefix("/");
		// registry.viewResolver(resolver);

		// registry.enableContentNegotiation(new MappingJackson2JsonView());
		registry.jsp("/",".jsp");
		// registry.freeMarker();
	}

	@Override
	public void addResourceHandlers(ResourceHandlerRegistry registry) {
    
    

		registry.addResourceHandler("/dist/**").addResourceLocations("classpath:/static/dist/");
		registry.addResourceHandler("/theme/**").addResourceLocations("classpath:/static/theme/");
		registry.addResourceHandler("/boot/*").addResourceLocations("classpath:/static/");
		// registry.addResourceHandler("/boot/**").addResourceLocations("classpath:/static/");
		// registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
	}

	@Override
	public void addViewControllers(ViewControllerRegistry registry) {
    
    
		registry.addViewController("/gotoJsp").setViewName("abc");
	}
}

但是要注意在自己定制功能时需要先去看看源码是怎么实现的,因为确实有很多细节稍不留神就容易出错。比如 configureViewResolvers() 方法,当 JSP、FTL 文件同时存在时,你就需要把 FLT 文件放在 JSP 前面才可以正常加解析到资源。

对于这些定制化的功能是怎么被装载到的,这里有个设计模式:委托模式。就是某个功能实现交给另一个人去做,自己只是调用一下。

这里简单分析下自定义的 configureViewResolvers() 方法是怎么被调用到的。我们知道所有 webmvc 功能基本都在 WebMvcConfigurationSupport 类中写死,但是这样不好,开发者也想到这一点,所以为了能够做扩展或者自定义一些实现,在这个类的每个方法中都埋点,这些埋点就是可以让开发自定义一些功能。就比如在 WebMvcConfigurationSupport 类中注入视图解析器,源码如下:

	@Bean
	public ViewResolver mvcViewResolver(
			@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {
    
    
		ViewResolverRegistry registry =
				new ViewResolverRegistry(contentNegotiationManager, this.applicationContext);
		configureViewResolvers(registry);

		if (registry.getViewResolvers().isEmpty() && this.applicationContext != null) {
    
    
			String[] names = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
					this.applicationContext, ViewResolver.class, true, false);
			// 这里 ==1 指的是上来容器中就有一个,这个是指定此方法 @Bean mvcViewResolver 这个 bean
			// 此时视图解析器就是 InternalResourceViewResolver
			if (names.length == 1) {
    
    
				registry.getViewResolvers().add(new InternalResourceViewResolver());
			}
		}
}

发现这个configureViewResolvers() 方法是一个勾子方法,可以勾到子类实现。这就是一个埋点方便扩展和自定义实现。一般这种模版方法就会在子类中实现,所以看到子类 DelegatingWebMvcConfiguration 中是怎么重写 configureViewResolvers() 这个方法的,源码如下:

@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
    
    

	private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();

	@Override
	protected void configureViewResolvers(ViewResolverRegistry registry) {
    
    
		this.configurers.configureViewResolvers(registry);
	}
}

可以发现哈,这个子类啥事没干,而是调用 WebMvcConfigurerComposite 类方法,所以这里就是一个委托模式,这里要特别注意到哈,WebMvcConfigurerComposite 的实例可以直接 new 创建的,并没有交给 Spring 容器管理。接着进入 WebMvcConfigurerComposite 源码如下:

class WebMvcConfigurerComposite implements WebMvcConfigurer {
    
    

	private final List<WebMvcConfigurer> delegates = new ArrayList<>();

	public void addWebMvcConfigurers(List<WebMvcConfigurer> configurers) {
    
    
		if (!CollectionUtils.isEmpty(configurers)) {
    
    
			this.delegates.addAll(configurers);
		}
	}

	@Override
	public void configureViewResolvers(ViewResolverRegistry registry) {
    
    
		for (WebMvcConfigurer delegate : this.delegates) {
    
    
			delegate.configureViewResolvers(registry);
		}
	}
}

从上面源码可以发现,并没有做任何事情,而是又交给 WebMvcConfigurer 类,又是委托模式,所以我们要想定制化功能,就可以去实现 WebMvcConfigurer 接口,然后重写里面的方法。

举个例子这里 MyWebMvcConfigure 去实现 WebMvcConfigurer 接口,定制化 configureViewResolvers() 视图解析器,代码如下:

@Configuration
@EnableWebMvc
public class MyWebMvcConfigure implements WebMvcConfigurer {
    
    

	@Override
	public void configureViewResolvers(ViewResolverRegistry registry) {
    
    
	
		registry.freeMarker();
		registry.jsp("/",".jsp");
	}

注意这个类需要被 Spring 管理,所以要加上 @Component 或者 @Configuration 注解,在 WebMvcConfigurationSupport 子类 DelegatingWebMvcConfiguration 类中会去把这个类添加到 WebMvcConfigurerComposite 类中的 delegates 集合中,源码如下:

@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
    
    

	private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();

	@Autowired(required = false)
	public void setConfigurers(List<WebMvcConfigurer> configurers) {
    
    
		if (!CollectionUtils.isEmpty(configurers)) {
    
    
			this.configurers.addWebMvcConfigurers(configurers);
		}
	}
}

class WebMvcConfigurerComposite implements WebMvcConfigurer {
    
    

	private final List<WebMvcConfigurer> delegates = new ArrayList<>();

	public void addWebMvcConfigurers(List<WebMvcConfigurer> configurers) {
    
    
		if (!CollectionUtils.isEmpty(configurers)) {
    
    
			this.delegates.addAll(configurers);
		}
	}
	
	@Override
	public void configureViewResolvers(ViewResolverRegistry registry) {
    
    
		for (WebMvcConfigurer delegate : this.delegates) {
    
    
			delegate.configureViewResolvers(registry);
		}
	}
}

WebMvcConfigurerComposite 类中 configureViewResolvers() 方法遍历时就能回调到自定义实现类 MyWebMvcConfigure 中的 configureViewResolvers() 方法完成定制化。

最后还是要提醒一句在定制化时一定要先去看看源码怎么执行,否则容易出现细节错误。

2、DispatcherServlet.properties 配置文件

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

五、registry 或 handlerMap 映射关系建立

最后在回过头看看上面红色字体问题。在哪里初始化 handlerMap 的呢? 通过前面分析 handlerMap 是用来保存实现 Controller、AbstractController、HttpRequestHandler 接口类型的 Controller 映射关系。其中 SimpleHandlerMapping 类中有个 urlMap 容器,可以保存 urlPath 和 Controller 的映射关系,最终其实也是会把 urlMap 容器中的值填充到 handlerMap 容器。接下来看一下一个 Controller 例子,看一下它是在哪个地方将映射关系保存到 handlerMap 中,代码如下:

@org.springframework.stereotype.Controller("/controller")
public class HttpController implements Controller {
    
    
    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    
        return null;
    }
}

跟踪源码,最终发现调用处从 Spring 容器启动处触发,源码如下:

public abstract class ApplicationObjectSupport implements ApplicationContextAware {
    
    

	@Override
	public final void setApplicationContext(@Nullable ApplicationContext context) throws BeansException {
    
    
		if (context == null && !isContextRequired()) {
    
    
			initApplicationContext(context);
		}
	}
}

然后继续追踪 initApplicationContext() 方法,最终调用到 registerHandler() 方法,源码如下:

	protected void registerHandler(String[] urlPaths, String beanName) throws BeansException, IllegalStateException {
    
    
		Assert.notNull(urlPaths, "URL path array must not be null");
		for (String urlPath : urlPaths) {
    
    
			registerHandler(urlPath, beanName);
		}
	}
	
	protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
    
    
	
		this.handlerMap.put(urlPath, resolvedHandler);
	}

发现最终就是在 Spring 容器启动就把映射关系保存到 handlerMap 容器中。所以后面再 DispatcherServlet 类中就可以直接通过 get() 方法直接取值。

这里 registry 容器中的值是什么初始化进去的呢? 这个通过上面分析就是 @RequestMapping 形式的 Controller 映射关系会保存到这个 Map 容器中。具体看下这个容器中值是什么初始化的呢?

跟踪代码发现也是 Spring 容器时触发调用,核心源码如下:

public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {
    
    

	@Override
	public void afterPropertiesSet() {
    
    
		initHandlerMethods();
	}
}

发现借助 Spring 声明周期类 InitializingBean 来实现的,进入 initHandlerMethods() 代码,核心源码如下:

	protected void detectHandlerMethods(Object handler) {
    
    

		if (handlerType != null) {
    
    
			Class<?> userType = ClassUtils.getUserClass(handlerType);

			methods.forEach((method, mapping) -> {
    
    
				Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
				registerHandlerMethod(handler, invocableMethod, mapping);
			});
		}
	}

获取到 @RequestMapping 形式定义的 Controller 中所有的方法,每个方法都要建立映射关系。继续进入 registerHandlerMethod() 方法深处,核心源码如下:

	protected void registerHandlerMethod(Object handler, Method method, T mapping) {
    
    
		this.mappingRegistry.register(mapping, handler, method);
	}

继续进入 register() 方法 ,核心源码如下:

class MappingRegistry {
    
    

		private final Map<T, MappingRegistration<T>> registry = new HashMap<>();

		private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();

		private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();

		private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();

		public Map<T, HandlerMethod> getMappings() {
    
    
			return this.mappingLookup;
		}

		@Nullable
		public List<T> getMappingsByUrl(String urlPath) {
    
    
			return this.urlLookup.get(urlPath);
		}
		
		public void register(T mapping, Object handler, Method method) {
    
    
			// Assert that the handler method is not a suspending one.
			if (KotlinDetector.isKotlinType(method.getDeclaringClass())) {
    
    
				Class<?>[] parameterTypes = method.getParameterTypes();
				if ((parameterTypes.length > 0) && "kotlin.coroutines.Continuation".equals(parameterTypes[parameterTypes.length - 1].getName())) {
    
    
					throw new IllegalStateException("Unsupported suspending handler method detected: " + method);
				}
			}
			this.readWriteLock.writeLock().lock();
			try {
    
    
				HandlerMethod handlerMethod = createHandlerMethod(handler, method);
				validateMethodMapping(handlerMethod, mapping);
				this.mappingLookup.put(mapping, handlerMethod);

				List<String> directUrls = getDirectUrls(mapping);
				for (String url : directUrls) {
    
    
					this.urlLookup.add(url, mapping);
				}

				String name = null;
				if (getNamingStrategy() != null) {
    
    
					name = getNamingStrategy().getName(handlerMethod, mapping);
					addMappingName(name, handlerMethod);
				}

				CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
				if (corsConfig != null) {
    
    
					this.corsLookup.put(handlerMethod, corsConfig);
				}

				this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
			}
			finally {
    
    
				this.readWriteLock.writeLock().unlock();
			}
		}
}

这个 MappingRegistry#register() 主要就是将每个方法映射关系保存到 registry,registry 也就是一个 Map 容器。只是人家的 key 做了一个层包装,解析 @RequestMapping 属性封装成 RequestMappingInfo 对象。然后值就是@RequestMapping 修饰的方法被封装成 HandlerMethod 对象。

六、参数解析器、返回值解析器初始化

最后在看看调用过程中参数解析器、返回值解析器的值是在哪里初始化的?
追踪源码,最终在 RequestMappingHandlerAdapter 类中借助 InitializingBean 声明周期类实现,核心源码如下:

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
		implements BeanFactoryAware, InitializingBean {
    
    

	@Override
	public void afterPropertiesSet() {
    
    
		// Do this first, it may add ResponseBody advice beans
		initControllerAdviceCache();

		if (this.argumentResolvers == null) {
    
    
			List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
			this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
		}
		if (this.initBinderArgumentResolvers == null) {
    
    
			List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
			this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
		}
		if (this.returnValueHandlers == null) {
    
    
			List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
			this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
		}
	}
}

进入 getDefaultArgumentResolvers() 方法,核心源码如下:

	private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
    
    
		List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(30);

		// Annotation-based argument resolution
		resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
		resolvers.add(new RequestParamMapMethodArgumentResolver());
		resolvers.add(new PathVariableMethodArgumentResolver());
		resolvers.add(new PathVariableMapMethodArgumentResolver());
		resolvers.add(new MatrixVariableMethodArgumentResolver());
		resolvers.add(new MatrixVariableMapMethodArgumentResolver());
		resolvers.add(new ServletModelAttributeMethodProcessor(false));
		resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
		resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
		resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
		resolvers.add(new RequestHeaderMapMethodArgumentResolver());
		resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
		resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
		resolvers.add(new SessionAttributeMethodArgumentResolver());
		resolvers.add(new RequestAttributeMethodArgumentResolver());

		// Type-based argument resolution
		resolvers.add(new ServletRequestMethodArgumentResolver());
		resolvers.add(new ServletResponseMethodArgumentResolver());
		resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
		resolvers.add(new RedirectAttributesMethodArgumentResolver());
		resolvers.add(new ModelMethodProcessor());
		resolvers.add(new MapMethodProcessor());
		resolvers.add(new ErrorsMethodArgumentResolver());
		resolvers.add(new SessionStatusMethodArgumentResolver());
		resolvers.add(new UriComponentsBuilderMethodArgumentResolver());

		// Custom arguments
		if (getCustomArgumentResolvers() != null) {
    
    
			resolvers.addAll(getCustomArgumentResolvers());
		}

		// Catch-all
		resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
		resolvers.add(new ServletModelAttributeMethodProcessor(true));

		return resolvers;
	}

在这上面默认创建了30个参数解析器,最终赋值到全局变量 argumentResolvers 集合中,后面就可以使用。同理返回值也是这样干的,核心源码如下:

	private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
    
    
		List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>(20);

		// Single-purpose return value types
		handlers.add(new ModelAndViewMethodReturnValueHandler());
		handlers.add(new ModelMethodProcessor());
		handlers.add(new ViewMethodReturnValueHandler());
		handlers.add(new ResponseBodyEmitterReturnValueHandler(getMessageConverters(),
				this.reactiveAdapterRegistry, this.taskExecutor, this.contentNegotiationManager));
		handlers.add(new StreamingResponseBodyReturnValueHandler());
		handlers.add(new HttpEntityMethodProcessor(getMessageConverters(),
				this.contentNegotiationManager, this.requestResponseBodyAdvice));
		handlers.add(new HttpHeadersReturnValueHandler());
		handlers.add(new CallableMethodReturnValueHandler());
		handlers.add(new DeferredResultMethodReturnValueHandler());
		handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory));

		// Annotation-based return value types
		handlers.add(new ModelAttributeMethodProcessor(false));
		handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(),
				this.contentNegotiationManager, this.requestResponseBodyAdvice));

		// Multi-purpose return value types
		handlers.add(new ViewNameMethodReturnValueHandler());
		handlers.add(new MapMethodProcessor());

		// Custom return value types
		if (getCustomReturnValueHandlers() != null) {
    
    
			handlers.addAll(getCustomReturnValueHandlers());
		}

		// Catch-all
		if (!CollectionUtils.isEmpty(getModelAndViewResolvers())) {
    
    
			handlers.add(new ModelAndViewResolverMethodReturnValueHandler(getModelAndViewResolvers()));
		}
		else {
    
    
			handlers.add(new ModelAttributeMethodProcessor(true));
		}

		return handlers;
	}

然后赋值到全局变量 returnValueHandlers 集合中,后续可以使用。这里会发现有两个特殊的参数解析,RequestResponseBodyMethodProcessorRequestPartMethodArgumentResolver 这里只看第一个,需要借助 MessageConverter 消息转换器才能解析封装( 主要对 @RequestBody、@ResponseBody 参数进行解析转换,而且主要是 JSON 转换成实体、实体转换成 JSON)。

七、MessageConverter 消息转换器初始化

进入 getMessageConverters() 方法,源码如下:

	public List<HttpMessageConverter<?>> getMessageConverters() {
    
    
		return this.messageConverters;
	}

那么就需要去看下这个 messageConverters 变量是在哪里赋值的,跟踪源码如下:

	@Bean
	public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
			@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
			@Qualifier("mvcConversionService") FormattingConversionService conversionService,
			@Qualifier("mvcValidator") Validator validator) {
    
    

		RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
		adapter.setContentNegotiationManager(contentNegotiationManager);
		adapter.setMessageConverters(getMessageConverters());
		adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer(conversionService, validator));
		adapter.setCustomArgumentResolvers(getArgumentResolvers());
		adapter.setCustomReturnValueHandlers(getReturnValueHandlers());

		if (jackson2Present) {
    
    
			adapter.setRequestBodyAdvice(Collections.singletonList(new JsonViewRequestBodyAdvice()));
			adapter.setResponseBodyAdvice(Collections.singletonList(new JsonViewResponseBodyAdvice()));
		}

		AsyncSupportConfigurer configurer = new AsyncSupportConfigurer();
		configureAsyncSupport(configurer);
		if (configurer.getTaskExecutor() != null) {
    
    
			adapter.setTaskExecutor(configurer.getTaskExecutor());
		}
		if (configurer.getTimeout() != null) {
    
    
			adapter.setAsyncRequestTimeout(configurer.getTimeout());
		}
		adapter.setCallableInterceptors(configurer.getCallableInterceptors());
		adapter.setDeferredResultInterceptors(configurer.getDeferredResultInterceptors());

		return adapter;
	}

调用代码 adapter.setMessageConverters(getMessageConverters()); 进入 getMessageConverters() 核心源码:

	protected final List<HttpMessageConverter<?>> getMessageConverters() {
    
    
		if (this.messageConverters == null) {
    
    
			this.messageConverters = new ArrayList<>();
			configureMessageConverters(this.messageConverters);
			if (this.messageConverters.isEmpty()) {
    
    
				addDefaultHttpMessageConverters(this.messageConverters);
			}
			extendMessageConverters(this.messageConverters);
		}
		return this.messageConverters;
	}

如果我们自己没有配置 messageConverters ,那么就会有默认的 messageConverters,进入 addDefaultHttpMessageConverters() 方法,源码如下:


	static {
    
    
		ClassLoader classLoader = WebMvcConfigurationSupport.class.getClassLoader();
		romePresent = ClassUtils.isPresent("com.rometools.rome.feed.WireFeed", classLoader);
		jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder", classLoader);
		jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
				ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
		jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
		jackson2SmilePresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader);
		jackson2CborPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.cbor.CBORFactory", classLoader);
		gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
		jsonbPresent = ClassUtils.isPresent("javax.json.bind.Jsonb", classLoader);
	}


	protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
    
    
		messageConverters.add(new ByteArrayHttpMessageConverter());
		messageConverters.add(new StringHttpMessageConverter());
		messageConverters.add(new ResourceHttpMessageConverter());
		messageConverters.add(new ResourceRegionHttpMessageConverter());
		try {
    
    
			messageConverters.add(new SourceHttpMessageConverter<>());
		}
		catch (Throwable ex) {
    
    
			// Ignore when no TransformerFactory implementation is available...
		}
		messageConverters.add(new AllEncompassingFormHttpMessageConverter());

		if (romePresent) {
    
    
			messageConverters.add(new AtomFeedHttpMessageConverter());
			messageConverters.add(new RssChannelHttpMessageConverter());
		}

		if (jackson2XmlPresent) {
    
    
			Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.xml();
			if (this.applicationContext != null) {
    
    
				builder.applicationContext(this.applicationContext);
			}
			messageConverters.add(new MappingJackson2XmlHttpMessageConverter(builder.build()));
		}
		else if (jaxb2Present) {
    
    
			messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
		}

		if (jackson2Present) {
    
    
			Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json();
			if (this.applicationContext != null) {
    
    
				builder.applicationContext(this.applicationContext);
			}
			messageConverters.add(new MappingJackson2HttpMessageConverter(builder.build()));
		}
		else if (gsonPresent) {
    
    
			messageConverters.add(new GsonHttpMessageConverter());
		}
		else if (jsonbPresent) {
    
    
			messageConverters.add(new JsonbHttpMessageConverter());
		}

		if (jackson2SmilePresent) {
    
    
			Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.smile();
			if (this.applicationContext != null) {
    
    
				builder.applicationContext(this.applicationContext);
			}
			messageConverters.add(new MappingJackson2SmileHttpMessageConverter(builder.build()));
		}
		if (jackson2CborPresent) {
    
    
			Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.cbor();
			if (this.applicationContext != null) {
    
    
				builder.applicationContext(this.applicationContext);
			}
			messageConverters.add(new MappingJackson2CborHttpMessageConverter(builder.build()));
		}
	}

从上述源码中可以发现,只要你引入相应依赖的第三方 jar 包,那么就会相应自动添加相应的转换器。比如你引入
com.fasterxml.jackson.databind 相关包,就会有 MappingJackson2HttpMessageConverter 消息转换器。所以你在使用 @RequestBody、@ResponseBody 注解时先看看这些包是否存在,若没有可能就转换不了直接报错。

还有一点要注意:如果想定制化 MessageConverter ,推荐重写 extendMessageConverters() 方法,不要重写 configureMessageConverters() 方法,看源码一清二楚,如下:

	protected final List<HttpMessageConverter<?>> getMessageConverters() {
    
    
		if (this.messageConverters == null) {
    
    
			this.messageConverters = new ArrayList<>();
			configureMessageConverters(this.messageConverters);
			if (this.messageConverters.isEmpty()) {
    
    
				addDefaultHttpMessageConverters(this.messageConverters);
			}
			extendMessageConverters(this.messageConverters);
		}
		return this.messageConverters;
	}

猜你喜欢

转载自blog.csdn.net/qq_35971258/article/details/128921548
今日推荐