写点什么

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

用户头像
Brave
关注
发布于: 刚刚

ModelFactory 组件分析


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

  1. 初始化 Model;

  2. 更新 Model,处理器执行后将 Model 中的参数更新到 SessionAttributes 中;



1,初始化 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

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<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

获取参数名方法: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中的参数设置到SessionAttributes2,如果需要渲染视图,给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
复制代码



3,结尾


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


用户头像

Brave

关注

还未添加个人签名 2018.12.13 加入

还未添加个人简介

评论

发布
暂无评论
SpringMVC源码分析-HandlerAdapter(6)-ModelFactory组件分析