SpringMVC源码分析-HandlerAdapter(6)-ModelFactory组件分析

ModelFactory组件分析

ModelFactory是用来维护Model的,包含两个功能:
1,初始化Model
2,更新Model,处理器执行后将Model中的参数更新到SessionAttributes中

初始化Model

初始化Model:
    在处理器执行前将数据设置到Model中,通过initModel方法完成

ModelFactory#initModel

public void initModel(NativeWebRequest request, ModelAndViewContainer container,
        HandlerMethod handlerMethod) throws Exception {

    // 从SessionAttributes中取出保存的参数,合并到mavContainer中
    Map<String, ?> sessionAttributes = this.sessionAttributesHandler.retrieveAttributes(request);
    container.mergeAttributes(sessionAttributes);

    // 执行注视了@ModelAttribute的方法并将结果设置到Model中
    invokeModelAttributeMethods(request, container);

    // 遍历既有@ModelAttribute注解又在@SessionAttributes注解中的参数,加入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);
        }
    }
}
主要做了三件事:
1,从SessionAttributes中取出保存的参数,合并到mavContainer
2,执行注释了@ModelAttribute的方法并将结果设置到Model
3,遍历有@ModelAttribute注解又在@SessionAttributes注解中的参数(参数名或类型被设置)
如果不在mavContainer中,使用sessionAttributesHandler从SessionAttributes中获取
并添加到mavContainer中

invokeModelAttributeMethods

invokeModelAttributeMethods方法

执行了含有@ModelAttribute注解的方法,将结果设置到Model

invokeModelAttributeMethods源码:

private void invokeModelAttributeMethods(NativeWebRequest request, ModelAndViewContainer container)
        throws Exception {

    while (!this.modelMethods.isEmpty()) {
        // 获取有@ModelAttribute的方法
        InvocableHandlerMethod modelMethod = getNextModelMethod(container).getHandlerMethod();
        // 获取@ModelAttribute注解信息
        ModelAttribute ann = modelMethod.getMethodAnnotation(ModelAttribute.class);
        // 如果container已包含参数名,跳过
        if (container.containsAttribute(ann.name())) {
            if (!ann.binding()) {
                container.setBindingDisabled(ann.name());
            }
            continue;
        }

        // container不包含参数名,执行方法
        Object returnValue = modelMethod.invokeForRequest(request, container);
        // 判断返回值是否为void类型
        // 如果是void,方法自己将参数设置到Model,不处理
        // 如果不是void,使用getNameForReturnValue获取参数名,如果不存在container,添加进去
        if (!modelMethod.isVoid()){
            // 获取方法返回参数名
            String returnValueName = getNameForReturnValue(returnValue, modelMethod.getReturnType());
            if (!ann.binding()) {
                container.setBindingDisabled(returnValueName);
            }
            // 如果不存在container,添加进去
            if (!container.containsAttribute(returnValueName)) {
                container.addAttribute(returnValueName, returnValue);
            }
        }
    }
}
遍历了每个注释了@ModelAttribute的方法,拿到注释信息
如果参数名医存在mavContainer中,跳过此方法,否则执行

执行方法后潘丹返回值是否为Void类型
如果是void,说明这个方法是自己将参数设置到Model中的,不再处理
如果不是void,使用getNameForReturnValue方法获取参数名,如果不存在container,添加进去

getNameForReturnValue

public static String getNameForReturnValue(Object returnValue, MethodParameter returnType) {
    // 获取@ModelAttribute注解信息
    ModelAttribute ann = returnType.getMethodAnnotation(ModelAttribute.class);
    // 如果有value,直接返回
    if (ann != null && StringUtils.hasText(ann.value())) {
        return ann.value();
    }
    // 如果没有value,使用Conventions.getVariableNameForReturnType根据方法,返回值类型,返回值,来获取
    else {
        // 方法
        Method method = returnType.getMethod();
        // 方法所属类
        Class<?> containingClass = returnType.getContainingClass();
        // 返回值类型
        Class<?> resolvedType = GenericTypeResolver.resolveReturnType(method, containingClass);
        // 根据方法,返回值类型,返回值,查找返回参数名
        return Conventions.getVariableNameForReturnType(method, resolvedType, returnValue);
    }
}

Conventions#getVariableNameForReturnType

public static String getVariableNameForReturnType(Method method, Class<?> resolvedType, Object value) {
    Assert.notNull(method, "Method must not be null");

    // 如果返回值类型是Object,返回实际类型
    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;
    // 返回值是数组或Collection会使用内部实际包装的类型,并在最后添加"List"
    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);
}
// 获取返回值类型ShortName
// 1,先获取去掉报名的类名
// 2,判断类名是否大于1个字符,前两个字符是否都是大写
//   如果是,直接返回,如果不是,降低一个字母变为小写返回
public static String 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;
}

findSessionAttributeArguments

findSessionAttributeArguments

获取同时有@ModelAttribute注解又在@SessionAttributes注解中的参数

findSessionAttributeArguments源码:

