[spring MVC source code] container creation, request handling, exception handling, view rendering and other processes

Relationship with spring

In short, it is the relationship between parent and child containers, but each has different settings. For container creation, you can look at the spring source code. The most special one is that springMVC will register ContextListner, and then finish refresh will trigger the completion of nine components

multicastEvent traverses all listner s and can handle invoke s. The contextrefreshlistener processing method will register and set nine components

The above is the creation of two containers, followed by the service method to accept requests. The final one is doDispatch, which relies on nine components to complete the processing of requests

Focus on doDispatch explanation

1. Check whether it is a file upload request

It mainly judges the processing according to the contentType and parses the uploaded file

processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);

2. Looking for handlerexecutionchain

Find the handler (handler is the logic we write to handle the request) and interceptor interceptors according to handler mapping

HandlerExecutionChain mappedHandler = null;
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
	noHandlerFound(processedRequest, response);
	return;
}

2.1 finding handler

We need to rely on the handlerMapping component to find the handler
There are two main HandlerMapping implementations: AbstractHandlerMethodMapping and AbstractUrlHandlerMapping. The class diagram is as follows

Before understanding these two classes, you need to understand the three implementation methods of Controller, You can refer to this

  • Annotate @ Controller annotation and specify url
  • Inherit the Controller class, implement the handleRequest method, and specify the url in the XML
  • Inherit HttpRequestHandler, implement the handleRequest method, and specify the url in XML

AbstractHandlerMethodMapping solves the problem of finding handlers with @ Controller mode

How to find? AbstractHandlerMethodMapping internally maintains six sets in the MappingRegistry. It mainly uses mappingLookup to save the mapping from url to method.

Where does mappingLookup initialize? Because AbstractHandlerMethodMapping inherits InitializingBean, the MappingRegistry collection is set by the setProperties method when the container creates the bean

The AbstractHandlerMethodMapping class solves the Controller that inherits the Controller and HTTPRequestHandler

How to find? AbstractHandlerMethodMapping internally saves map < pathpattern, Object > handlermap, which saves the mapping from url to controller object
Where is handlerMap initialized? AbstractHandlerMethodMapping inherits ApplicationContextAware, and invokeAwareMethod will be executed when the container creates this bean

2.2 searching for interceptors

adaptedInterceptors are matched mainly according to the url of the handler. Those that can be matched are added to the interceptor set of the chain.

Where does adaptedInterceptors initialize? Similarly, if you inherit ApplicationContextAware, you will execute invokeAwareMethod to initialize the bean when the container creates it

3.handler corresponding to handlerAdapter


In the above process of finding a handler, the name of each handler method is different, so a layer of adaptation is added to make it possible to call the handle method uniformly. Of course, the adapter is far more than that. Because the subsequent call depends on this class, this class needs to have a function, which is also the most cumbersome implementation and the processing and parsing of parameter values. Let's take the RequestMappingHandlerAdapter we often use

4. Handle last modified

boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
	long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
	if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
		return;
	}
}

This method can help us reduce the number of times to obtain resources

5. prehandler method of intercetor

This is where the prehandler method of the interceptor we often write in the project executes

if (!mappedHandler.applyPreHandle(processedRequest, response)) {
	return;
}

6. The handler corresponds to the handlerAdapter call

// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

RequestMappingHandlerAdapter mainly deals with the classes marked by @ Controller. I divide them into creation and call. It should be noted that the creation is created when the springMVC container is started. This doDispatch is called, but the creation process must be explained before understanding

6.1 creating

The most important thing for the container to create the RequestMappingHandlerAdapter object is the afterPorpertiesSet method. Here, the following figure sets the properties

  • initBinderAdviceCache: set the InitBinder of global configuration. The InitBinder of global type needs to be marked with @ controlleradviceon the declared class
  • modelAttributeAdviceCache:: set the ModelAttribute of global configuration. The ModelAttribute of global type needs to be marked on the declared class with @ controlleradvicecache
  • argumentResolvers: used to obtain the corresponding ArgumentResolver according to the annotation type annotated by the method parameters of the current handler, such as @ RequestParam, @ ModelAttribute, etc., so as to convert the parameters in the request to the type of the corresponding annotation in the current method
  • returnValueHandlers: adapt the return value through ReturnValueHandler

@InitBinder can bind request parameters to the specified attribute editor, such as

  • Trim the passed string by trim
  • Convert the date of string type to java Date type

6.2 calling

Call also has preparation procedure and call procedure
Preparation: initBinder, modelAttribute, parameter processor, return value processor, etc

