Spring's DispatcherServlet and @EnableWebMvc source code analysis and some problems

1. DispatcherServlet class analysis

Enter the DispatcherServlet class directly. The core source code is as follows:


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

Next is a very long analysis process, analyzing the above four methods: getHandler (), getHandlerAdapter (), handle (), processDispatchResult ().

1. getHandler() method analysis

When a request comes in, the first one enters the getHandler () method, and the corresponding mapping relationship is found through this method. The core source code is as follows:

	@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: Why should HandlerMethod or the class itself wrap HandlerExecutionChain again? Because SpringMVC has many interceptors, it is necessary to wrap the target method and interceptor into the HandlerExecutionChain context.

Where is the handlerMappings collection here initialized? Analyze the entire process first and then look back. Continue to entermapping.getHandler(request)Internal logic, core source code is as follows:

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

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

The handlers returned here are not necessarily all HandlerMethod types. They may be the class itself . Only those that are annotated with @RequestMapping will return the HandlerMethod type. Returns the class itself, such as classes that implement the Controller , AbstractController , and HttpRequestHandler interfaces, because the methods There is only one. If you find the class, you can determine which method will handle the current request, so the handler returned at this time is the class itself. So that's why the return handler object is accepted through the Object type.

Check that the getHandlerInternal() method is a hook method. The source code is as follows:

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

There are two obvious subclass implementations here: AbstractHandlerMethodMapping and AbstractUrlHandlerMapping. Looking at the names, you can tell that the first one is for the @RequestMapping annotation, and the second one is for the implementation of the Controller , AbstractController , and HttpRequestHandler interfaces.

First analyze the simple AbstractUrlHandlerMapping class and enter the core source code as follows:

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

initLookupPath () generates a mapping path based on the data passed by the request, which is equivalent to the key. Find the corresponding handler according to the key.

For example, when accessing the HelloHttpRequestHandler class below , key = /hello, the handler value is the class instance of HelloHttpRequestHandler itself.

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

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

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

Finally lookupPath = /hello, call the lookupHandler() method, the core source code is as follows:

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

Continue to getDirectMatch(). The core source code is as follows:

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

From the above source code, it is obvious that the handler value is finally retrieved from handlerMap , urlPath = /hello, so it can be summarized here: Handlers that implement the Controller , AbstractController , and HttpRequestHandler interface types are all instances of the class itself, and the mapping relationship is It is saved in the handlerMap collection. When is the value assigned to this handlerMap initialized? , analyze the entire process and look back.

Then analyze the AbstractHandlerMethodMapping class and enter the core source code as follows:

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

