ModelAndViewContainer, ModelMap, Model Details [Learning Spring MVC]

Keywords: Java Spring Session Attribute JSP

Every sentence

How well an open source technology product does depends on how many non-functional problems you can solve (because functional problems are conceivable for all products).

Preface

Writing this article is not my original intention, because I think the understanding of these categories is still a relatively basic and simple piece of content, until more than two students asked me some questions: through chat found that small partners have heard of these categories, but for their use, functional positioning is silly and unclear. Chu's (because there are many similarities in names).
So writing this article is just like a popular science article. It's not necessary to continue reading this article because I'm already familiar with my little buddies, because it's not too difficult (of course, I'm just suggesting).

ModelAndViewContainer

I put this class first because it is a little more logical and helpful in understanding the processing of processor ReturnValue returns.

ModelAndViewContainer: It can be defined as a container for the ModelAndView context, which is responsible for data transfer throughout the request process - > Preserving Models and Views. The official doc interprets it as follows:

Records model and view related decisions made by {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers} and
{@link HandlerMethodReturnValueHandler HandlerMethodReturnValueHandlers} during the course of invocation of a controller method.

Translated into "Mandarin" is to record the model model and view information used by Handler Method ArgumentResolver and Handler Method Return Value Handler in handling handler method of Controller.

Of course, besides saving Model and View, it also provides some other functions. Now let's familiarize ourselves with its API and source code.

// @since 3.1
public class ModelAndViewContainer {
    // ================= The attributes it holds are still important.=================
    // Whether the defaultModel default value is false when redirect is ignored: not ignored
    private boolean ignoreDefaultModelOnRedirect = false;
    // This view may be a View or just a logical view String
    @Nullable
    private Object view;
    // Default Model
    // Note: ModelMap is just a Map, but the implementation class BindingAware ModelMap implements the org. spring framework. ui. Model interface.
    private final ModelMap defaultModel = new BindingAwareModelMap();
    // Model used for redirection (provide set method settings)
    @Nullable
    private ModelMap redirectModel;
    // Does the controller return redirection instructions?
    // For example, the prefix "redirect:xxx.jsp" is used, and this value is true. And then ultimately a RedirectView
    private boolean redirectModelScenario = false;
    // Http status code
    @Nullable
    private HttpStatus status;
    
    private final Set<String> noBinding = new HashSet<>(4);
    private final Set<String> bindingDisabled = new HashSet<>(4);

    // It's easy to imagine that it's related to the @SessionAttributes tag element
    private final SessionStatus sessionStatus = new SimpleSessionStatus();
    // This property is always important: whether the tag handler has completed ** request processing
    // In chain operation, this markup is very important.
    private boolean requestHandled = false;
    ...

    public void setViewName(@Nullable String viewName) {
        this.view = viewName;
    }
    public void setView(@Nullable Object view) {
        this.view = view;
    }
    // Is it a reference to a view?
    public boolean isViewReference() {
        return (this.view instanceof String);
    }

    // Whether to use the default Model
    private boolean useDefaultModel() {
        return (!this.redirectModelScenario || (this.redirectModel == null && !this.ignoreDefaultModelOnRedirect));
    }
    
    // Notice the difference between the submethod and the following getDefaultModel() method
    public ModelMap getModel() {
        if (useDefaultModel()) { // Use the default view
            return this.defaultModel;
        } else {
            if (this.redirectModel == null) { // If the redirected view is null, an empty new return is made
                this.redirectModel = new ModelMap();
            }
            return this.redirectModel;
        }
    }
    // @since 4.1.4
    public ModelMap getDefaultModel() {
        return this.defaultModel;
    }

    // @ since 4.3 can set the response code, which will eventually be used with ModelAndView when rendered by View.
    public void setStatus(@Nullable HttpStatus status) {
        this.status = status;
    }

    // Programmatically registering a property that ** should not ** have data binding, and for subsequently declared @ModelAttribute it cannot be bound
    // Although the method is set, the inside is add.~~~~
    public void setBindingDisabled(String attributeName) {
        this.bindingDisabled.add(attributeName);
    }
    public boolean isBindingDisabled(String name) {
        return (this.bindingDisabled.contains(name) || this.noBinding.contains(name));
    }
    // Whether registration should be data binding for the corresponding model attributes
    public void setBinding(String attributeName, boolean enabled) {
        if (!enabled) {
            this.noBinding.add(attributeName);
        } else {
            this.noBinding.remove(attributeName);
        }
    }

