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

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. NET基础篇——反射的奥妙

    反射是一个程序集发现及运行的过程,通过反射可以得到*.exe或*.dll等程序集内部的信息.使用反射可以看到一个程序集内部的接口.类.方法.字段.属性.特性等等信息.在System.Reflectio ...

  2. cowboy添加验证码

    参考的http://beebole.com/blog/erlang/how-to-implement-captcha-in-erlang-web-application/,移到cowboy,修改了下: ...

  3. 黄聪:win7 64位系统PS、AI、PSD缩略图预览补丁

    MysticThumbs支持Windows 7 / Vista / XP,32位和64位.除了预览PSD以外,还支持DDS.SGI缩略图显示. Mystic Thumbs是一款用来支持win7 64位 ...

  4. css用法大全

    direction:控制文本方向 ltr:默认.文本方向从左到右. rtl:文本方向从右到左. inherit:规定应该从父元素继承 direction 属性的值. <select name=& ...

  5. Gson的几种使用方式

    一.Gson是一个Java类库,用于将Java对象转换为它们所代表的JSON数据,也可以用于将一个JSON字符串转换为对应的Java对象.这个是谷歌开发的一套针对json处理的一个类库,功能很强大. ...

  6. OpenGL 画高斯随机函数

    高斯函数代码 const float CFFTOceanShader::_getGaussianRandomFloat() const { float u1 = rand() / (float)RAN ...

  7. springcloud(五) Hystrix 降级,超时

    分布式系统中一定会遇到的一个问题:服务雪崩效应或者叫级联效应什么是服务雪崩效应呢? 在一个高度服务化的系统中,我们实现的一个业务逻辑通常会依赖多个服务,比如:商品详情展示服务会依赖商品服务, 价格服务 ...

  8. OpenMP 旅行商问题,静态调度

    ▶ <并行程序设计导论>第六章中讨论了旅行商,分别使用了 MPI,Pthreads,OpenMP 来进行实现,这里是 OpenMP 的代码,分为静态调度(每个线程分分配等量的搜索人物)和动 ...

  9. Rhythmk 学习 Hibernate 09 - Hibernate HQL

    1.初始数据 @Test public void test01() { Session session = null; try { session = HibernateUtil.getSession ...

  10. 【321】python进程监控:psutil

    参考:Python进程监控-MyProcMonitor 参考:Python3.6 安装psutil 模块和功能简介 参考:psutil module (Download files) 参考:廖雪峰 - ...