Talk about how SpringMVC works?

The article will be relatively long, it is recommended to collect it, or pay attention to the official account: Hoeller

The role of SpringMVC is unquestionable. Although we all use SpringBoot now, SpringMVC is still used in SpringBoot to process requests.

When we use SpringMVC, the traditional way is to define web.xml, such as:

<web-app>

	<servlet>
		<servlet-name>app</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>/WEB-INF/spring.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<servlet-mapping>
		<servlet-name>app</servlet-name>
		<url-pattern>/app/*</url-pattern>
	</servlet-mapping>

</web-app>
复制代码

We only need to define such a web.xml, and then start Tomcat, then we can use SpringMVC normally.

In SpringMVC, the core is DispatcherServlet, in the process of starting Tomcat:

  1. Tomcat will first create the DispatcherServlet object
  2. Then call the init() of the DispatcherServlet object

In the init() method, a Spring container will be created and a ContextRefreshListener listener will be added, which will listen to the ContextRefreshedEvent event (this event will be released after the Spring container is started), that is to say, after the Spring container is started, The onApplicationEvent() method in the ContextRefreshListener will be executed, and finally the initStrategies() in the DispatcherServlet will be executed. More content will be initialized in this method:

protected void initStrategies(ApplicationContext context) {
    initMultipartResolver(context);
	initLocaleResolver(context);
	initThemeResolver(context);

	initHandlerMappings(context);
	initHandlerAdapters(context);

	initHandlerExceptionResolvers(context);
	initRequestToViewNameTranslator(context);
	initViewResolvers(context);
	initFlashMapManager(context);
}
复制代码

The core of which is HandlerMapping and HandlerAdapter .

What is a Handler?

Handler represents the request processor. There are four Handlers in SpringMVC:

  1. A Bean object that implements the Controller interface
  2. A Bean object that implements the HttpRequestHandler interface
  3. Added @RequestMapping annotation method
  4. a HandlerFunction object

For example, a Bean object that implements the Controller interface:

@Component("/test")
public class HoellerBeanNameController implements Controller {

	@Override
	public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
		System.out.println("Hoeller");
		return new ModelAndView();
	}
}
复制代码

A Bean object that implements the HttpRequestHandler interface:

@Component("/test")
public class HoellerBeanNameController implements HttpRequestHandler {

	@Override
	public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		System.out.println("Hoeller");
	}
}
复制代码

Added @RequestMapping annotation method:

@RequestMapping
@Component
public class HoellerController {

	@Autowired
	private HoellerService hoellerService;

	@RequestMapping(method = RequestMethod.GET, path = "/test")
	@ResponseBody
	public String test(String username) {
		return "Hoeller";
	}

}
复制代码

A HandlerFunction object (there are two in the code below):

@ComponentScan("com.hoeller")
@Configuration
public class AppConfig {

	@Bean
	public RouterFunction<ServerResponse> person() {
		return route()
				.GET("/app/person", request -> ServerResponse.status(HttpStatus.OK).body("Hello GET"))
				.POST("/app/person", request -> ServerResponse.status(HttpStatus.OK).body("Hello POST"))
				.build();
	}
    
}
复制代码

What is HandlerMapping?

HandlerMapping is responsible for finding the Handler and saving the mapping relationship between the path and the Handler.

Because there are different types of Handlers, different HandlerMappings are responsible for finding Handlers in SpringMVC, such as:

  1. BeanNameUrlHandlerMapping: Responsible for Controller interface and HttpRequestHandler interface
  2. RequestMappingHandlerMapping: method responsible for @RequestMapping
  3. RouterFunctionMapping: responsible for RouterFunction and its HandlerFunction

BeanNameUrlHandlerMapping search process:

  1. Find all beanNames in the Spring container
  2. Determine whether the beanName starts with "/"
  3. If yes, treat it as a Handler, and store the beanName as the key and the bean object as the value in the handlerMap
  4. handlerMap is a Map

The search process of RequestMappingHandlerMapping:

  1. Find all beanTypes in the Spring container
  2. Determine whether the beanType has @Controller annotations, or whether it has @RequestMapping annotations
  3. If the judgment is successful, continue to find the Method with @RequestMapping added to the beanType
  4. And parse the content in @RequestMapping, such as method, path, and encapsulate it as a RequestMappingInfo object
  5. Finally, the RequestMappingInfo object is used as a key, and the Method object is encapsulated into a HandlerMethod object as a value, which is stored in the registry
  6. Registry is a Map

The search process of RouterFunctionMapping will be somewhat different, but it is roughly the same, which is equivalent to a path corresponding to a HandlerFunction.

In addition to being responsible for finding the Handler and recording the mapping relationship, each HandlerMapping naturally needs to find the corresponding Handler according to the request path. In the source code, these three HandlerMappings have a common parent class AbstractHandlerMapping 

AbstractHandlerMapping implements the HandlerMapping interface and implements the getHandler(HttpServletRequest request) method.

AbstractHandlerMapping will be responsible for calling the getHandlerInternal(HttpServletRequest request) method of the subclass to find the Handler corresponding to the request, and then AbstractHandlerMapping will be responsible for integrating the Handler and the HandlerInterceptor configured in the application into a HandlerExecutionChain object.

Therefore, the source code of finding Handler is implemented in getHandlerInternal() in each HandlerMapping subclass. The process of finding Handler according to the request path is not complicated, because the mapping relationship between path and Handler already exists in Map.

The more difficult point is, when DispatcherServlet receives a request, which HandlerMapping should be used to find the Handler? Look at the source code:

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;
}
复制代码

It is very simple, it is traversal, and returns when found, the default order is: 

So BeanNameUrlHandlerMapping has the highest priority, for example:

@Component("/test")
public class HoellerBeanNameController implements Controller {

	@Override
	public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
		System.out.println("Hello Hoeller");
		return new ModelAndView();
	}
}
复制代码
@RequestMapping(method = RequestMethod.GET, path = "/test")
@ResponseBody
public String test(String username) {
	return "Hi Hoeller";
}
复制代码

The request paths are all /test, but ultimately the Controller interface will take effect.

What is HandlerAdapter?

After finding the Handler, it is time to execute it, such as executing the following test()

@RequestMapping(method = RequestMethod.GET, path = "/test")
@ResponseBody
public String test(String username) {
	return "Hoeller";
}
复制代码

However, since there are different types of Handlers, the execution methods are different. Let’s summarize the types of Handlers:

  1. The Bean object that implements the Controller interface executes handleRequest() in the Bean object
  2. The Bean object that implements the HttpRequestHandler interface executes handleRequest() in the Bean object
  3. Added the @RequestMapping annotation method, specifically a HandlerMethod, which executes the currently annotated method
  4. A HandlerFunction object that executes handle() in the HandlerFunction object

So, logically speaking, after finding the Handler, we have to judge its type. For example, the code may look like this:

Object handler = mappedHandler.getHandler();
if (handler instanceof Controller) {
    ((Controller)handler).handleRequest(request, response);
} else if (handler instanceof HttpRequestHandler) {
    ((HttpRequestHandler)handler).handleRequest(request, response);
} else if (handler instanceof HandlerMethod) {
    ((HandlerMethod)handler).getMethod().invoke(...);
} else if (handler instanceof HandlerFunction) {
    ((HandlerFunction)handler).handle(...);
}
复制代码

But SpringMVC is not written in this way, it still adopts the adaptation mode , adapts different types of Handlers into a HandlerAdapter, and then executes the handle() method of the HandlerAdapter to execute the corresponding methods of different types of Handlers.

For different Handlers, there will be different adapters:

  1. HttpRequestHandlerAdapter
  2. SimpleControllerHandlerAdapter
  3. RequestMappingHandlerAdapter
  4. HandlerFunctionAdapter

The adaptation logic is:

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    if (this.handlerAdapters != null) {
        for (HandlerAdapter adapter : this.handlerAdapters) {
            if (adapter.supports(handler)) {
                return adapter;
            }
        }
    }
    throw new ServletException("No adapter for handler [" + handler +
                               "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
复制代码

Pass in the handler, traverse the above four Adapters, and return whoever supports it. For example, the judgment code is as follows:

public boolean supports(Object handler) {
    return (handler instanceof HttpRequestHandler);
}

public boolean supports(Object handler) {
    return (handler instanceof Controller);
}

public final boolean supports(Object handler) {
    return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}

public boolean supports(Object handler) {
    return handler instanceof HandlerFunction;
}
复制代码

After the corresponding HandlerAdapter is adapted according to the Handler, the handle() method of the specific HandlerAdapter object is executed, for example:

HttpRequestHandlerAdapter的handle():

public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
	((HttpRequestHandler) handler).handleRequest(request, response);
	return null;
}
复制代码

SimpleControllerHandlerAdapter的handle():

public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
	return ((Controller) handler).handleRequest(request, response);
}
复制代码

HandlerFunctionAdapter的handle():

HandlerFunction<?> handlerFunction = (HandlerFunction<?>) handler;
serverResponse = handlerFunction.handle(serverRequest);
复制代码

Because these three receive directly the Requestet object, without SpringMVC for additional parsing, it is relatively simple, and the more complicated one is RequestMappingHandlerAdapter, which executes the method with @RequestMapping added, and this method can be written in various ways , SpringMVC needs to parse the Request object according to the definition of the method, get the corresponding data from the request and pass it to the method, and execute it.

@RequestMapping method parameter analysis

When SpringMVC receives the request and finds the corresponding Method, it will execute the method, but before executing it, it needs to obtain the corresponding data from the request according to the parameter information defined by the method, and then pass the data to the method and execute it. .

A HttpServletRequest usually has:

  1. request parameter
  2. request attribute
  3. request session
  4. reqeust header
  5. reqeust body

For example, the following methods:

public String test(String username) {
    return "Hoeller";
}
复制代码

Indicates that the value whose key is username is to be obtained from the request parameter

public String test(@RequestParam("uname") String username) {
    return "Hoeller";
}
复制代码

Indicates that the value whose key is uname is to be obtained from the request parameter

public String test(@RequestAttribute String username) {
    return "Hoeller";
}
复制代码

Indicates that the value whose key is username is to be obtained from the request attribute

public String test(@SessionAttribute String username) {
    return "Hoeller";
}
复制代码

Indicates that the value whose key is username is to be obtained from the request session

public String test(@RequestHeader String username) {
    return "Hoeller";
}
复制代码

Indicates that the value whose key is username is to be obtained from the request header

public String test(@RequestBody String username) {
    return "Hoeller";
}
复制代码

Represents getting the entire request body

Therefore, we found that SpringMVC has to parse the method parameters to see what information in the request the parameter is to obtain.

And this process is realized through HandlerMethodArgumentResolver in the source code, such as:

  1. RequestParamMethodArgumentResolver: Responsible for handling @RequestParam
  2. RequestHeaderMethodArgumentResolver: responsible for processing @RequestHeader
  3. SessionAttributeMethodArgumentResolver: responsible for handling @SessionAttribute
  4. RequestAttributeMethodArgumentResolver: Responsible for handling @RequestAttribute
  5. RequestResponseBodyMethodProcessor: Responsible for processing @RequestBody
  6. and many others...

It is also very rude to judge which HandlerMethodArgumentResolver should handle a certain parameter:

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;

}
复制代码

It is to traverse all HandlerMethodArgumentResolver, whichever can support the processing of the current parameter will be handled by whichever one.

for example:

@RequestMapping(method = RequestMethod.GET, path = "/test")
@ResponseBody
public String test(@RequestParam @SessionAttribute String username) {
	System.out.println(username);
	return "Hoeller";
}
复制代码

The username in the above code will correspond to the username in RequestParam, not in session, because RequestParamMethodArgumentResolver is higher in the source code.

Of course, HandlerMethodArgumentResolver will also be responsible for obtaining the corresponding data from the request, corresponding to the resolveArgument() method.

For example RequestParamMethodArgumentResolver:

protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
    HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);

    if (servletRequest != null) {
        Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
        if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
            return mpArg;
        }
    }

    Object arg = null;
    MultipartRequest multipartRequest = request.getNativeRequest(MultipartRequest.class);
    if (multipartRequest != null) {
        List<MultipartFile> files = multipartRequest.getFiles(name);
        if (!files.isEmpty()) {
            arg = (files.size() == 1 ? files.get(0) : files);
        }
    }
    if (arg == null) {
        String[] paramValues = request.getParameterValues(name);
        if (paramValues != null) {
            arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
        }
    }
    return arg;
}

复制代码

The core is:

if (arg == null) {
    String[] paramValues = request.getParameterValues(name);
    if (paramValues != null) {
        arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
    }
}
复制代码

In the same way, you can find the value required by each parameter in the method, so as to execute the method and get the return value of the method.

@RequestMapping method return value analysis

The return value of the method will also be divided into different situations. For example, if the @ResponseBody annotation is added, if the method returns a String:

  1. Added @ResponseBody annotation: it means to return this String directly to the browser
  2. No @ResponseBody annotation: It means that the corresponding page should be found according to this String, and the page should be returned to the browser

In SpringMVC, HandlerMethodReturnValueHandler is used to handle the return value:

  1. RequestResponseBodyMethodProcessor: handle the case of adding @ResponseBody annotation
  2. ViewNameMethodReturnValueHandler: handle the situation where the @ResponseBody annotation is not added and the return value type is String
  3. ModelMethodProcessor: handle the case where the return value is the Model type
  4. and many others...

We only talk about RequestResponseBodyMethodProcessor here, because it will handle the situation where @ResponseBody annotation is added, and it is also the situation we use most at present.

RequestResponseBodyMethodProcessor is equivalent to directly responding to the browser with the object returned by the method. If the returned object is a string, then it is easy to say that the string is directly responded to the browser. What if it returns a Map? Is it a User object? How to respond these complex objects to the browser?

To deal with this, SpringMVC will use HttpMessageConverter to handle it. For example, by default, SpringMVC will have 4 HttpMessageConverters:

  1. ByteArrayHttpMessageConverter: handle the case where the return value is a byte array , and return the byte array to the browser
  2. StringHttpMessageConverter: handle the case where the return value is a string , and return the string to the browser after the specified encoding sequence number
  3. SourceHttpMessageConverter: Handle the situation where the return value is an XML object , such as returning the DOMSource object to the browser
  4. AllEncompassingFormHttpMessageConverter: handle the case where the return value is a MultiValueMap object

The source code of StringHttpMessageConverter is also relatively simple:

protected void writeInternal(String str, HttpOutputMessage outputMessage) throws IOException {
    HttpHeaders headers = outputMessage.getHeaders();
    if (this.writeAcceptCharset && headers.get(HttpHeaders.ACCEPT_CHARSET) == null) {
        headers.setAcceptCharset(getAcceptedCharsets());
    }
    Charset charset = getContentTypeCharset(headers.getContentType());
    StreamUtils.copy(str, charset, outputMessage.getBody());
}
复制代码

First check whether the Content-Type is set, if not set, take the default, the default is ISO-8859-1, so by default, the returned Chinese will be garbled, you can solve it by the following methods:

@RequestMapping(method = RequestMethod.GET, path = "/test", produces = {"application/json;charset=UTF-8"})
@ResponseBody
public String test() {
	return "公众号 Hoeller";
}
复制代码
@ComponentScan("com.hoeller")
@Configuration
@EnableWebMvc
public class AppConfig implements WebMvcConfigurer {

	@Override
	public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
		StringHttpMessageConverter messageConverter = new StringHttpMessageConverter();
		messageConverter.setDefaultCharset(StandardCharsets.UTF_8);
		converters.add(messageConverter);
	}
}
复制代码

However, the above four Converters cannot handle Map objects or User objects, so if a Map or User object is returned, a Converter must be configured separately, such as MappingJackson2HttpMessageConverter. This Converter is more powerful and can convert String, Map, User objects, etc. can be converted into JSON format.

@ComponentScan("com.hoeller")
@Configuration
@EnableWebMvc
public class AppConfig implements WebMvcConfigurer {

	@Override
	public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
		MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
		messageConverter.setDefaultCharset(StandardCharsets.UTF_8);
		converters.add(messageConverter);
	}
}
复制代码

The specific conversion logic is the conversion logic of Jackson2.

Summarize

The above is the overall process of the entire SpringMVC from startup to processing requests, from receiving requests to executing methods. If you like it, please like it and forward it. I will share more details later.

Guess you like

Origin blog.csdn.net/Java_LingFeng/article/details/128672906