The above source code mainly observes this sentencethis.mappingRegistry.getRegistrations(), enter the core source code as follows:

	class MappingRegistry {
    
    

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

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

It can be found that the mapping relationship of the @RequestMapping annotation is also stored in a Map type registry container. The @RequestMapping annotation is encapsulated into a RequestMappingInfo object as the key in the request registry container.

The value of the registry container is the HandlerMethodMapping#MappingRegistration type instance. The MappingRegistration encapsulates the HandlerMethod type from the Handler, that is, the corresponding specific method +@ in the Controller. The RequestMapping information is encapsulated into a HandlerMethod object. So it can be concluded that the mapping relationship in the form of @RequestMapping is ultimately stored in a Map container.

What is the value in the registry container initialized here? Analyze the entire process and then go back and analyze it.

Summary

After analyzing this, we can know that the getHandler () method will eventually return two type values, one is HandlerMethod (use @RequestMapping annotation to return this type), and the other is the class itself (implementing the Controller and HttpRequestHandler interfaces returns the class itself instance ).

Side note: A method that can handle the current request has been found through the request request. The next step is to see how to call the method. Then it depends on how the getHandlerAdapter () method adapts the call, because the method forms in each type of Controller are different. The same, so a layer of adapter is needed to encapsulate the call.

2. getHandlerAdapter() method analysis

Next, look at the second big step, the getHandlerAdapter () method. The core source code is as follows:

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

It can be seen that there are many HandlerAdapter adapters provided. Where is the initialization of handlerAdapters? Later analysis is used to adapt and process the method calls of different forms of Controller. Because the form of each type of Controller is a little different, an adapter is needed to adapt the processing. To put it bluntly, It just wraps a layer of calls in the middle. Here is a common example:

  • HandlerFunctionAdapter : Controller method that handles functional programming types, not used yet.

  • HttpRequestHandlerAdapter : Processes the call of the Controller method that implements the HttpRequestHandler interface. This method does not need to return a value, so you can define a Controller of the HttpRequestHandler type when there is no need to return a value.

  • SimpleControllerHandlerAdapter : handles the Controller method call that implements the Controller and AbstractController interfaces. This method needs to return a value.

  • AbstractHandlerMethodAdapter : handles @RequestMapping annotation form Controller method calls, which may or may not have a return value. It is more like a combination of Controller and HttpRequestHandler types.

The above writing method is very typical 动态策略模式and has great reference significance. Without further ado, let’s proceed with the analysis.

Back to the request abovehttp://localhost:9292/helloThe requested Controller type is HttpRequestHandler type, so it will be processed by this HttpRequestHandlerAdapter adapter. It depends on how he handles it. The core source code is as follows:

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

From the source code above, we can know that the handler is HelloHttpRequestHandler at this time, and the type is HttpRequestHandler, so the handle() processing method will be called. This method does two things. It calls back the handleRequest() method of the HelloHttpRequestHandler class, and then returns a null value. Just by making a simple calling process, the HttpRequestHandlerAdapter adapter does these two things.

Let's take an example, such as the following Controller, the code is as follows:

@Controller
public class JspController {
    
    

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

in requesthttp://localhost:9292/toJsp, it will be processed by the AbstractHandlerMethodAdapter adapter. The adapter must also encapsulate how to call the toJsp() method in JspController. Enter the core source code as follows:

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

From the above source code, we can know that JspController is composed of @RequestMapping form, so the handler in the supports() method is the HandlerMethod type, which can be processed by the AbstractHandlerMethodAdapter adapter, and then calls the handle() processing method and enters the handleInternal() core source code as follows:

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

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

Continue into the invokeHandlerMethod() method. The core source code is as follows:

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

From the source code above, I found that a lot of things have been done:

  • Get parameter binding factory binderFactory

  • Encapsulate handlerMethod into ServletInvocableHandlerMethod again

  • Set parameter parser

  • Set return value parser

  • Set the data binding factory (@RequestBody annotation is useful)

  • new created a ModelAndViewContainer container, similar to Context

Where are the things obtained above initialized? Let’s look back at it all, continue to analyze, and enter the core source code of the invokeAndHandle() method as follows:

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

It can be divided into the following steps:

  • Call specific methods in Controller
  • Set the view rendering switch requestHandled, false means view rendering is required, true means view rendering is not required.
  • Method return value processing in Controller

Let’s first look at the invokeForRequest() method. The core source code is as follows:

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

It can be found that this method mainly parses parameter values ​​and calls specific methods in the Controller through reflection. Let’s see how he parses the parameters and enters the getMethodArgumentValues() method. The core source code is as follows:

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

From the above source code, we can know that each parameter is processed, and each type parameter will have a strategy to process it. If it cannot be processed, an exception will be thrown directly, and the request process will end. This is also a typical dynamic 策略模式. for example:

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

First of all, each parameter type is different, and a strategy must be used to deal with it, so there are a lot of parameter parsers, currently at least >17.
After parsing the parameter values ​​here, you can finally call specific methods through reflection and perform custom logic processing.

Then set the switch requestHandled according to the return value. If there is a return value, it is set to false, which means that view rendering is required. If there is no return value, it is set to true, which means that there is no view rendering.

Then the last step is to process the return value. Enter the handleReturnValue() method. The core source code is as follows:

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

It also requires a bunch of strategies to handle different types of return values. As shown below:

Insert image description here

After the return value processing is completed, return to the top level call and continue to enterreturn getModelAndView(mavContainer, modelFactory, webRequest);The core source code is as follows:

3. Create a ModelAndView object


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

From the above source code, we can know that when requestHandled=true, null is returned directly, indicating that view rendering is not required, and there is no need to perform the following ModeAndView data preparation. If it is false, view rendering is required, so the following code will be executed here to prepare the Mode data, specify the logical view, and prepare the flash memory data if there is flash memory. Finally return to ModelAndView. Then continue to execute the view parsing process.

4. View parsing and rendering

Enter processDispatchResult() and the core source code is as follows:

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

As can be seen from the small code above, mv is the ModelAndView object. If it is null, there is no need for view rendering. When is mv null? As has been analyzed above, it is controlled by a requestHandled switch. When requestHandled=true, null is returned directly, indicating that view rendering is not required, and there is no need to perform the following ModeAndView data preparation. If false, view rendering is required.

Enter the core source code of render() as follows:

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

Here are a few common view parsers:

  • InternalResourceViewResolver : Generates the View view of the JSP page. Spring does not determine whether the JSP page must exist. Tomcat handles it by itself.
  • FreeMarkerViewResolver : Generates the View view of the .ftl template page. Spring will determine whether the .ftl file resource exists.
  • ThymeleafViewResolver : Generates the View view of the .html template page. Spring will determine whether the .html file resource exists.

After the View View is successfully generated, it is necessary to display the effect. Then you need to call the render() rendering method in the View view to display the effect so that you can see it with the naked eye.

Finally, the entire SpringMVC calling process ends. The rest is to analyze which data obtained by get() and where it is initialized.

When a request comes in, the first one enters the getHandler () method, and the corresponding mapping relationship is found through this method. The core source code is as follows:

2. HandlerMapping initialization

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

Look at the code below, where is the initialization value of handlerMappings? You can see a piece of source code as follows:

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

Tips: When the container starts, initStrategies() will be called by the ContextRefreshListener listening class to initialize the components required by webmvc.

You can find that HandlerMapping, HandlerAdapter, and ViewResolver are initialized here. Next, let’s take a look at the initHandlerMappings() method. The core source code is as follows:

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

From the above source code, it can be seen that initialization is done in two places: @EnableWebMvcor DispatcherServlet.properties:

1. @EnableWebMvc annotation method

The beansOfTypeIncludingAncestors(HandlerMapping.class) method can obtain the HandlerMapping implementation class configured in the container. Generally, the @EnableWebMvc annotation will introduce the configuration class, and the HandlerMapping implementation class is injected through @Bean in the configuration class.

The @EnableWebMvc annotation source code is as follows:

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

}

The configuration class DelegatingWebMvcConfiguration is introduced through the Spring annotation @Import. The core source code is as follows:

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

}

