• SpringMVC源码解析- HandlerAdapter


    ModelFactory主要是两个职责:

      1. 初始化model

      2. 处理器执行后将modle中相应参数设置到SessionAttributes中

    我们来看看具体的处理逻辑(直接充当分析目录):

    1. 初始化model

      1.1 解析类上使用的sessionAttributres,将获取参数合并到mavContainer中

      1.2 执行注解了@ModelAttribute的方法,并将结果同步到Model

        参数名的生成规则:@ModelAttribute中定义的value > 方法的返回类型决定(直接往model.addAttribute的除外)

      1.3 将注解@ModelAttribute方法参数(在@SessionAttributes定义范围内)同步到model中

         将方法中使用@ModelAttribute的参数跟@SessionAttribute核对,如果都定义了,需要将其参数值同步至mavContainer

    2. 处理器执行后将modle中相应参数设置到SessionAttributes中

      2.1 如果SessionStatus被调用了setComplete则清除sesssionAttributesHandler中缓存的数据

      2.2 如果没清除,将model中的数据同步至sessionAttributesHandler中

      2.3 如果handler还没处理完(是否需要渲染页面),绑定BindingResult到model(如果需要的话)

      上面的代码说明在日常开发时,SessionStatus.setComplete写在方法哪个位置都行,因为他是在方法执行后才在这边调用,跟方法中的顺序无关.

     1. 初始化model

    做了三个事情,详细见源码中的注释吧:

     1 package org.springframework.web.method.annotation;
     2 public final class ModelFactory {
     3     public void initModel(NativeWebRequest request, ModelAndViewContainer mavContainer, HandlerMethod handlerMethod)
     4             throws Exception {
     5     // 获取使用@SessionAttributes注解并已经解析的参数,合并到mavContainer
     6         Map<String, ?> attributesInSession = this.sessionAttributesHandler.retrieveAttributes(request);
     7         mavContainer.mergeAttributes(attributesInSession);
     8     // 执行使用@ModelAttribute注解的方法,并将结果设置到mavContainer
     9         invokeModelAttributeMethods(request, mavContainer);
    10     // 将同时使用@ModelAttribute和@SessionAttributes注解的参数设置到mavContainer
    11         for (String name : findSessionAttributeArguments(handlerMethod)) {
    12             if (!mavContainer.containsAttribute(name)) {
    13                 Object value = this.sessionAttributesHandler.retrieveAttribute(request, name);
    14                 if (value == null) {
    15                     throw new HttpSessionRequiredException("Expected session attribute '" + name + "'");
    16                 }
    17                 mavContainer.addAttribute(name, value);
    18             }
    19         }
    20     }
    21     // ...
    22 }

    1.1 解析类上使用的sessionAttributres,将获取参数合并到mavContainer中

    这部分,之前的<SpringMVC源码解析 - HandlerAdapter - @SessionAttributes注解处理>已经讲述得很细,这边就不展开.

    1.2 执行注解了@ModelAttribute的方法,并将结果同步到Model

      迭代所有使用@ModelAttribute注解的方法

      获取@ModelAttribute中的value属性值作为 model attribute,如果mavContainer中已经存在则退出

      委托InvocableHandlerMethod的invokeForRequest生成属性值.

        a,获取当前方法的调用参数

        b,直接执行invoke,并返回结果

      如果方法不是void的,则需要将值同步到mavContainer

        a,如果方法是void,则说明用户直接将参数通过model.addAttribute设置好值了

        b,参数名的生成规则:@ModelAttribute中定义的value > 方法的返回类型决定

        根据方法的返回类型决定参数名时,大致的规则如下:

          String -> string(这边就解释我之前没搞明白使用@ModelAttribute注解实例的最后一个情况)

          List<Double> -> doubleList

        c,如果mavContainer中还没有这个参数值,则同步进去

     1 package org.springframework.web.method.annotation;
     2 public final class ModelFactory {
     3     private void invokeModelAttributeMethods(NativeWebRequest request, ModelAndViewContainer mavContainer)
     4             throws Exception {
     5         // 迭代使用@ModelAttribute注解的方法
     6         for (InvocableHandlerMethod attrMethod : this.attributeMethods) {
     7             // 使用@ModelAttribute的value值作为 attribute name
     8             String modelName = attrMethod.getMethodAnnotation(ModelAttribute.class).value();
     9             if (mavContainer.containsAttribute(modelName)) {
    10                 continue;
    11             }
    12             // 委托InvocableHandlerMethod调用方法,生成值
    13             Object returnValue = attrMethod.invokeForRequest(request, mavContainer);
    14             // 如果方法返回值,需要将这个值同步到mavContainer中
    15             if (!attrMethod.isVoid()){
    16                 // 生成参数名:注解的value或者返回值类型
    17                 String returnValueName = getNameForReturnValue(returnValue, attrMethod.getReturnType());
    18                 if (!mavContainer.containsAttribute(returnValueName)) {
    19                     mavContainer.addAttribute(returnValueName, returnValue);
    20                 }
    21             }
    22         }
    23     }
    24     // ...
    25 }

    看看InvocableHandlerMethod的invokeForRequest(NativeWebRequest request,ModelAndViewContainer mavContainer,Object... providedArgs)

      这边涉及到两个封装类:InvocableHandlerMethod和MethodParameter.

      InvocableHandlerMethod封装一个可执行的方法,在HandlerMethod基础上添加方法参数解析的职责.

      MethodParameter封装方法定义相关的概念

    具体的处理逻辑还是看代码中的注释吧.

     1 package org.springframework.web.method.support;
     2 public class InvocableHandlerMethod extends HandlerMethod {
     3     public final Object invokeForRequest(NativeWebRequest request,
     4                                          ModelAndViewContainer mavContainer,
     5                                          Object... providedArgs) throws Exception {
     6         // 生成方法调用时的参数
     7         Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
     8         // 霸气的调用
     9         Object returnValue = invoke(args);
    10 
    11         return returnValue;
    12     }
    13     private Object[] getMethodArgumentValues(
    14             NativeWebRequest request, ModelAndViewContainer mavContainer,
    15             Object... providedArgs) throws Exception {
    16         // 获取参数,这边没有值
    17         MethodParameter[] parameters = getMethodParameters();
    18         Object[] args = new Object[parameters.length];
    19         for (int i = 0; i < parameters.length; i++) {
    20             MethodParameter parameter = parameters[i];
    21             // 参数名称查找器,反射中拿不到参数名,所以使用spring的parameterNameDiscover
    22             parameter.initParameterNameDiscovery(parameterNameDiscoverer);
    23             // 获取参数的目标类型,methodParam.setParameterType(result);设置.这边具体的逻辑后面再细化
    24             GenericTypeResolver.resolveParameterType(parameter, getBean().getClass());
    25             // 尝试通过类型判断,获取参数的值
    26             args[i] = resolveProvidedArgument(parameter, providedArgs);
    27             if (args[i] != null) {
    28                 continue;
    29             }
    30             // 使用HandlerMethodArgumentResolver,判断是否支持处理
    31             if (argumentResolvers.supportsParameter(parameter)) {
    32                 try {
    33                     // 这边直接处理,实际执行时,是通过责任链设计模式处理
    34                     args[i] = argumentResolvers.resolveArgument(parameter, mavContainer, request, dataBinderFactory);
    35                     continue;
    36                 } catch (Exception ex) {
    37                     throw ex;
    38                 }
    39             }
    40 
    41             if (args[i] == null) {
    42                 String msg = getArgumentResolutionErrorMessage("No suitable resolver for argument", i);
    43                 throw new IllegalStateException(msg);
    44             }
    45         }
    46         return args;
    47     }
    48     private Object invoke(Object... args) throws Exception {
    49         // 解决权限的问题
    50         ReflectionUtils.makeAccessible(this.getBridgedMethod());
    51         try {
    52             return getBridgedMethod().invoke(getBean(), args);
    53         }
    54         catch (IllegalArgumentException | InvocationTargetExceptione) {
    55             // 省略异常处理机制
    56         }
    57     }
    58     // ...
    59 }

    我们再来看看参数名称的生成规则吧:

      如果@ModelAttribute中定义了value,就以value命名

      如果注解中没有定义value,则根据返回值类型定义名称

      如:String会被定义为string,List<Double>会被定义为doubleList(集合都是这样定义的,包括array数组)

     1 package org.springframework.web.method.annotation;
     2 public final class ModelFactory {
     3 
     4     public static String getNameForReturnValue(Object returnValue, MethodParameter returnType) {
     5         ModelAttribute annot = returnType.getMethodAnnotation(ModelAttribute.class);
     6         if (annot != null && StringUtils.hasText(annot.value())) { // 注解中定义了value
     7             return annot.value();
     8         }
     9         else { // 根据类型生成
    10             Method method = returnType.getMethod();
    11             Class<?> resolvedType = GenericTypeResolver.resolveReturnType(method, returnType.getDeclaringClass());
    12             return Conventions.getVariableNameForReturnType(method, resolvedType, returnValue);
    13         }
    14     }
    15     // ...
    16 }

    接下来是如何根据返回值类型生成参数名称的逻辑,挺有意思,重点展开:

    这边又根据方法的signature中定义的参数类型是否细化再衍生一个分支:

      如果方法签名中只定义Object类型,则需要根据value生成;否则根据签名生成

     1 package org.springframework.core;
     2 public abstract class Conventions {
     3     public static String getVariableNameForReturnType(Method method, Class resolvedType, Object value) {
     4         // 如果signature定义为object,则根据value来判断
     5         if (Object.class.equals(resolvedType)) {
     6             if (value == null) {
     7                 throw new IllegalArgumentException("Cannot generate variable name for an Object return type with null value");
     8             }
     9             // 这边的处理逻辑跟下面的很类似,不展开.差别是一个根据value,一个根据resolvedType判断
    10             return getVariableName(value);
    11         }
    12 
    13         Class valueClass;
    14         // 是否是数组或集合
    15         boolean pluralize = false;
    16 
    17         if (resolvedType.isArray()) { // 数组,读取内部元素的类型
    18             valueClass = resolvedType.getComponentType();
    19             pluralize = true;
    20         }
    21         else if (Collection.class.isAssignableFrom(resolvedType)) { // 集合
    22             // 集合内的元素类型
    23             valueClass = GenericCollectionTypeResolver.getCollectionReturnType(method);
    24             if (valueClass == null) {
    25                 if (!(value instanceof Collection)) {// 跟value再校验一遍类型
    26                     throw new IllegalArgumentException(
    27                             "Cannot generate variable name for non-typed Collection return type and a non-Collection value");
    28                 }
    29                 Collection collection = (Collection) value;
    30                 if (collection.isEmpty()) {
    31                     throw new IllegalArgumentException(
    32                             "Cannot generate variable name for non-typed Collection return type and an empty Collection value");
    33                 }
    34                 // 获取集合中的第一个value
    35                 Object valueToCheck = peekAhead(collection);
    36                 // 获取value的类系
    37                 valueClass = getClassForValue(valueToCheck);
    38             }
    39             pluralize = true;
    40         }
    41         else {
    42             valueClass = resolvedType;
    43         }
    44 
    45         String name = ClassUtils.getShortNameAsProperty(valueClass);
    46         return (pluralize ? pluralize(name) : name);
    47     }
    48     // 获取集合中的第一个value
    49     private static Object peekAhead(Collection collection) {
    50         Iterator it = collection.iterator();
    51         if (!it.hasNext()) {
    52             throw new IllegalStateException(
    53                     "Unable to peek ahead in non-empty collection - no element found");
    54         }
    55         Object value = it.next();
    56         if (value == null) {
    57             throw new IllegalStateException(
    58                     "Unable to peek ahead in non-empty collection - only null element found");
    59         }
    60         return value;
    61     }
    62     private static Class getClassForValue(Object value) {
    63         Class valueClass = value.getClass();
    64         // 代理时根据接口获取,遍历时以第一个符合条件的为准
    65         if (Proxy.isProxyClass(valueClass)) {
    66             Class[] ifcs = valueClass.getInterfaces();
    67             for (Class ifc : ifcs) {
    68                 if (!ignoredInterfaces.contains(ifc)) {
    69                     return ifc;
    70                 }
    71             }
    72         }
    73         else if (valueClass.getName().lastIndexOf('$') != -1 && valueClass.getDeclaringClass() == null) {
    74             // '$' in the class name but no inner class -
    75             // assuming it's a special subclass (e.g. by OpenJPA)
    76             valueClass = valueClass.getSuperclass();
    77         }
    78         return valueClass;
    79     }
    80     // 数组或结合统一添加后缀List
    81     private static String pluralize(String name) {
    82         //private static final String PLURAL_SUFFIX = "List";
    83         return name + PLURAL_SUFFIX;
    84     }
    85 
    86 }

    1.3 将注解@ModelAttribute方法参数(在@SessionAttributes定义范围内)同步到model中

    遍历HandlerMethod的所有参数,找出使用了@ModelAttribute注解的参数

      获取参数的名称:注解value值 > 参数类型

      核对这个参数名称是否在@SessionAttributes注解内

      如果mavContainer中还没有该参数,继续处理

      获取缓存在sessionAttributesHandler中的参数值

      如果值为空,抛HttpSessionRequiredException

      否则同步到mavContainer中

     1 package org.springframework.web.method.annotation;
     2 public final class ModelFactory {
     3     // ...
     4     public void initModel(NativeWebRequest request, ModelAndViewContainer mavContainer, HandlerMethod handlerMethod)
     5             throws Exception {
     6         // ...
     7         for (String name : findSessionAttributeArguments(handlerMethod)) {
     8             if (!mavContainer.containsAttribute(name)) {
     9                 Object value = this.sessionAttributesHandler.retrieveAttribute(request, name);
    10                 if (value == null) {
    11                     throw new HttpSessionRequiredException("Expected session attribute '" + name + "'");
    12                 }
    13                 mavContainer.addAttribute(name, value);
    14             }
    15         }
    16     }
    17     private List<String> findSessionAttributeArguments(HandlerMethod handlerMethod) {
    18         List<String> result = new ArrayList<String>();
    19         // 这边找的是HandlerMethod的参数
    20         for (MethodParameter param : handlerMethod.getMethodParameters()) {
    21             if (param.hasParameterAnnotation(ModelAttribute.class)) {
    22                 String name = getNameForParameter(param);
    23                 if (this.sessionAttributesHandler.isHandlerSessionAttribute(name, param.getParameterType())) {
    24                     result.add(name);
    25                 }
    26             }
    27         }
    28         return result;
    29     }
    30     public static String getNameForParameter(MethodParameter parameter) {
    31         ModelAttribute annot = parameter.getParameterAnnotation(ModelAttribute.class);
    32         String attrName = (annot != null) ? annot.value() : null;
    33         // 如果value为空,获取参数类型解析属性名称
    34         return StringUtils.hasText(attrName) ? attrName :  Conventions.getVariableNameForParameter(parameter);
    35     }
    36 }

    2. 处理器执行后将modle中相应参数设置到SessionAttributes中

      2.1 如果SessionStatus被调用了setComplete则清除sesssionAttributesHandler中缓存的数据

      2.2 如果没清除,将model中的数据同步至sessionAttributesHandler中

      2.3 如果handler还没处理完(是否需要渲染页面),绑定BindingResult到model(如果需要的话)

    还需要补充说明的是:

    判断绑定BindingResult到model时的条件(满足任意):

      a,不是其他参数绑定结果的Bindingresult

      b,@SessionAttributes注解定义范围内

      c, 不是null,数组,集合,map,简单数据类型

    剩下的看代码注释就行了

     1 package org.springframework.web.method.annotation;
     2 public final class ModelFactory {
     3     // ...
     4     public void updateModel(NativeWebRequest request, ModelAndViewContainer mavContainer) throws Exception {
     5         if (mavContainer.getSessionStatus().isComplete()){ // 清除
     6             this.sessionAttributesHandler.cleanupAttributes(request);
     7         }
     8         else { // 不清除,那么就需要同步
     9             this.sessionAttributesHandler.storeAttributes(request, mavContainer.getModel());
    10         }
    11 
    12         if (!mavContainer.isRequestHandled()) {
    13             updateBindingResult(request, mavContainer.getModel());
    14         }
    15     }
    16 
    17     private void updateBindingResult(NativeWebRequest request, ModelMap model) throws Exception {
    18         List<String> keyNames = new ArrayList<String>(model.keySet());
    19         for (String name : keyNames) {
    20             Object value = model.get(name);
    21             // 核对是否需要绑定BindingResult到model
    22             if (isBindingCandidate(name, value)) {
    23                 String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + name;
    24 
    25                 if (!model.containsAttribute(bindingResultKey)) { // 不是其他参数绑定的结果
    26                     WebDataBinder dataBinder = binderFactory.createBinder(request, value, name);
    27                     model.put(bindingResultKey, dataBinder.getBindingResult());
    28                 }
    29             }
    30         }
    31     }
    32 
    33     private boolean isBindingCandidate(String attributeName, Object value) {
    34         // 不是其他参数绑定的结果
    35         if (attributeName.startsWith(BindingResult.MODEL_KEY_PREFIX)) {
    36             return false;
    37         }
    38         // 是否在@SessionAttributes注解定义中
    39         Class<?> attrType = (value != null) ? value.getClass() : null;
    40         if (this.sessionAttributesHandler.isHandlerSessionAttribute(attributeName, attrType)) {
    41             return true;
    42         }
    43         // 不是null,数组,集合,map,简单数据类型,则调用
    44         return (value != null && !value.getClass().isArray() && !(value instanceof Collection) &&
    45                 !(value instanceof Map) && !BeanUtils.isSimpleValueType(value.getClass()));
    46     }
    47 }
  • 相关阅读:
    断点续传的原理
    中国无线音乐搜索综合测评结果
    从头开始学jsp
    SQLServer和Oracle常用函数对比
    Asp.net程序重启自己
    What Can I do if "The type initializer for 'Emgu.CV.CvInvoke' threw an exception"?
    C++Builder2010多线程调用WebService的问题
    Desmon and Penny
    C#显示摄像头预览
    甲骨文78亿美金并购全球第二芯片商AMD
  • 原文地址:https://www.cnblogs.com/leftthen/p/5224816.html
Copyright © 2020-2023  润新知