private List<String> findSessionAttributeArguments(HandlerMethod handlerMethod) {
    List<String> result = new ArrayList<String>();
    // 遍历方法中的所有参数
    for (MethodParameter parameter : handlerMethod.getMethodParameters()) {
        // 如果有@ModelAttribute注解
        if (parameter.hasParameterAnnotation(ModelAttribute.class)) {
            // 获取参数名和参数类型
            String name = getNameForParameter(parameter);
            Class<?> paramType = parameter.getParameterType();
            // 根据获取到的参数名和参数类型检查参数是否在@SessionAttributes注释中
            if (this.sessionAttributesHandler.isHandlerSessionAttribute(name, paramType)) {
                // 如果在@SessionAttributes注解中,即为符合要求的参数,将参数名放入集合
                result.add(name);
            }
        }
    }
    return result;
}
1,遍历方法中的每一个参数
2,判断是否有@ModelAttribute注解
3,如果有@ModelAttribute注解,获取参数名和参数类型,
并根据参数名和参数类型,检查参数是否在@@SessionAttributes注解中
如果在@SessionAttributes注解中,即为符合要求的参数,将参数名放入集合

获取参数名方法: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));
}
获取参数名:
如果有@ModelAttribute注解,找注解value
如果没有@ModelAttribute注解或注解没有value,和第二步执行方法,获取参数名逻辑相同

第三步和第一步的区别:

第一步将当前处理器中保存的所有SessionAttributes属性合并到mavContainer中,
然后执行有@ModelAttribute注解的方法,并将返回结果合并到mavContainer中
最后检查有@ModelAttribute注解且在@SessionAttributes中也设置的参数,是否在mavContainer中
如果不在mavContainer中,则从这个SessionAttributes中获取并设置到mavContainer中
如果获取不到则抛出异常

Model参数的优先级:

从源码可以看出:
1,FlashMap中保存的参数优先级最高,在ModelFactory前执行
2,SessionAttributes中保存的参数优先级第二,不能覆盖FlashMap中设置的参数
3,拥有@ModelAttribute注解的方法设置的参数优先级第三
4,拥有@ModelAttribute注解且从别的处理器的SessionAttributes获取到的参数优先级最低

从创建ModelFactory的过程看,@ModelAttribute注解的方法是全局的优先,处理器自己定义的其次

更新Model

从之前的分析,我们知道更新Model是通过调用ModelFactory#updateModel方法完成的

ModelFactory#updateModel

public void updateModel(NativeWebRequest request, ModelAndViewContainer container) throws Exception {

    // 获取defaultModel
    ModelMap defaultModel = container.getDefaultModel();
    // 如果处理器调用了SessionStatus#setComplete,清空SessionAttributes
    if (container.getSessionStatus().isComplete()){
        this.sessionAttributesHandler.cleanupAttributes(request);
    }
    // 将mavContainer的defaultModel中的参数设置到SessionAttributes
    else {
        this.sessionAttributesHandler.storeAttributes(request, defaultModel);
    }

    // 如果请求未处理完成,且Model类型为defaultModel,给Model参数设置BindingResult
    if (!container.isRequestHandled() && container.getModel() == defaultModel) {
        updateBindingResult(request, defaultModel);
    }
}

updateModel做了两件事:

1,设置SessionAttributes
    如果处理器调用了SessionStatus#setComplete,则清空SessionAttributes
    否则将mavContainer的defaultModel中的参数设置到SessionAttributes
2,如果需要渲染视图,给Model参数设置BindingResult
  也可以说是给Model中需要的参数设置BindingResult,给视图渲染备用

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);
        // 判断是否需要添加BindingResult
        if (isBindingCandidate(name, value)) {
            String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + name;
            // 如果model中不存在bindingResult
            if (!model.containsAttribute(bindingResultKey)) {
                // 通过dataBinderFactory创建WebDataBinder
                WebDataBinder dataBinder = this.dataBinderFactory.createBinder(request, value, name);
                // 添加到model
                model.put(bindingResultKey, dataBinder.getBindingResult());
            }
        }
    }
}
遍历Model中板寸的所有参数
通过isBindingCandidate方法判断是否需要添加BindingResult
如果需要添加且model中不存在bindingResult,
使用WebDatabinder获取BindingResult并添加到Model

isBindingCandidate

String MODEL_KEY_PREFIX = BindingResult.class.getName() + ".";

private boolean isBindingCandidate(String attributeName, Object value) {
    // 判断前缀,如果是BindingResult开头,说明是其他参数绑定结果的BindingResult
    if (attributeName.startsWith(BindingResult.MODEL_KEY_PREFIX)) {
        return false;
    }

    Class<?> attrType = (value != null ? value.getClass() : null);
    // 判断是否是sessionAttributes管理的属性,如果是返回true
    if (this.sessionAttributesHandler.isHandlerSessionAttribute(attributeName, attrType)) {
        return true;
    }

    // 检查如果不是空值,数组,Collection,Map,简单类型,都返回true
    return (value != null && !value.getClass().isArray() && !(value instanceof Collection) &&
            !(value instanceof Map) && !BeanUtils.isSimpleValueType(value.getClass()));
}
先判断是否是其他参数绑定结果的BindingResult,
如果是则返回false,不需要添加BindingResult

判断是否是sessionAttributes管理的属性,
如果是返回true,需要添加BindingResult

判断如果不是空值,数组,Collection,Map,简单类型的其他类型
都返回true,需要添加BindingResult

综上:
不是BindingResult,空值,数组,Collection,Map,简单类型都返回true
如果是这些类型(BindingResult除外),但是在@sessionAttributes中设置了,也返回true
其他情况返回false

ModelFactory组件就说完了,下面说ServletInvocableHandlerMethod

猜你喜欢

转载自blog.csdn.net/abap_brave/article/details/80961527