“This is the second day of my participation in the First Challenge 2022.
preface
If I had to sum up the SpringMVC implementation idea in one sentence, it would be who can handle it, who can I hand it to.
We will not discuss Tomcat here because it is not built into SpringMVC and will not be needed to analyze its process for the time being, but we will need to understand the Tomcat core when we look at SpringBoot in the next chapter.
If Tomcat is not used, how can you test it?
We know that one of the core components of SpringMVC is the DispatcherServlet, which calls its srevice method inside Tomcat to start processing requests. Sure, that’s what we did in the previous chapter with MockHttpServletRequest to construct a request that manually calls the Service method of the DispatcherServlet.
@org.springframework.stereotype.Controller
public static class UserController {
@GetMapping("getUser")
public String getUser(Model model) {
return "user"; }}@Test
public void test(a) throws Exception {
DispatcherServlet dispatcherServlet = initServlet(UserController.class);
MockHttpServletRequest request = new MockHttpServletRequest("GET"."/getUser");
MockHttpServletResponse response = new MockHttpServletResponse();
dispatcherServlet.service(request, response);
}
private DispatcherServlet initServlet(finalClass<? > controllerClass) throws ServletException {
DispatcherServlet dispatcherServlet = new DispatcherServlet() {
@Override
protected WebApplicationContext createWebApplicationContext(@Nullable WebApplicationContext parent) {
GenericWebApplicationContext wac = new GenericWebApplicationContext();
wac.registerBeanDefinition("controller".new RootBeanDefinition(controllerClass));
wac.refresh();
returnwac; }}; dispatcherServlet.init(new MockServletConfig());
return dispatcherServlet;
}
Copy the code
I’ll give it to whoever can handle it
Before we do that, let’s understand the opening sentence, which is who can handle it and who CAN I hand it to. The core of SpringMVC is very simple, because it’s written elegantly and it’s amazing to read, and it keeps asking for a request at every opportunity: “Who can deal with me, who can deal with me”, if anyone, then handed him, such as to the argument parsing time, will all parameters on traversal methods, bring out a single parameter information to ask: “who can deal with me, who can deal with me”, if you have can handle his object, then to him, when you return to view the same, the same is true of.
So where?
Each class in the collection will only handle what he’s interested in. For example, when parsing parameters, the class will only handle @requestParam annotations, and he’ll let go of the rest.
Here we call the traversal of the collection a query and the result of the object’s processing a reply.
And there are two ways to ask and answer, one is to specify an interface, the interface must have two methods, method one is used to identify the class can deal with what things, method two is the class to do specific logic.
For example, when hiring, a company might require that candidates Speak English, so everyone implements the “Speak” interface, as follows.
interface Speak{
Spring getType(a);
void handler(a);
}
Copy the code
GetType () returns what language the person speaks, “Such as English”, which means he can handle the situation with English. The handler() method is to do something specific, whether he is talking or writing an article.
So List
holds everyone, and when asked, you need to walk through the collection, pick out the object that getType() returns “English”, and call its handler().
The second method has no getType(), just handler(), but handler() must return a value. If it can’t handle it, it returns null. If it can handle it, it returns another object.
These two methods are commonly used in SpringMVC.
RequestMappingHandlerMapping
Want to know how to get a request is marked with @ GetMapping annotation methods, the need to understand the RequestMappingHandlerMapping, he is one of the core, his role is to collect RequestMappingInfo, RequestMappingHandlerMapping the default is not in the Spring container, but most of his in the container, points, say first by default, the default will create, through createBean createBean method can refer to the previous Spring core, This method is used to create an object and follows all of Spring’s process of creating an object, but the key point is under the InitializingBean interface that it implements.
The InitializingBean interface has an afterPropertiesSet() method that Spring calls after the object has been created. This method collects all the classes annotated with the @Controller annotation from the existing bean collection. Iterate through all of the @requestMapping annotations, including @getMapping, because @getMapping is labeled @requestMapping (method = requestMethod.get). Find out what this means and encapsulate the method as RequestMappingInfo, so it contains the information on the method and annotation, i.e., a RequestMappingInfo corresponds to a method with an @requestMapping annotation.
With RequestMappingInfo, we can map requests to specific methods.
So the DispatcherServlet must also hold all RequestMappingInfo, otherwise it cannot be mapped.
DispatcherServlet
Java Web development is generally inseparable from Servlet, and SpringMVC is also inseparable from DispatcherServlet, it inherits Servlet, used to do request mapping, is to map the request to the corresponding method, usually his URL address in Tomcat is /.
doDispatch
Starting with the doDispatch of the DispatcherServlet, which is called from service(), the first task is to get the HandlerMapping that can handle the request
mappedHandler = getHandler(processedRequest);
Copy the code
Attention, here is the essence, the RequestMappingHandlerMapping we said above is a HandlerMapping, he can handle request from @ collected under the Controller class, which is sponsored by the request address and request method, It just happens to be defined by @getMapping or something.
But how can SpringMVC define an interface only by @controller and @getMapping? There are other ways for SpringMVC to define interfaces, and other ways for SpringMVC to define interfaces are handled by other HandlerMapping. There is a kind of unusual BeanNameUrlHandlerMapping is, he is according to the name mapping, if there is a name of the Spring container with/at the beginning of bean, and the bean implementation the Controller interface, so this request if like bean name, Leave it to him.
So that’s when SpringMVC starts asking, who can handle the request?
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings ! =null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if(handler ! =null) {
returnhandler; }}}return null;
}
Copy the code
Remember the two modes of inquiry and response we talked about above? Spring uses the second method, which returns null if the Mapping.gethandler can’t handle it, and HandlerExecutionChain if it can.
We start with RequestMappingHandlerMapping getHandler, because he is the most commonly used, but the key is too abstract for SpringMVC, jump around, so I don’t see the source code, we just say thought.
Now that you have all the RequestMappingInfo, remember that you can also add variables to the path, which makes the matching work more complicated, but after all, you just return the HandlerMethod that matched. HandlerMethod is the method that can handle this request.
The next step is to find the HandlerAdapter adapter. This is very nice. We know that before we go into the @getMapping annotation, we have to look for HTTP request parameters based on the parameters that the method needs. If you’ve ever used the Controller interface, you know that the API defined by this method doesn’t require SpringMVC internally to give you the parsing parameters, it just passes you an HttpServletRequest, and you can decide whether to use the parameters or not.
In this case, SpringMVC provides a HandlerAdapter that does some other work on the request, eventually calling the target method.
This is also the process of asking, SpringMVC is going to ask all the HandlerAdapters, who can handle this request, I’m going to hand it to them.
In this case, the query response uses the first method described above, which means that the HandlerAdapter provides two methods, one to determine whether it can handle the request and the other to handle the request specifically.
public interface HandlerAdapter {
boolean supports(Object handler);
@Nullable
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
}
Copy the code
We see his SimpleControllerHandlerAdapter an implementation class, he is dealing with the Controller interface, specific treatment method is to directly call his handleRequest, very simple, called SimpleController after all.
public class SimpleControllerHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Object handler) {
return (handler instanceof Controller);
}
@Override
@Nullable
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return ((Controller) handler).handleRequest(request, response);
}
Copy the code
In view of today’s leading role RequestMappingHandlerAdapter, his support one of the types of conditions must be implemented HandlerMethod.
@Override
public final boolean supports(Object handler) {
return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}
Copy the code
Is the HandlerMethod familiar? As mentioned above, a method labeled @requestMapping can be understood as an instance of HandlerMethod.
The next step will go into the handle of RequestMappingHandlerAdapter started to handle the request.
But there’s another step, and that’s interceptors, they go through all interceptors before they actually process the request, and if one of them returns false, the request doesn’t go down, and there are two by default, but neither of them will intercept any request, Interceptors collected under WebMvcConfigurationSupport normally, if who knows WebMvcConfigurationSupport source code can be found, his internal have a lot of @ Bean, One of these is the RequestMappingHandlerMapping, then there is a problem, the above said we are not RequestMappingHandlerMapping createBean is used to create? Is divided into two kinds of circumstances, the first is the container no WebMvcConfigurationSupport, he will create, through createBean but if have, then use them directly.
By inheriting from addInterceptors, we can add our own interceptor. The interceptor needs to implement the HandlerInterceptor interface, but it will be internally packaged as the MappedInterceptor, because an interceptor usually takes three parameters. Intercept path, exclude path, interceptor interface itself.
If there is no any interceptors to intercept the request, then, would be the Adapter, or for RequestMappingHandlerAdapter, the task of his adaptation is the parameter transformation, in the same way, for SpringMVC converter has many parameters, Some of them deal with @requestParam, some of them deal with arguments of type ServletRequest, so when you’re starting out with SpringMVC, you might get the feeling that no matter how you write arguments, SpringMVC is going to find them for you, you need parameters in the URL, So, @requestParam, you need a Model, that’s Model, you need a Map object, you need a Map object, you need a Map object, you can do anything, that’s because SpringMVC gives us 30 + parameter converters, and those 30 + converters cover most of them, all of them, If you don’t think that’s enough, SpringMVC’s great extensibility also allows you to customize.
So all that’s left is to ask, who can handle this parameter?
SupportsParameter is used to determine whether the argument supports resolution, and resolveArgument is used to convert the argument into a specific argument. I got everything I can use that I can’t.
public interface HandlerMethodArgumentResolver {
boolean supportsParameter(MethodParameter parameter);
@Nullable
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}
Copy the code
RequestParamMethodArgumentResolver, for one, he supported type is @ RequestParam annotations on parameters.
public boolean supportsParameter(MethodParameter parameter) {
if (parameter.hasParameterAnnotation(RequestParam.class)) {
if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
return(requestParam ! =null && StringUtils.hasText(requestParam.name()));
}
else {
return true; }}else {
if (parameter.hasParameterAnnotation(RequestPart.class)) {
return false;
}
parameter = parameter.nestedIfOptional();
if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
return true;
}
else if (this.useDefaultResolution) {
return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
}
else {
return false; }}}Copy the code
Parsing is also done through the native getParameterValues method, which is relatively simple.
The result processing, as well as in the beginning, the SpringMVC, whether the result how to write, can give you return, the SpringMVC, this also is the reason why HandlerMethodReturnValueHandler, have a plenty of processing Map, have a plenty of processing Stream, There are also parameter converters and result converters for HttpHeaders that we use a lot.
Methods on the tag on @ ResponseBody, for example, then eventually will use RequestResponseBodyMethodProcessor, still can design to an HttpMessageConverter inside, used for value, the value is the result of the method to return here, Transformations are used to convert results to JSON format or XML. SpringMVC also provides a large number of HttpMessageConverter, which can also be customized.
Returns the view
That’s the end of it, but there’s one last step, which is to generate the HTML return from the Model, but that’s not necessary, because if you didn’t add @responseBody, then you’d have to do that.
However, you will notice that this step is entered after the request is completed, which is the following method.
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
Copy the code
So this method is going to go in whether or not we added @responseBody, so there must be a flag that says, I’ve handled it, I’ve returned it to the client, don’t handle the view parsing.
And that’s exactly what it is, because if you look at the following sentence, which is very clear in the comments, it means that the handler handler returns does it render this view? If you want to render the MV variable must not be empty, (mv is ModelAndView)
// Did the handler return a view to render?
if(mv ! =null && !mv.wasCleared()) {
render(mv, request, response);
if(errorView) { WebUtils.clearErrorRequestAttributes(request); }}Copy the code
But this is not the main, the main is the following, is located in ModelAndViewContainer, ModelAndViewContainer comes with all the request process, under the RequestResponseBodyMethodProcessor, “ResponseBody” means this method is labeled @responseBody, so during the process, set the following requestHandled variable to true, and the view will be skipped.
/** * Whether the request has been handled fully within the handler. */
public boolean isRequestHandled(a) {
return this.requestHandled;
}
Copy the code
The concrete under RequestMappingHandlerAdapter getModelAndView.
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer, ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
modelFactory.updateModel(webRequest, mavContainer);
// If it has already been processed, skip it
if (mavContainer.isRequestHandled()) {
return null;
}
ModelMap model = mavContainer.getModel();
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
return mav;
}
Copy the code
We can customize view resolution by inheriting the ViewResolver and putting it in a container.
public class ViewTest implements ViewResolver {
@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
return new View() {
@Override
public String getContentType(a) {
return "text/html";
}
@Override
public void render(Map
model, HttpServletRequest request, HttpServletResponse response)
,> throws Exception {
response.getWriter().append("<h1>111</h1>"); }}; }}Copy the code
There can be multiple ViewResolvers, but when SpringMVC iterates internally, as long as the resolveViewName does not return null, it calls the View’s render method to start rendering, which can also be used in SpringBoot.
The ViewResolver parser takes the attributes from the Model and renders them into HTML based on tags such as <c:forEach> in JSP and th:each in Thymeleaf.