ModelFactory component analysis

ModelFactory is used to maintain the Model. It contains two functions: 1. Initialize Model 2, update Model, and update parameters in Model to SessionAttributes after the processor executesCopy the code

Initialize the Model

Initialize Model: Sets the data into Model before processor execution, which is done through the initModel methodCopy the code

ModelFactory#initModel

public void initModel(NativeWebRequest request, ModelAndViewContainer container, Throws Exception {// Take the saved parameters from SessionAttributes and merge them into mavContainer Map<String,? > sessionAttributes = this.sessionAttributesHandler.retrieveAttributes(request); container.mergeAttributes(sessionAttributes); / / execution at the @ ModelAttribute method and set the result to the Model invokeModelAttributeMethods (request, the container); // Iterate over parameters in both @modelAttribute and @sessionAttributes, adding mavContainer for (String name: findSessionAttributeArguments(handlerMethod)) { if (! container.containsAttribute(name)) { Object value = this.sessionAttributesHandler.retrieveAttribute(request, name); if (value == null) { throw new HttpSessionRequiredException("Expected session attribute '" + name + "'", name); } container.addAttribute(name, value); }}}Copy the code
Three main things have been done: 1, take the saved parameters from SessionAttributes, merge them into mavContainer 2, execute the annotated @ModelAttribute method and set the result to Model 3, iterate over the @modelAttribute and @sessionAttributes parameters (parameter name or type set) If not in the mavContainer, use the sessionAttributesHandler to get it from the SessionAttributes and add it to the mavContainerCopy the code

invokeModelAttributeMethods

InvokeModelAttributeMethods method

A method with the @ModelAttribute annotation is executed to set the result to ModelCopy the code

InvokeModelAttributeMethods source code:

private void invokeModelAttributeMethods(NativeWebRequest request, ModelAndViewContainer container) throws Exception { while (! This. ModelMethods. IsEmpty ()) {/ / @ ModelAttribute techniques have been used for obtain InvocableHandlerMethod modelMethod = getNextModelMethod(container).getHandlerMethod(); / / @ ModelAttribute annotation information ModelAttribute Ann. = modelMethod getMethodAnnotation (ModelAttribute. Class); / / if the parameter name is included in the container, skip the if (container. ContainsAttribute (Ann. The name ())) {if (! ann.binding()) { container.setBindingDisabled(ann.name()); } continue; } / / container does not contain the parameter name, executing the method Object. ReturnValue = modelMethod invokeForRequest (request, the container); // If it is void, the method itself sets the parameter to Model and does not handle // If it is not void, use getNameForReturnValue to get the parameter name. If container does not exist, add if (! Modelmethod.isvoid ()){String returnValueName = getNameForReturnValue(returnValue, modelMethod.getReturnType()); if (! ann.binding()) { container.setBindingDisabled(returnValueName); } // If no container exists, add if (! container.containsAttribute(returnValueName)) { container.addAttribute(returnValueName, returnValue); }}}}Copy the code
If the parameter name exists in mavContainer, skip this method. Otherwise, the return value of Pandan is Void after executing the method If void, the method sets its own parameters to the Model and does not process them. If void is not used, use the getNameForReturnValue method to get the parameter name. If container does not exist, add itCopy the code

getNameForReturnValue

public static String getNameForReturnValue(Object returnValue, MethodParameter returnType) {// Get the @modelAttribute attribute information ModelAttribute Ann = returnType.getMethodAnnotation(ModelAttribute.class); // If there is value, return if (Ann! = null && StringUtils.hasText(ann.value())) { return ann.value(); } / / if there is no value, the use of Conventions. GetVariableNameForReturnType according to the Method, the return value type, the return value, in order to get the else {/ / Method Method = returnType.getMethod(); // The method belongs to Class<? > containingClass = returnType.getContainingClass(); // Return value type Class<? > resolvedType = GenericTypeResolver.resolveReturnType(method, containingClass); / / according to the method, the return value type, the return value, parameter name lookup returns return Conventions. GetVariableNameForReturnType (method, resolvedType returnValue); }}Copy the code

Conventions#getVariableNameForReturnType

public static String getVariableNameForReturnType(Method method, Class<? > resolvedType, Object value) { Assert.notNull(method, "Method must not be null"); If (object.class == resolvedType) {if (value == null) {throw new IllegalArgumentException("Cannot generate variable name for an Object return type with null value"); } return getVariableName(value); } Class<? > valueClass; boolean pluralize = false; If (resolvedType.isarray ()) {valueClass = resolvedType.getComponentType(); pluralize = true; } else if (Collection.class.isAssignableFrom(resolvedType)) { valueClass = ResolvableType.forMethodReturnType(method).asCollection().resolveGeneric(); if (valueClass == null) { if (! (value instanceof Collection)) { throw new IllegalArgumentException( "Cannot generate variable name for non-typed Collection return type and a non-Collection value"); } Collection<? > collection = (Collection<? >) value; if (collection.isEmpty()) { throw new IllegalArgumentException( "Cannot generate variable name for non-typed Collection return type and an empty Collection value"); } Object valueToCheck = peekAhead(collection); valueClass = getClassForValue(valueToCheck); } pluralize = true; } else { valueClass = resolvedType; } String name = ClassUtils.getShortNameAsProperty(valueClass); return (pluralize ? pluralize(name) : name); } // Obtain the return value type ShortName // 1, obtain the name of the class that was removed from the list // 2, check whether the class name is greater than 1 character, and whether the first two characters are uppercase // If so, return public static String, if not, lower one character to lowercase getShortNameAsProperty(Class<? > clazz) { String shortName = getShortName(clazz); int dotIndex = shortName.lastIndexOf(PACKAGE_SEPARATOR); shortName = (dotIndex ! = 1? shortName.substring(dotIndex + 1) : shortName); return Introspector.decapitalize(shortName); } private static final String PLURAL_SUFFIX = "List"; private static String pluralize(String name) { return name + PLURAL_SUFFIX; }Copy the code

findSessionAttributeArguments

findSessionAttributeArguments

Gets a parameter that has both an @ModelAttribute annotation and an @sessionAttributes annotationCopy the code

FindSessionAttributeArguments source code:

private List<String> findSessionAttributeArguments(HandlerMethod handlerMethod) { List<String> result = new ArrayList<String>(); For (MethodParameter parameter: HandlerMethod. GetMethodParameters ()) {/ / if you have @ ModelAttribute annotate the if (parameter. HasParameterAnnotation (ModelAttribute. Class)) {/ / parameter name and parameter types String name = getNameForParameter (parameter); Class<? > paramType = parameter.getParameterType(); / / depending on the type of access to the parameter name and parameter check whether the parameter is in @ SessionAttributes comments if (this. SessionAttributesHandler. IsHandlerSessionAttribute (name, ParamType)) {// If the @sessionAttributes annotation is a valid parameter, put the parameter name into the collection result.add(name); } } } return result; }Copy the code
If @modelAttribute exists, get the parameter name and parameter type. If @modelAttribute exists, get the parameter name and parameter type. And based on the parameter name and parameter type, check if the parameter is in the @@sessionAttributes annotation. If it is in the @sessionAttributes annotation, that's the parameter that meets the requirements, put the parameter name in the collectionCopy the code

Method for obtaining the parameter name :getNameForParameter

public static String getNameForParameter(MethodParameter parameter) { ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class); String name = (ann ! = null ? ann.value() : null); return (StringUtils.hasText(name) ? name : Conventions.getVariableNameForParameter(parameter)); }Copy the code
Get the parameter name: If there is an @modelAttribute annotation, find the value. If there is no @modelAttribute annotation or the annotation has no value, the logic is the same as in step 2Copy the code

The difference between step 3 and Step 1:

The first step is to merge all SessionAttributes stored in the current handler into mavContainer, It then executes the @ModelAttribute annotated method, merges the returned result into mavContainer, and finally checks whether the @ModelAttribute annotated parameter, which is also set in @sessionAttributes, is in mavContainer If not in mavContainer, get from this SessionAttributes and set it to mavContainer. If not, throw an exceptionCopy the code

Priority of Model parameters:

From the source can be seen: 1, parameters saved in FlashMap have the highest priority, so perform 2 before ModelFactory. Parameters saved in SessionAttributes have the second priority and cannot overwrite parameters set in FlashMap Methods with @modelAttribute attributes have the third highest priority for parameters set. 4. Parameters with @ModelAttribute attributes obtained from other processors' SessionAttributes have the lowest priority From the process of creating a ModelFactory, the @ModelAttribute annotation method is global first, and the processor-defined method is secondCopy the code

Update the Model

From our previous analysis, we know that updating the Model is done by calling the ModelFactory#updateModel method

ModelFactory#updateModel

public void updateModel(NativeWebRequest request, ModelAndViewContainer Container) throws Exception {// Obtain defaultModel ModelMap defaultModel = container.getDefaultModel(); / / if the processor calls SessionStatus# setComplete, empty SessionAttributes if (container. GetSessionStatus () isComplete ()) { this.sessionAttributesHandler.cleanupAttributes(request); } / / will mavContainer defaultModel parameter is set to SessionAttributes else {this. SessionAttributesHandler. StoreAttributes (request, defaultModel); } // If the request is not completed and the Model type is defaultModel, set BindingResult if (! container.isRequestHandled() && container.getModel() == defaultModel) { updateBindingResult(request, defaultModel); }}Copy the code

UpdateModel does two things:

If the handler calls SessionStatus#setComplete, empty the SessionAttributes Otherwise, set the parameters in defaultModel of mavContainer to SessionAttributes 2. If you need to render the view, set BindingResult to the Model parameter BindingResult = BindingResult = BindingResult = BindingResultCopy the code

updateBindingResult

private void updateBindingResult(NativeWebRequest request, ModelMap model) throws Exception { List<String> keyNames = new ArrayList<String>(model.keySet()); for (String name : keyNames) { Object value = model.get(name); // Determine whether to add BindingResult if (isBindingCandidate(name, value)) { String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + name; // If there is no bindingResult if (! Model.containsattribute (bindingResultKey)) {// Create WebDataBinder WebDataBinder dataBinder = with dataBinderFactory this.dataBinderFactory.createBinder(request, value, name); / / added to the model model. The put (bindingResultKey, dataBinder getBindingResult ()); }}}}Copy the code
The isBindingCandidate method is used to determine whether BindingResult needs to be added. If BindingResult needs to be added and does not exist in the Model, Use WebDatabinder to get the BindingResult and add it to the ModelCopy the code

isBindingCandidate

String MODEL_KEY_PREFIX = BindingResult.class.getName() + "."; private boolean isBindingCandidate(String attributeName, Object value) {// Determine the prefix, if it starts with BindingResult, BindingResult if is the result of the binding of other parameters (attributeName.startsWith(BindingResult.MODEL_KEY_PREFIX)) { return false; } Class<? > attrType = (value ! = null ? value.getClass() : null); / / judge whether sessionAttributes management properties, if it is return true if (this. SessionAttributesHandler. IsHandlerSessionAttribute (attributeName, attrType)) { return true; } // Check if there is no null value, array,Collection,Map, simple type, all return true (value! = null && ! value.getClass().isArray() && ! (value instanceof Collection) && ! (value instanceof Map) && ! BeanUtils.isSimpleValueType(value.getClass())); }Copy the code
If BindingResult is the result of binding other parameters, return false. Do not add BindingResult to check if it is a sessionAttributes attribute. If it returns true, add BindingResult. If it is not null, array,Collection,Map, simple all return true, add BindingResult. Non-bindingresult, null, array,Collection,Map, and simple types all return true if they are (except BindingResult), but are set in the @sessionAttributes attribute and return true otherwiseCopy the code

ServletInvocableHandlerMethod ModelFactory components had finished, the following saidCopy the code