    // This method needs to focus on whether the request has been fully processed in the handler.
    // For example, the @ResponseBody annotated method returns a value that does not require View to continue processing, so you can set the value to true.
    // Explanation: This property can achieve the same effect through the source ServletResponse and OutputStream.
    public void setRequestHandled(boolean requestHandled) {
        this.requestHandled = requestHandled;
    }
    public boolean isRequestHandled() {
        return this.requestHandled;
    }

    // ========= Here's how Model works==========
    // addAttribute/addAllAttributes/mergeAttributes/removeAttributes/containsAttribute
}

After reading the source code intuitively, at least I can get the following conclusions to share with you:

  • It maintains model models: defaultModle and redirectModel
  • defaultModel is the default Model, and redirectModel is the Model used to pass redirect.
  • When the Controller processor enters the Model or ModelMap type, the defaultModel is actually passed in.
    - defaultModel is actually a Binding Aware Model, a Map. It also inherits ModelMap and implements the Model interface, so when you use Model or ModelMap in a processor, you actually use the same object.~~~
    - Refer to MapMethodProcessor, which ultimately calls the mavContainer.getModel() method
  • If the processor input type is RedirectAttributes type, the redirectModel is eventually passed in.
    - As for why is defaultModel actually passed in? Reference: Redirect Attributes Method ArgumentResolver, using the new Redirect Attributes Model Map (data Binder).
  • Maintenance view (compatible support logical view name)
  • Maintaining redirect information and judging from this, Handler Adapter uses default model or redirect model
  • Maintain @SessionAttributes annotation information status
  • Maintaining whether handler handles markup (important)

Next, I will focus on the role of its request Handled attribute.

RequHandled property

1. Let's first look at the use of the isRequestHandled() method:
RequestMapping Handler Adapter's use of the mavContainer.isRequestHandled() method may help you understand something:

The actual execution of this method is that when the Handler Method is fully invoked and executed, the method is executed to fetch the ModelAndView (passing in request and ModelAndViewContainer)

RequestMappingHandlerAdapter: 
    @Nullable
    private ModelAndView getModelAndView(ModelAndViewContainer mavContainer ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
        // Promote model attributes listed as @Session Attributes to sessions
        modelFactory.updateModel(webRequest, mavContainer);
        if (mavContainer.isRequestHandled()) {
            return null;
        }

        ModelMap model = mavContainer.getModel();
        ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
        // Real View Visible ModelMap / View Name, State HttpStatus are finally handed over to Veiw to render
        if (!mavContainer.isViewReference()) {
            mav.setView((View) mavContainer.getView());
        }
        
        // This step: Spring MVC support for redirection~~~~
        // Pass values between redirections, using the RedirectAttributes Model~~~~
        if (model instanceof RedirectAttributes) {
            Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
            HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
            if (request != null) {
                RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
            }
        }
    }

You can see that if the Model AndViewContainer has been processed, it returns null directly here, that is, it will no longer continue to process Model and View.~

2. Use of setRequestHandled() method
As a setup method, there are many places to call, summarized as follows:

  • AsyncTaskMethodReturnValueHandler: Processing return value types is the method of WebAsyncTask
// If null is returned, there is no need to continue processing
if (returnValue == null) {
    mavContainer.setRequestHandled(true);
    return;
}
  • Callable MethodReturnValueHandler/Deferred ResultMethodReturnValueHandler/StreamingResponseBodyReturnValueHandler: The method for handling return value types is Callable/Deferred Result/Listenable Future/CompletionStage/StreamingResponseBody (same principle)
  • HttpEntityMethodProcessor: The return value type is the method of HttpEntity
// As you can see, this return value is marked as processed so that the view (rendering) is no longer needed.
    @Override
    public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
        mavContainer.setRequestHandled(true); // The first sentence is this code.
        if (returnValue == null) {
            return;
        }
        ... // Give it to the message processor to write        
        outputMessage.flush();
    }
  • The same principle applies to HttpHeaders Return Value Handler/RequestResponseBodyMethod Processor/ResponseBodyEmitter Return Value Handler and so on.
  • ServletInvocableHandlerMethod/HandlerMethod sometimes annotates that true has been processed when handling Handler methods (e.g. get request NotModified/HttpStatus status code/isRequestHandled()==true and so on). In addition to these case s, when the method method is executed, it will display the false settings (because after the handler method is executed, it needs to be rendered to the view ~)
  • Servlet Response Method ArgumentResolver: This is the only one that handles entry time. If the input type is ServletResponse/OutputStream/Writer, and mavContainer!= null, it is set to true (because Spring MVC thinks that since you have introduced response yourself, you can do it yourself, so when you use it, here is the detail that needs special attention ~)