The core source code is in the parent class WebMvcConfigurationSupport. Let’s first look at the core source code of initializing HandlerMapping as follows:

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 configuration method

If the HandlerMapping implementation class is not found through the above configuration classes, there will be a covert solution. The getDefaultStrategies () method will look for DispatcherServlet.propertiesthe HandlerMapping implementation class configured in the configuration file. As shown below:

Insert image description here

DispatcherServlet.propertiesThe configuration file is as follows:

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 : Processes the Controller mapping relationship that implements the Controller, AbstractController, and HttpRequestHandler interfaces. In fact, the bottom layer maintains a Map.

RequestMappingHandlerMapping : It handles the Controller relationship mapping in the form of @RequestMapping. The bottom layer maintains a Map container.

SimpleUrlHandlerMapping : Simple URL relationship mapping. You can customize a URL and then bind a processor. It is often used for expansion, such as static resource access, Controller jump, etc.

3. Brief introduction to SimpleUrlHandlerMapping

The source code of this class is as follows:

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

}

I found that a urlMap container is maintained inside , which must be used to maintain the mapping relationship between urlPath and handler. When using this class, you only need to store the relationship between urlPath and handler in the urlMap container, but please note that here only supports handler which is the Controller of the class itself, because when registering the relationship, you can only fill in one value, and this value cannot be accurate. to a certain method, so @RequestMapping cannot be bound through this. This only supports Controllers that implement Controller, AbstractController, and HttpRequestHandler types.

A use case is as follows:

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

In this way, a certain url access path can be bound to a certain Controller and accessed on the browser:http://localhost:8887/area/index
You can access the Controller.

Then we see two other use cases of this class as follows:

@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");
	}
}

Everyone should have used the custom webmvc function. You can specify access to static resources through addResourceHandlers(), and specify which page to jump to through the addViewControllers() method. The bottom layer of these two functions is completed by the SimpleUrlHandlerMapping class.

Let’s first analyze the static resource access method added by addResourceHandlers (). The debug trace is finally called in the following core source code:

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

From the above source code, we can see that the incoming static resource access paths are encapsulated into the urlMap container in the SimpleUrlHandlerMapping class. That's why you write a line of code like thisregistry.addResourceHandler(“/**”).addResourceLocations(“classpath:/static/”)You can access static resources on the browser, because the SimpleUrlHandlerMapping class helps you establish the access path and Controller mapping relationship. The Controller here is ResourceHttpRequestHandler. Each path corresponds to a new ResourceHttpRequestHandler class.

