Springboot -- Web -- > > complex parameter model and map parsing principle [operate and request parameters with model and map]

Posted by wisewood on Wed, 29 Dec 2021 11:06:51 +0100

Analysis principle of complex parameter model and map [operate and request parameters with model and map]

As early as the Mvc stage, we know that we can use Model and Map to store data and display it on the page. Now we are exploring how to put the data in the field and display it on the page after getting the data and parsing the parameters. That is, how to complete the process of rendering the view

Controller method

Here, the required attribute of the two parameters is set to not necessarily false (if the test is too lazy to change the code, it should learn to change the attribute), because the following request to be forwarded to the controller method does not carry the parameters with these two names. The required default value is true. An error without parameters will be reported when accessing the request, so it is set to false

@ResponseBody
@RequestMapping("/requestParam")
public Map testRequestParam(@RequestAttribute(value = "msg",required = false) String msg,
                            @RequestAttribute(value = "code",required = false) String code,
                            HttpServletRequest request){
    Map map = new HashMap();
    map.put("msg",msg);
    map.put("code",code);
    Object msg1 = request.getAttribute("msg");
    System.out.println("msg1 = " + msg1);
    Object baby = request.getAttribute("baby");
    map.put("baby",baby);
    Object model1 = request.getAttribute("model1");
    map.put("model",model1);
    Object req1 = request.getAttribute("req1");
    map.put("req",req1);
    return map;
}
​
@RequestMapping("/map")
public String testModel(Map<String,Object> map,
                        Model model,
                        HttpServletRequest request,
                        HttpServletResponse response){
    map.put("baby","PaKe");
    model.addAttribute("model1","Petter");
    request.setAttribute("req1","value1");
    Cookie cookie = new Cookie("k1","v1");
    response.addCookie(cookie);
    return "forward:/requestParam";
}

First start the server, then request the map, and find that it can be forwarded through request The getattribute method obtains the data stored in the map and model, which indicates that the data stored in the map and model are actually placed in the request field during processing, that is, the map and model will put the data in the request field, and then obtain it through the reqest object operation. You want to know why to put it in and how to execute it

Request a map and start debug ging

First, go to the dispatcher servlet and determine that the adapter for processing the request controller method is RequestMappingXXXX,

Then come to MV = ha handle.... This line handles the parameter parser and executes the controller method, entering

Go to abstracthandlermethodadapter -- > > handle method, and then go to requestmappinghandleradapter -- > > invokehandlermethod to find familiar methods

invocableMethod.invokeAndHandle(webRequest, mavContainer);

Go to the familiar servletinvocablehandlermethod -- > > invokeandhandle and find it

Here, one line is to process the request and controller parameters, and the other line is to set the return result, which is very important

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

Go to the first line and come to this sentence

Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);

Then go to the controller parameter processing method and find these two lines

//Parameter determination parser
if (!this.resolvers.supportsParameter(parameter)) {
......
//Use the parameter parser to parse
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);

Go directly to the first line. First, find a parser that can handle the current controller parameters from the 27 parameter parsers supported by spring boot

In this for loop, it is finally determined that the first parameter is processed by MapMethodProcessor

private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
   //First get it from the cache to see if this parameter has been resolved. This is to restart the server, so no, result==null, enter the loop
    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;
}

How to deal with it? It's simple

Just judge whether the parameter is of map type

public boolean supportsParameter(MethodParameter parameter) {
   return (Map.class.isAssignableFrom(parameter.getParameterType()) &&
         parameter.getParameterAnnotations().length == 0);
}

After determining the parameters above, the parser will come to args[i] This line parses the parameters, which is to take the matched parameter parser from the cache, judge whether it is not empty, and then resolveArgument parses the returned data

public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
      NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
//Get the parameter parser just matched from the cache
   HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
    //Parser non null judgment
   if (resolver == null) {
      throw new IllegalArgumentException("Unsupported parameter type [" +
            parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
   }
    //Analytical parameters
   return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}