6.2.1 preparation

It can be seen that the values of the previous settings for creating RequestMappingHandlerAdapter are used, but there are two more InitBinderCache, modelAttributeCache, which are local @ InitBinderCache and @ ModelAttribute. They only take effect in the defined class, not global. In this way, we need to set both global and local to the preparation material of this call process. The process of getting the required InitBinder and ModelAttribute code is very type (get the local code first, then the global code, and finally the cache). Get the required InitBinder as follows

Class<?> handlerType = handlerMethod.getBeanType();
Set<Method> methods = this.initBinderCache.get(handlerType);
if (methods == null) {
	methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS);
	this.initBinderCache.put(handlerType, methods);
}
List<InvocableHandlerMethod> initBinderMethods = new ArrayList<>();
// Global methods first
this.initBinderAdviceCache.forEach((controllerAdviceBean, methodSet) -> {
	if (controllerAdviceBean.isApplicableToBeanType(handlerType)) {
		Object bean = controllerAdviceBean.resolveBean();
		for (Method method : methodSet) {
			initBinderMethods.add(createInitBinderMethod(bean, method));
		}
	}
});
for (Method method : methods) {
	Object bean = handlerMethod.getBean();
	initBinderMethods.add(createInitBinderMethod(bean, method));
}
return createDataBinderFactory(initBinderMethods);

First, get the local initBinder cache. If there is no selectMethods, find the local initBinder; Then get the global. After that, the global and local are encapsulated into initBinderMethods and returned

6.2.2 calling

//Get the value restover
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
	HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
	if (result == null) {
		for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
			if (resolver.supportsParameter(parameter)) {
				result = resolver;
				this.argumentResolverCache.put(parameter, result);
				break;
			}
		}
	}
	return result;
}
  • Introduction to important return value processors
    If we label @ ResponseBody, json will be returned. Here is the role of returnValueHanlders. These handlers implement two methods: supportsreturnantype and handlerturnantype
    The returnValueHanlders for processing @ ResponseBody is the RequestResponseBodyMethodProcessor class, and its processing flow is as follows

7. Apply default view name

applyDefaultViewName(processedRequest, mv);
/**
 * Do we need view name translation?
 */
private void applyDefaultViewName(HttpServletRequest request, @Nullable ModelAndView mv) throws Exception {
	if (mv != null && !mv.hasView()) {
		String defaultViewName = getDefaultViewName(request);
		if (defaultViewName != null) {
			mv.setViewName(defaultViewName);
		}
	}
}

8. Execution of interceptor's postHandler

mappedHandler.applyPostHandle(processedRequest, response, mv);

9. Exception handling

It is written inside processHandlerException. If an exception occurs in the previous steps and is caught, it will be detected here for processing

if (exception != null) {
	if (exception instanceof ModelAndViewDefiningException) {
		logger.debug("ModelAndViewDefiningException encountered", exception);
		mv = ((ModelAndViewDefiningException) exception).getModelAndView();
	}
	else {
		Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
		mv = processHandlerException(request, response, handler, exception);
		errorView = (mv != null);
	}
}


Exception handling is not complicated. The complexity is that exception handling is also a method, which also involves the processing of parameters and return values. It is very similar to the handler method executed by the adapter before, as shown in the following figure

10. Page rendering

Here, we need to be very important. According to whether we have mv, if we have mv, we will enter the view rendering, otherwise we will return the original data.

// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
	render(mv, request, response);
	if (errorView) {
		WebUtils.clearErrorRequestAttributes(request);
	}
}
else {
	if (logger.isTraceEnabled()) {
		logger.trace("No view rendering, null ModelAndView returned.");
	}
}

It's also easy to understand here. How to return the original data? I'll take the returnValueHanlders of @ ResponseBody as the RequestResponseBodyMethodProcessor class

When processing again, there is a key statement in the entry, which indicates that mv is not required

mavContainer.setRequestHandled(true);

Then, the last getModelAndView method of the handle of the adapter is executed as follows: if mavContainer.isRequestHandled() is true, null will be returned, and the mv will be empty if NULL is returned, so the view will not be directly returned after rendering

modelFactory.updateModel(webReqest, mavContainer);
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;

11. Execute trigger after completion of interceptor

if (mappedHandler != null) {
	mappedHandler.triggerAfterCompletion(request, response, null);
}

Tags: Operation & Maintenance Spring Container

Posted on Sat, 30 Oct 2021 05:39:51 -0400 by ataylor20