resolveArgument()Method:

        if (mavContainer != null) {
            mavContainer.setRequestHandled(true); // It's equivalent to saying you need `ServletResponse', so the return value should be handled by yourself.~~~~
        }

This is the most important class in this article: Model AndViewContainer. Next, the introduction is very simple, relaxed and pleasant.

Model

The concept of org. spring framework. ui. Model is often mentioned in both MVC design patterns and Spring MVC: it is used by the control layer to return the required data (data needed for rendering) to the front end.

//  @ since 2.5.1 It is an interface
public interface Model {
    ...
    // addAttribute/addAllAttributes/mergeAttributes/containsAttribute
    ...
    // Return the current set of model attributes as a Map.
    Map<String, Object> asMap();
}

Its succession tree is as follows:

The most important one must be the Extended Model Map, which is left to go into detail when introducing the Model Map, and simply look at the rest of the subclasses.

RedirectAttributes

As you can see from the naming, it is related to redirection, which extends the Model interface:

// @since 3.1
public interface RedirectAttributes extends Model {
    ...
    // The three methods it extends are all related to flash attributes
    RedirectAttributes addFlashAttribute(String attributeName, @Nullable Object attributeValue);
    // There is no key specified here, because the key is automatically generated according to Conventions#getVariableName().
    RedirectAttributes addFlashAttribute(Object attributeValue);
    // Return the attributes candidate for flash storage or an empty Map.
    Map<String, ?> getFlashAttributes();
}
RedirectAttributesModelMap

It implements the RedirectAttributes interface and inherits from ModelMap, so it "indirectly" implements all the methods of the Model interface.

public class RedirectAttributesModelMap extends ModelMap implements RedirectAttributes {
    @Nullable
    private final DataBinder dataBinder;
    private final ModelMap flashAttributes = new ModelMap();
    ...
    @Override
    public RedirectAttributesModelMap addAttribute(String attributeName, @Nullable Object attributeValue) {
        super.addAttribute(attributeName, formatValue(attributeValue));
        return this;
    }

    // So the data Binder here is for data conversion.
    // Convert all parameters to String type (because Http is string parameter)
    @Nullable
    private String formatValue(@Nullable Object value) {
        if (value == null) {
            return null;
        }
        return (this.dataBinder != null ? this.dataBinder.convertIfNecessary(value, String.class) : value.toString());
    }
    ...

    @Override
    public Map<String, Object> asMap() {
        return this;
    }
    @Override
    public RedirectAttributes addFlashAttribute(String attributeName, @Nullable Object attributeValue) {
        this.flashAttributes.addAttribute(attributeName, attributeValue);
        return this;
    }
    ...
}

I think the only thing that makes sense is that it uses DataBinder to convert the added attribute parameter to String type (why to String type, have you ever thought??) ~

ConcurrentModel

It came only after Spring 5.0. It's a thread-safe Model, and it doesn't provide anything new, just for scenarios with thread-safe problems.

ModelMap

ModelMap inherits from LinkedHashMap, so its essence is just a Map.
Its characteristic is that it indirectly implements the interface method of org. spring framework. ui. model with the help of Map's ability, which is more worthy of our reference and study.

so, just look at the Extended Model Map. It inherits itself from ModelMap. It has no features. It's all a copy of the interface method completed by calling the method of the parent class. Meow his subclass.~

BindingAwareModelMap

Note: It differs from ordinary ModelMap in that it can perceive data validation results (if the keys put in have the corresponding binding results, and your value is not the binding result itself). Then remove the key pair ~ of MODEL_KEY_PREFIX + key.

public class BindingAwareModelMap extends ExtendedModelMap {

    // Annotations override Map's put method and intercept all addAttr methods at once...
    @Override
    public Object put(String key, Object value) {
        removeBindingResultIfNecessary(key, value);
        return super.put(key, value);
    }
    @Override
    public void putAll(Map<? extends String, ?> map) {
        map.forEach(this::removeBindingResultIfNecessary);
        super.putAll(map);
    }