The parameter resolver for the first parameter determined above is MapMethodProcessor [the first parameter of the controller is of Map type], and the second parameter is of Model type. Repeat the above steps to determine that its resolver is ModelMethodProcessor. How to determine?

The same is to see whether the parameter type is Model type. If it matches, it is handled by him. Other procedures for determining parameter parsers are similar to these two

public boolean supportsParameter(MethodParameter parameter) {
   return Model.class.isAssignableFrom(parameter.getParameterType());
}

After determining the parameter parser above, go to args[i] This line parses the parameters and returns the result. Found while observing the results returned by the method getMethodArgumentValues

Map and Model return the same type ModelMap after parsing

 

After all parameters are processed and the array processing results are returned, the parameters can be found here and resolved to ModelMap type

 

After entering the BindingAwareModelMap, it is found that it inherits the extended ModelMap. After entering, it is found that the former inherits the ModelMap. After entering, it is found that it inherits the LinkedHashMap. After entering, it inherits the HashMap. This shows that the essence of ModelMap is a Map. Although this was known when Mvc studied, it is almost forgotten now. In ModelMap, the parameters become both Model and Map [no matter how they become first]

After executing these, go to the Dispatcher and find this line [this is the rendering view, which Mvc has learned]

view.render(mv.getModelInternal(), request, response);

Go to abstractview Render inside

Parameters, request and response are passed in

Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);

This line is the key. Go to abstractview -- > > createmergedoutputmodel [create merged output model data]

Find this line

The above has passed in the model, request and response. At this time, the model is not empty, so put the data into the mergedModel

if (model != null) {
   mergedModel.putAll(model);
}

Finally, return to mergedModel and go to the render method. renderMergedOutputModel [render merged output model data]

renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);

Here is the specific operation. Put the parameters in the request field and enter

Come to InternalResourceView - > > rendermergedoutputmodel, which is the forwarding view [the method return value in Mvc plus forwad will be resolved to InternalResourceView]

// Expose the model object as request attributes.
//Expose the model data as request attributes -- > > to put it bluntly, this sentence puts the data into the request domain
exposeModelAsRequestAttributes(model, request);

After entering, you come to abstractview -- > > exposemoderasrequestattributes

protected void exposeModelAsRequestAttributes(Map<String, Object> model,
      HttpServletRequest request) throws Exception {
​
   model.forEach((name, value) -> {
      if (value != null) {
         request.setAttribute(name, value);
      }
      else {
         request.removeAttribute(name);
      }
   });
}

Is to call request setAttribute(name, value); Putting parameters in is super simple

That is, when the controller method executed when processing a request has parameters of type Model and Map, such parameters will be resolved to ModelAndMap. After the controller method returns, And put the data into the request field when the dispatcher servlet executes the step of render [before deciding to jump (the return value of the current controller method is prefixed with forward)].

After passing the setResponseStatus breakpoint below, When you come to the dispatcher servlet, you find that you have determined the method to process the return value [another method in the controller above], which is another method in the controller. How it determines this is ignored first. After similar operations as above, determine the parameter parser and parse the parameters. Return the parsing results, and finally come to the dispatcher servlet

Determine the return value in handlermethodreturnvaluehandlercomposite --- > > handlereturnvalue

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

Judge whether the return value is not empty, and then return the result

 

It is found that the View is empty, so there must be steps to set the corresponding body to be displayed on the page. Enter the handleReturnValue line to requestresponsebodymethodprocessor -- > > handleReturnValue

He will put the return value into the response body and display it on the page as a response message

public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
      ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
      throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
​
   mavContainer.setRequestHandled(true);
   ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
   ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
​
   // Try even with null return value. ResponseBodyAdvice could get involved.
   writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}

Determines the return value of the controller method

 

Finally, the data and view are returned in this line, which is in requestmappinghandleradapter --- > > invokehandlermethod

return getModelAndView(mavContainer, modelFactory, webRequest);

Finally, the response body and the returned model data are displayed on the page

Topics: Spring Boot mvc