Let’s analyze the addViewControllers () method and trace the debug to the calling point. The core source code is as follows:

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

From the above code, you can know that the SimpleUrlHandlerMapping class establishes a mapping relationship between urlPath and ParameterizableViewController, so you know why you add a line in the addViewControllers() methodregistry.addViewController(“/gotoJsp”).setViewName(“abc2”);Such code can be accessed on the browser. The access path is:http://localhost:8887/gotoJsp

Among them, abc.jsp needs to exist under the webapp , otherwise a 404 error will occur.

Insert image description here

The final access effect is as follows:

Insert image description here

Therefore, the SimpleUrlHandlerMapping class is very important. You can use this class to do some dynamic mapping relationship binding and so on.

3. HandlerAdapter initialization

HandlerAdapter and HandlerMapping are basically similar. The core source code is as follows:

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

It's the same in two ways @EnableWebMvcand DispatcherServletl.properties.

1. @EnableWebMvc annotation method

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 configuration file

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

4. ViewResolver initialization

The view parser is initialized and the core source code is entered as follows:

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

You can find that the initialization of view parsing is basically the same routine as HandlerAdapter and HandlerMapping, but what needs to be noted here is that the @EnableWebMvc annotation has some details when injecting the view parser. As follows:

1. @EnableWebMvc injects the view resolver

Enter the source code as follows:

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

Carefully read the above source code and find the implementation class of the ViewResolver interface from the Spring container through the beanNamesForTypeIncludingAncestors () method. If you do not configure any ViewResolver implementation class, then there will only be one in the container, which is the current @Bean modified mvcViewResolver () Method, there is no doubt that only one will be found. At this time, SpringMVC will help us build a view resolver InternalResourceViewResolver . This view resolver parses JSP resource files by default. Remember to configure suffix and prefix . If they do not match, then add the suffix and prefix on the access path.http://localhost:9292/toJsp?cx=abc2.jspThat's it, if direct access is configuredhttp://localhost:9292/toJsp?cx=abc2, anyway, it is to form a complete road.

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

In addition to default loading, it can also be customized, but you need to implement an interface WebMvcConfigurer and then rewrite the corresponding methods. For example:

@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");
	}
}

However, please note that when customizing functions yourself, you need to first look at how the source code is implemented, because there are indeed many details that can easily lead to errors if you are not careful. For example, in the configureViewResolvers () method, when JSP and FTL files exist at the same time, you need to put the FLT file in front of the JSP to parse the resource normally.

For how these customized functions are loaded, here is a design pattern: 委托模式. It means that the implementation of a certain function is left to another person to do it, and I just call it.

Here is a brief analysis of how the custom configureViewResolvers () method is called. We know that all webmvc functions are basically hard-coded in the WebMvcConfigurationSupport class, but this is not good. Developers also think of this, so in order to be able to extend or customize some implementations, they bury points in each method of this class . The key point is to allow developers to customize some functions. For example, inject the view parser into the WebMvcConfigurationSupport class. The source code is as follows:

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

It is found that the configureViewResolvers () method is a hook method that can be hooked to the subclass implementation. This is a hidden point to facilitate expansion and custom implementation. Generally, this template method will be implemented in a subclass, so you can see how the configureViewResolvers() method is overridden in the subclass DelegatingWebMvcConfiguration . The source code is as follows:

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

	private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();

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

It can be found that this subclass does nothing, but calls the WebMvcConfigurerComposite class method, so here is one 委托模式. Pay special attention here. The instance of WebMvcConfigurerComposite can be directly created with new and is not handed over to the Spring container for management. Then enter the WebMvcConfigurerComposite source code as follows:

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

From the source code above, we can find that it does not do anything, but gives it to the WebMvcConfigurer class again 委托模式, so if we want to customize the function, we can implement the WebMvcConfigurer interface and then rewrite the methods inside.

For example, MyWebMvcConfigure here implements the WebMvcConfigurer interface and customizes the configureViewResolvers() view resolver. The code is as follows:

