Bootstrap

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

ModelFactory 组件分析

ModelFactory 是用来维护Model的,包含两个功能:

1,初始化 Model

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

ModelFactory#initModel

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

  // 从SessionAttributes中取出保存的参数,合并到mavContainer中
  Map 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);
    }
  }
}

主要做了三件事:

如果不在mavContainer中,使用 sessionAttributesHandler 从 SessionAttributes 中获取,并添加到 mavContainer 中;

invokeModelAttributeMethods

invokeModelAttributeMethodsinvokeModelAttributeMethods 方法:执行了含有 @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 findSessionAttributeArguments(HandlerMethod handlerMethod) {
  List result = new ArrayList();
  // 遍历方法中的所有参数
  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;
}

getNameForParameter

获取参数名方法: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注解的方法是全局的优先,处理器自己定义的其次

2,更新 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 keyNames = new ArrayList(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

3,结尾

ModelFactory 组件就说完了。下面说 ServletInvocableHandlerMethod;