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. 1 package org.springframework.web.method.annotation;
  2. 2 public final class ModelFactory {
  3. 3 public void initModel(NativeWebRequest request, ModelAndViewContainer mavContainer, HandlerMethod handlerMethod)
  4. 4 throws Exception {
  5. 5 // 获取使用@SessionAttributes注解并已经解析的参数,合并到mavContainer
  6. 6 Map<String, ?> attributesInSession = this.sessionAttributesHandler.retrieveAttributes(request);
  7. 7 mavContainer.mergeAttributes(attributesInSession);
  8. 8 // 执行使用@ModelAttribute注解的方法,并将结果设置到mavContainer
  9. 9 invokeModelAttributeMethods(request, mavContainer);
  10. 10 // 将同时使用@ModelAttribute和@SessionAttributes注解的参数设置到mavContainer
  11. 11 for (String name : findSessionAttributeArguments(handlerMethod)) {
  12. 12 if (!mavContainer.containsAttribute(name)) {
  13. 13 Object value = this.sessionAttributesHandler.retrieveAttribute(request, name);
  14. 14 if (value == null) {
  15. 15 throw new HttpSessionRequiredException("Expected session attribute '" + name + "'");
  16. 16 }
  17. 17 mavContainer.addAttribute(name, value);
  18. 18 }
  19. 19 }
  20. 20 }
  21. 21 // ...
  22. 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. 1 package org.springframework.web.method.annotation;
  2. 2 public final class ModelFactory {
  3. 3 private void invokeModelAttributeMethods(NativeWebRequest request, ModelAndViewContainer mavContainer)
  4. 4 throws Exception {
  5. 5 // 迭代使用@ModelAttribute注解的方法
  6. 6 for (InvocableHandlerMethod attrMethod : this.attributeMethods) {
  7. 7 // 使用@ModelAttribute的value值作为 attribute name
  8. 8 String modelName = attrMethod.getMethodAnnotation(ModelAttribute.class).value();
  9. 9 if (mavContainer.containsAttribute(modelName)) {
  10. 10 continue;
  11. 11 }
  12. 12 // 委托InvocableHandlerMethod调用方法,生成值
  13. 13 Object returnValue = attrMethod.invokeForRequest(request, mavContainer);
  14. 14 // 如果方法返回值,需要将这个值同步到mavContainer中
  15. 15 if (!attrMethod.isVoid()){
  16. 16 // 生成参数名:注解的value或者返回值类型
  17. 17 String returnValueName = getNameForReturnValue(returnValue, attrMethod.getReturnType());
  18. 18 if (!mavContainer.containsAttribute(returnValueName)) {
  19. 19 mavContainer.addAttribute(returnValueName, returnValue);
  20. 20 }
  21. 21 }
  22. 22 }
  23. 23 }
  24. 24 // ...
  25. 25 }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  获取缓存在sessionAttributesHandler中的参数值

  如果值为空,抛HttpSessionRequiredException

  否则同步到mavContainer中

  1. 1 package org.springframework.web.method.annotation;
  2. 2 public final class ModelFactory {
  3. 3 // ...
  4. 4 public void initModel(NativeWebRequest request, ModelAndViewContainer mavContainer, HandlerMethod handlerMethod)
  5. 5 throws Exception {
  6. 6 // ...
  7. 7 for (String name : findSessionAttributeArguments(handlerMethod)) {
  8. 8 if (!mavContainer.containsAttribute(name)) {
  9. 9 Object value = this.sessionAttributesHandler.retrieveAttribute(request, name);
  10. 10 if (value == null) {
  11. 11 throw new HttpSessionRequiredException("Expected session attribute '" + name + "'");
  12. 12 }
  13. 13 mavContainer.addAttribute(name, value);
  14. 14 }
  15. 15 }
  16. 16 }
  17. 17 private List<String> findSessionAttributeArguments(HandlerMethod handlerMethod) {
  18. 18 List<String> result = new ArrayList<String>();
  19. 19 // 这边找的是HandlerMethod的参数
  20. 20 for (MethodParameter param : handlerMethod.getMethodParameters()) {
  21. 21 if (param.hasParameterAnnotation(ModelAttribute.class)) {
  22. 22 String name = getNameForParameter(param);
  23. 23 if (this.sessionAttributesHandler.isHandlerSessionAttribute(name, param.getParameterType())) {
  24. 24 result.add(name);
  25. 25 }
  26. 26 }
  27. 27 }
  28. 28 return result;
  29. 29 }
  30. 30 public static String getNameForParameter(MethodParameter parameter) {
  31. 31 ModelAttribute annot = parameter.getParameterAnnotation(ModelAttribute.class);
  32. 32 String attrName = (annot != null) ? annot.value() : null;
  33. 33 // 如果value为空,获取参数类型解析属性名称
  34. 34 return StringUtils.hasText(attrName) ? attrName : Conventions.getVariableNameForParameter(parameter);
  35. 35 }
  36. 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. 1 package org.springframework.web.method.annotation;
  2. 2 public final class ModelFactory {
  3. 3 // ...
  4. 4 public void updateModel(NativeWebRequest request, ModelAndViewContainer mavContainer) throws Exception {
  5. 5 if (mavContainer.getSessionStatus().isComplete()){ // 清除
  6. 6 this.sessionAttributesHandler.cleanupAttributes(request);
  7. 7 }
  8. 8 else { // 不清除,那么就需要同步
  9. 9 this.sessionAttributesHandler.storeAttributes(request, mavContainer.getModel());
  10. 10 }
  11. 11
  12. 12 if (!mavContainer.isRequestHandled()) {
  13. 13 updateBindingResult(request, mavContainer.getModel());
  14. 14 }
  15. 15 }
  16. 16
  17. 17 private void updateBindingResult(NativeWebRequest request, ModelMap model) throws Exception {
  18. 18 List<String> keyNames = new ArrayList<String>(model.keySet());
  19. 19 for (String name : keyNames) {
  20. 20 Object value = model.get(name);
  21. 21 // 核对是否需要绑定BindingResult到model
  22. 22 if (isBindingCandidate(name, value)) {
  23. 23 String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + name;
  24. 24
  25. 25 if (!model.containsAttribute(bindingResultKey)) { // 不是其他参数绑定的结果
  26. 26 WebDataBinder dataBinder = binderFactory.createBinder(request, value, name);
  27. 27 model.put(bindingResultKey, dataBinder.getBindingResult());
  28. 28 }
  29. 29 }
  30. 30 }
  31. 31 }
  32. 32
  33. 33 private boolean isBindingCandidate(String attributeName, Object value) {
  34. 34 // 不是其他参数绑定的结果
  35. 35 if (attributeName.startsWith(BindingResult.MODEL_KEY_PREFIX)) {
  36. 36 return false;
  37. 37 }
  38. 38 // 是否在@SessionAttributes注解定义中
  39. 39 Class<?> attrType = (value != null) ? value.getClass() : null;
  40. 40 if (this.sessionAttributesHandler.isHandlerSessionAttribute(attributeName, attrType)) {
  41. 41 return true;
  42. 42 }
  43. 43 // 不是null,数组,集合,map,简单数据类型,则调用
  44. 44 return (value != null && !value.getClass().isArray() && !(value instanceof Collection) &&
  45. 45 !(value instanceof Map) && !BeanUtils.isSimpleValueType(value.getClass()));
  46. 46 }
  47. 47 }

http://www.cnblogs.com/leftthen/p/5224816.html

SpringMVC源码解析- HandlerAdapter - ModelFactory(转)的更多相关文章

  1. SpringMVC源码解析- HandlerAdapter - ModelFactory

    ModelFactory主要是两个职责: 1. 初始化model 2. 处理器执行后将modle中相应参数设置到SessionAttributes中 我们来看看具体的处理逻辑(直接充当分析目录): 1 ...

  2. SpringMVC源码解析- HandlerAdapter初始化

    HandlerAdapter初始化时,主要是进行注解解析器初始化注册;返回值处理类初始化;全局注解@ControllerAdvice内容读取并缓存. 目录: 注解解析器初始化注册:@ModelAttr ...

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

    使用SpringMVC开发时,可以使用@SessionAttributes注解缓存信息.这样业务开发时,就不需要一次次手动操作session保存,读数据. @Controller @RequestMa ...

  4. SpringMVC源码解析 - HandlerAdapter - HandlerMethodArgumentResolver

    HandlerMethodArgumentResolver主要负责执行handler前参数准备工作. 看个例子,红色部分的id初始化,填充值就是它干的活: @RequestMapping(value ...

  5. springMVC源码解析--ViewResolver视图解析器执行(三)

    之前两篇博客springMVC源码分析--ViewResolver视图解析器(一)和springMVC源码解析--ViewResolverComposite视图解析器集合(二)中我们已经简单介绍了一些 ...

  6. SpringMVC源码解析

    一:springmvc运行过程: 1. dispatcherServlet 通过 HandlerMapping 找到controller2. controller经过后台逻辑处理得到结果集modela ...

  7. 深入了解SpringMVC源码解析

    Spring MVC源码解析 Spring MVC的使用原理其实是通过配置一个Servlet来接管所有的请求,所有的请求由这个Servlet来进行分发处理. 我们可以从web.xml里面看出这一点 & ...

  8. springMVC源码解析--ViewResolverComposite视图解析器集合(二)

    上一篇博客springMVC源码分析--ViewResolver视图解析器(一)中我们介绍了一些springMVC提供的很多视图解析器ViewResolver,在开发的一套springMVC系统中是可 ...

  9. springMVC源码解析--HandlerMethodArgumentResolverComposite参数解析器集合(二)

    上一篇博客springMVC源码分析--HandlerMethodArgumentResolver参数解析器(一)中我们已经介绍了参数解析相关的东西,并且也提到了HandlerMethodArgume ...

随机推荐

  1. k-d tree算法

    k-d树(k-dimensional树的简称),是一种分割k维数据空间的数据结构.主要应用于多维空间关键数据的搜索(如:范围搜索和最近邻搜索). 应用背景 SIFT算法中做特征点匹配的时候就会利用到k ...

  2. 在内网架设一个可供外网登录的ftpserver

    ftpserver是使用比較寻常的server,可是IP资源是有限的.那么怎么让内网的server給外网的用户提供服务了? 首先须要找一个FTPserver程序,我在这边使用pure-ftpd-mys ...

  3. 关于静态与动态编译arm平台程序的比較

    因为近期弄个console程序,调用了readline,ncurses库,这两个动态库加起来有四百多k.而程序事实上非常小,其它地方也没使用到这两个库 所以想静态编译看看console程序有多大. # ...

  4. NGUI研究之在Unity中使用贝塞尔曲线

    鼎鼎大名的贝塞尔曲线相信大家都耳熟能详.这两天由于工作的原因须要将贝塞尔曲线加在project中.那么我迅速的研究了一下成果就分享给大家了哦.贝塞尔曲线的原理是由两个点构成的随意角度的曲线,这两个点一 ...

  5. 【甘道夫】Win7x64环境下编译Apache Hadoop2.2.0的Eclipse小工具

    目标: 编译Apache Hadoop2.2.0在win7x64环境下的Eclipse插件 环境: win7x64家庭普通版 eclipse-jee-kepler-SR1-win32-x86_64.z ...

  6. 恢复js文件在windows默认打开方式

    解决办法: 运行 regedit 打开注册表编辑器,定位 "HKEY_CLASSES_ROOT" > ".js" 这一项,双击默认值将数值数据改为&quo ...

  7. ReactNavtive框架教程(2)

    , alignItems: 'center' } }); 标准的 CSS 属性.尽管用CSS比在IB设置UI样式的可视化要差.但总比在viewDidLoad()方法中用代码写要好一些. 然后增加下面代 ...

  8. android4.0 USB Camera示例(五个辅助)jpg压缩

    前的最后一个 我们说,一个直接yuv变成jpg该功能 但是转换不成功 主要功能是yuv420转jpg的 根据研究发现 yuv420的序列是这种 YYYY YYYY UVUV 而yuv422的隔行扫描的 ...

  9. 关于埃博拉(Ebola)基础研究病毒

    关于埃博拉(Ebola)病毒的基础研究 2005年.美国哈佛大学医学研究院(Harvard Medical School)James Cunningham教授关于埃博拉病毒有一项基础研究,研究成果发表 ...

  10. HDU 1171 Big Event in HDU (多重背包)

    Big Event in HDU Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others ...