    // The logic of this type of processing:
    private void removeBindingResultIfNecessary(Object key, Object value) {
        // key must be String type to be handled
        if (key instanceof String) {
            String attributeName = (String) key;
            if (!attributeName.startsWith(BindingResult.MODEL_KEY_PREFIX)) {
                String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + attributeName;
                BindingResult bindingResult = (BindingResult) get(bindingResultKey);

                // If there is a validation result and the value put in is not the binding result itself, remove the binding result (equivalent to overwriting it).
                if (bindingResult != null && bindingResult.getTarget() != value) {
                    remove(bindingResultKey);
                }
            }
        }
    }
}

Spring MVC uses this ModelMap by default, but most of the perception capabilities it provides are not needed. But you don't need to take care of it anyway.

ModelAndView

As the name implies, ModelAndView refers to a collection of models and views that contain both models and views; ModelAndView can generally be used as a return value for Controller, so its instance is manually created by the developer itself, which is also the main difference between it and the above (all containers are created above, and then injected to us for use) .

Because this class is directly for developers, it is recommended that some of the API s should be familiar with:

public class ModelAndView {
    @Nullable
    private Object view; // It can be View or String
    @Nullable
    private ModelMap model;

    // Obviously, you can also put an http status code in by yourself.
    @Nullable
    private HttpStatus status;  
    // Mark whether the instance has been called the clear() method~~~
    private boolean cleared = false;

    // A total of these attributes: It provides a lot of constructors, which I don't list here.
    public void setViewName(@Nullable String viewName) {
        this.view = viewName;
    }
    public void setView(@Nullable View view) {
        this.view = view;
    }
    @Nullable
    public String getViewName() {
        return (this.view instanceof String ? (String) this.view : null);
    }
    @Nullable
    public View getView() {
        return (this.view instanceof View ? (View) this.view : null);
    }
    public boolean hasView() {
        return (this.view != null);
    }
    public boolean isReference() {
        return (this.view instanceof String);
    }

    // protected method~~~
    @Nullable
    protected Map<String, Object> getModelInternal() {
        return this.model;
    }
    public ModelMap getModelMap() {
        if (this.model == null) {
            this.model = new ModelMap();
        }
        return this.model;
    }

    // Some methods for manipulating ModelMap are as follows:
    // addObject/addAllObjects

    public void clear() {
        this.view = null;
        this.model = null;
        this.cleared = true;
    }
    // The premise is: this. view = null 
    public boolean isEmpty() {
        return (this.view == null && CollectionUtils.isEmpty(this.model));
    }
    
    // It was used unexpectedly, and the crooked nuts were rigorous.
    public boolean wasCleared() {
        return (this.cleared && isEmpty());
    }
}

Many people wonder: Why can Controller not only return ModelAndView, but also pass values directly to the page by returning Map/Model/ModelMap, etc? If the return value is the last three, how do you find the view to render?

I'll throw out this question, but I won't give you an answer in this article. Because we have all talked about this, this problem should not be difficult, it is suggested that small partners must understand the reasons for their own (please do not let go of useful knowledge points). If there's something I really don't understand, leave a message and I'll answer it for you.~
Answer Reference Tips: Refer to Model Method Processor and ModelMethod Processor's Processing Module for Return Value

In most cases, I recommend returning to Model AndView instead of the other Nago III. Because the third brother didn't specify the view name, the view name generated by Dispatcher Servlet. applyDefaultViewName () is generally not what we need. (Unless your catalogue, naming, and so on are all very specific, it can save a lot of work by the way.)

ModelFactory

About Model Factory, This article I've explained it in detail. Here are two more sentences about its function.
Model Factory is used to maintain Model, which contains two functions.

  1. Initialization Model
  2. After the processor executes, the corresponding parameters in Model are updated to Session Attributes (processing @ModelAttribute and @Session Attributes)

    summary

    I thought this article would not be very long, but I did not expect to write more than 10,000 words in the middle of the article. I hope this article can help you understand the core content of model and view in Spring MVC, and help you clear some obstacles on the way.~
    == If you are interested in Spring, Spring Boot, MyBatis and other source code analysis, you can add me wx: fsx641385712, invite you to join the group and fly together manually.==

Posted by scampbell on Mon, 02 Sep 2019 00:49:46 -0700