@Configuration
@EnableWebMvc
public class MyWebMvcConfigure implements WebMvcConfigurer {
    
    

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

Note that this class needs to be managed by Spring, so the @Component or @Configuration annotation must be added. In the WebMvcConfigurationSupport subclass DelegatingWebMvcConfiguration class, this class will be added to the delegates collection in the WebMvcConfigurerComposite class . The source code is as follows:

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

When traversing the configureViewResolvers () method in the WebMvcConfigurerComposite class , you can call back to the configureViewResolvers () method in the custom implementation class MyWebMvcConfigure to complete customization.

Finally, I would like to remind you that when customizing, you must first check how the source code is executed, otherwise it is easy to make detailed errors.

2. DispatcherServlet.properties configuration file

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

5. Establishment of registry or handlerMap mapping relationship

Finally, look back at the question in red font above. Where to initialize handlerMap ? Through the previous analysis, handlerMap is used to save the Controller mapping relationship that implements the Controller, AbstractController, and HttpRequestHandler interface types. There is a urlMap container in the SimpleHandlerMapping class, which can save the mapping relationship between urlPath and Controller. In the end, the value in the urlMap container will be filled into the handlerMap container . Next, take a look at a Controller example to see where it saves the mapping relationship to the handlerMap . The code is as follows:

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

Tracking the source code, we finally found that the call is triggered from the Spring container startup. The source code is as follows:

public abstract class ApplicationObjectSupport implements ApplicationContextAware {
    
    

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

Then continue to trace the initApplicationContext() method, and finally call the registerHandler() method. The source code is as follows:

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

It was found that the mapping relationship was saved to the handlerMap container when the Spring container started . So later in the DispatcherServlet class, you can directly get the value through the get() method.

What is the value in the registry container initialized here? Through the above analysis, the Controller mapping relationship in the form of @RequestMapping will be saved in this Map container. Specifically, let’s take a look at how the value in this container is initialized?

The tracking code triggers the call when it is discovered that it is also a Spring container. The core source code is as follows:

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

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

It is found that it is implemented with the help of Spring declaration cycle class InitializingBean . Enter the initHandlerMethods() code. The core source code is as follows:

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

Get all the methods in the Controller defined in the @RequestMapping form, and establish a mapping relationship for each method. Continue into the registerHandlerMethod() method. The core source code is as follows:

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

Continue to enter the register() method, the core source code is as follows:

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

thisMappingRegistry#register()The main thing is to save the mapping relationship of each method to the registry, which is also a Map container. It's just that their key is wrapped in a layer, and the @RequestMapping attribute is parsed and encapsulated into a RequestMappingInfo object. Then the value is the @RequestMapping modified method which is encapsulated into a HandlerMethod object.

6. Initialization of parameter parser and return value parser

Finally, let’s take a look at where the values ​​of the parameter parser and return value parser are initialized during the calling process ?
Trace the source code and finally implement it in the RequestMappingHandlerAdapter class with the help of the InitializingBean declaration cycle class. The core source code is as follows:

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

Enter the getDefaultArgumentResolvers() method, the core source code is as follows:

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

On this, 30 parameter resolvers are created by default, and finally assigned to the global variable argumentResolvers collection, which can be used later. In the same way, the return value is also done in the same way. The core source code is as follows:

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

Then assign the value to the global variable returnValueHandlers collection, which can be used later. Here you will find that there are two special parameter parsing, RequestResponseBodyMethodProcessor and RequestPartMethodArgumentResolver . Here we only look at the first one. You need to use the MessageConverter message converter to parse and encapsulate it (mainly parsing and converting @RequestBody and @ResponseBody parameters , and mainly converting JSON into entities , entities converted to JSON).

7. MessageConverter message converter initialization

Enter the getMessageConverters() method, the source code is as follows:

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

Then you need to see where the messageConverters variable is assigned. The tracking source code is as follows:

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

calling codeadapter.setMessageConverters(getMessageConverters());Enter the core source code of 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;
	}

If we do not configure messageConverters ourselves, then there will be default messageConverters, enter the addDefaultHttpMessageConverters () method, the source code is as follows:


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

From the above source code, we can find that as long as you introduce the corresponding dependent third-party jar package, the corresponding converter will be automatically added accordingly. For example, if you introduce
com.fasterxml.jackson.databindrelated packages, there will be MappingJackson2HttpMessageConverter message converter. So when you use the @RequestBody and @ResponseBody annotations, first check whether these packages exist. If not, you will not be able to convert them and report an error directly.

One more thing to note: If you want to customize MessageConverter, it is recommended to override the extendMessageConverters() method instead of the configureMessageConverters() method. Look at the source code for clarity, as follows:

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

Guess you like

Origin blog.csdn.net/qq_35971258/article/details/128921548