SpringMVC源码解析- HandlerAdapter - ModelFactory

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 }
时间: 2024-10-23 21:33:21

SpringMVC源码解析- HandlerAdapter - ModelFactory的相关文章

SpringMVC源码解析- HandlerAdapter初始化

HandlerAdapter初始化时,主要是进行注解解析器初始化注册;返回值处理类初始化;全局注解@ControllerAdvice内容读取并缓存. 目录: 注解解析器初始化注册:@ModelAttribute(往model中添加属性) 注解解析器初始化注册:@InitBinder(用于注册校验器,参数编辑器等) 返回值处理returnValueHandlers初始化 全局的@ControllerAdvice注解使用类的@ModelAttribute 和 @InitBinder信息读取并缓存 注

SpringMVC源码解析 - HandlerAdapter - @SessionAttributes注解处理

使用SpringMVC开发时,可以使用@SessionAttributes注解缓存信息.这样业务开发时,就不需要一次次手动操作session保存,读数据. 1 @Controller 2 @RequestMapping("telephones") 3 @SessionAttributes(value={"name","degree"},types={Double.class}) 4 public class AttributeController

springmvc源码解析-初始化

1.      概述 对于Web开发者,MVC模型是大家再熟悉不过的了,SpringMVC中,满足条件的请求进入到负责请求分发的DispatcherServlet,DispatcherServlet根据请求url到控制器的映射(HandlerMapping中保存),HandlerMapping最终返回HandlerExecutionChain,其中包含了具体的处理对象handler(也即我们编程时写的controller)以及一系列的拦截器interceptors,此时DispatcherSer

SpringMVC源码解析

1. SpringMVC重要组件 1. DispatcherServlet SpringMVC的中央Servlet,所有请求的入口,重写了doService()方法.核心方法:doService().doDispatch(). 2. HandlerMapping 处理器映射,负责根据HttpServletRequest找到对应的Handler,这里返回Handler的辅助类HandlerExecutionChain. public interface HandlerMapping { @Null

SpringMVC源码解析 - HandlerMethod

HandlerMethod及子类主要用于封装方法调用相关信息,子类还提供调用,参数准备和返回值处理的职责. 分析下各个类的职责吧(顺便做分析目录): HandlerMethod 封装方法定义相关的信息,如类,方法,参数等. 使用场景:HandlerMapping时会使用 InvocableHandlerMethod 添加参数准备,方法调用功能 使用场景:执行使用@ModelAttribute注解会使用 ServletInvocableHandlerMethod 添加返回值处理职责,Respons

SpringMVC源码解析 - HandlerAdater - ModelAndViewContainer上下文容器

HandlerAdapter在处理请求时上下文数据的传递工作是由ModelAndViewContainer负责的. 源码注释是这样描述的: Records model and view related decisions made by HandlerMethodArgumentResolvers and HandlerMethodReturnValueHandlers during the course of invocation of a controller method. 翻译下: 记录

SpringMVC源码解析-DispatcherServlet启动流程和初始化

在使用springmvc框架,会在web.xml文件配置一个DispatcherServlet,这正是web容器开始初始化,同时会在建立自己的上下文来持有SpringMVC的bean对象. 先从DispatcherServlet入手,从名字来看,它是一个Servlet.它的定义如下: public class DispatcherServlet extends FrameworkServlet { 它是继承FrameworkServlet,来看一下整个的继承关系. 从继承关系来看,Dispatc

SpringMVC源码解析(上)

原文:http://ayufox.iteye.com/blog/393226 1.从DispatcherServlet开始     与很多使用广泛的MVC框架一样,SpringMVC使用的是FrontController模式,所有的设计都围绕DispatcherServlet为中心来展开的.见下图,所有请求从DispatcherServlet进入,DispatcherServlet根据配置好的映射策略确定处理的Controller,Controller处理完成返回ModelAndView,Dis

SpringMVC源码解析(下)

4.请求-处理链映射(HandlerMapping)    HandlerMapping定义了请求与处理链之间的映射的策略,见如下接口. public interface HandlerMapping { String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping"; HandlerExecutionChain getHandler(