SpringMVC源码解析- HandlerAdapter - ModelFactory
2016-03-01 08:23
633 查看
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 解析类上使用的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中还没有这个参数值,则同步进去
看看InvocableHandlerMethod的invokeForRequest(NativeWebRequest request,ModelAndViewContainer mavContainer,Object... providedArgs)
这边涉及到两个封装类:InvocableHandlerMethod和MethodParameter.
InvocableHandlerMethod封装一个可执行的方法,在HandlerMethod基础上添加方法参数解析的职责.
MethodParameter封装方法定义相关的概念
具体的处理逻辑还是看代码中的注释吧.
我们再来看看参数名称的生成规则吧:
如果@ModelAttribute中定义了value,就以value命名
如果注解中没有定义value,则根据返回值类型定义名称
如:String会被定义为string,List<Double>会被定义为doubleList(集合都是这样定义的,包括array数组)
接下来是如何根据返回值类型生成参数名称的逻辑,挺有意思,重点展开:
这边又根据方法的signature中定义的参数类型是否细化再衍生一个分支:
如果方法签名中只定义Object类型,则需要根据value生成;否则根据签名生成
1.3 将注解@ModelAttribute方法参数(在@SessionAttributes定义范围内)同步到model中
遍历HandlerMethod的所有参数,找出使用了@ModelAttribute注解的参数
获取参数的名称:注解value值 > 参数类型
核对这个参数名称是否在@SessionAttributes注解内
如果mavContainer中还没有该参数,继续处理
获取缓存在sessionAttributesHandler中的参数值
如果值为空,抛HttpSessionRequiredException
否则同步到mavContainer中
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. 初始化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
做了三个事情,详细见源码中的注释吧:
package org.springframework.web.method.annotation; public final class ModelFactory { public void initModel(NativeWebRequest request, ModelAndViewContainer mavContainer, HandlerMethod handlerMethod) throws Exception { // 获取使用@SessionAttributes注解并已经解析的参数,合并到mavContainer Map<String, ?> attributesInSession = this.sessionAttributesHandler.retrieveAttributes(request); mavContainer.mergeAttributes(attributesInSession); // 执行使用@ModelAttribute注解的方法,并将结果设置到mavContainer invokeModelAttributeMethods(request, mavContainer); // 将同时使用@ModelAttribute和@SessionAttributes注解的参数设置到mavContainer for (String name : findSessionAttributeArguments(handlerMethod)) { if (!mavContainer.containsAttribute(name)) { Object value = this.sessionAttributesHandler.retrieveAttribute(request, name); if (value == null) { throw new HttpSessionRequiredException("Expected session attribute '" + name + "'"); } mavContainer.addAttribute(name, value); } } } // ... }
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中还没有这个参数值,则同步进去
package org.springframework.web.method.annotation; public final class ModelFactory { private void invokeModelAttributeMethods(NativeWebRequest request, ModelAndViewContainer mavContainer) throws Exception { // 迭代使用@ModelAttribute注解的方法 for (InvocableHandlerMethod attrMethod : this.attributeMethods) { // 使用@ModelAttribute的value值作为 attribute name String modelName = attrMethod.getMethodAnnotation(ModelAttribute.class).value(); if (mavContainer.containsAttribute(modelName)) { continue; } // 委托InvocableHandlerMethod调用方法,生成值 Object returnValue = attrMethod.invokeForRequest(request, mavContainer); // 如果方法返回值,需要将这个值同步到mavContainer中 if (!attrMethod.isVoid()){ // 生成参数名:注解的value或者返回值类型 String returnValueName = getNameForReturnValue(returnValue, attrMethod.getReturnType()); if (!mavContainer.containsAttribute(returnValueName)) { mavContainer.addAttribute(returnValueName, returnValue); } } } } // ... }
看看InvocableHandlerMethod的invokeForRequest(NativeWebRequest request,ModelAndViewContainer mavContainer,Object... providedArgs)
这边涉及到两个封装类:InvocableHandlerMethod和MethodParameter.
InvocableHandlerMethod封装一个可执行的方法,在HandlerMethod基础上添加方法参数解析的职责.
MethodParameter封装方法定义相关的概念
具体的处理逻辑还是看代码中的注释吧.
package org.springframework.web.method.support; public class InvocableHandlerMethod extends HandlerMethod { public final Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { // 生成方法调用时的参数 Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); // 霸气的调用 Object returnValue = invoke(args); return returnValue; } private Object[] getMethodArgumentValues( NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { // 获取参数,这边没有值 MethodParameter[] parameters = getMethodParameters(); Object[] args = new Object[parameters.length]; for (int i = 0; i < parameters.length; i++) { MethodParameter parameter = parameters[i]; // 参数名称查找器,反射中拿不到参数名,所以使用spring的parameterNameDiscover parameter.initParameterNameDiscovery(parameterNameDiscoverer); // 获取参数的目标类型,methodParam.setParameterType(result);设置.这边具体的逻辑后面再细化 GenericTypeResolver.resolveParameterType(parameter, getBean().getClass()); // 尝试通过类型判断,获取参数的值 args[i] = resolveProvidedArgument(parameter, providedArgs); if (args[i] != null) { continue; } // 使用HandlerMethodArgumentResolver,判断是否支持处理 if (argumentResolvers.supportsParameter(parameter)) { try { // 这边直接处理,实际执行时,是通过责任链设计模式处理 args[i] = argumentResolvers.resolveArgument(parameter, mavContainer, request, dataBinderFactory); continue; } catch (Exception ex) { throw ex; } } if (args[i] == null) { String msg = getArgumentResolutionErrorMessage("No suitable resolver for argument", i); throw new IllegalStateException(msg); } } return args; } private Object invoke(Object... args) throws Exception { // 解决权限的问题 ReflectionUtils.makeAccessible(this.getBridgedMethod()); try { return getBridgedMethod().invoke(getBean(), args); } catch (IllegalArgumentException | InvocationTargetExceptione) { // 省略异常处理机制 } } // ... }
我们再来看看参数名称的生成规则吧:
如果@ModelAttribute中定义了value,就以value命名
如果注解中没有定义value,则根据返回值类型定义名称
如:String会被定义为string,List<Double>会被定义为doubleList(集合都是这样定义的,包括array数组)
package org.springframework.web.method.annotation; public final class ModelFactory { public static String getNameForReturnValue(Object returnValue, MethodParameter returnType) { ModelAttribute annot = returnType.getMethodAnnotation(ModelAttribute.class); if (annot != null && StringUtils.hasText(annot.value())) { // 注解中定义了value return annot.value(); } else { // 根据类型生成 Method method = returnType.getMethod(); Class<?> resolvedType = GenericTypeResolver.resolveReturnType(method, returnType.getDeclaringClass()); return Conventions.getVariableNameForReturnType(method, resolvedType, returnValue); } } // ... }
接下来是如何根据返回值类型生成参数名称的逻辑,挺有意思,重点展开:
这边又根据方法的signature中定义的参数类型是否细化再衍生一个分支:
如果方法签名中只定义Object类型,则需要根据value生成;否则根据签名生成
package org.springframework.core; public abstract class Conventions { public static String getVariableNameForReturnType(Method method, Class resolvedType, Object value) { // 如果signature定义为object,则根据value来判断 if (Object.class.equals(resolvedType)) { if (value == null) { throw new IllegalArgumentException("Cannot generate variable name for an Object return type with null value"); } // 这边的处理逻辑跟下面的很类似,不展开.差别是一个根据value,一个根据resolvedType判断 return getVariableName(value); } Class valueClass; // 是否是数组或集合 boolean pluralize = false; if (resolvedType.isArray()) { // 数组,读取内部元素的类型 valueClass = resolvedType.getComponentType(); pluralize = true; } else if (Collection.class.isAssignableFrom(resolvedType)) { // 集合 // 集合内的元素类型 valueClass = GenericCollectionTypeResolver.getCollectionReturnType(method); if (valueClass == null) { if (!(value instanceof Collection)) {// 跟value再校验一遍类型 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"); } // 获取集合中的第一个value Object valueToCheck = peekAhead(collection); // 获取value的类系 valueClass = getClassForValue(valueToCheck); } pluralize = true; } else { valueClass = resolvedType; } String name = ClassUtils.getShortNameAsProperty(valueClass); return (pluralize ? pluralize(name) : name); } // 获取集合中的第一个value private static Object peekAhead(Collection collection) { Iterator it = collection.iterator(); if (!it.hasNext()) { throw new IllegalStateException( "Unable to peek ahead in non-empty collection - no element found"); } Object value = it.next(); if (value == null) { throw new IllegalStateException( "Unable to peek ahead in non-empty collection - only null element found"); } return value; } private static Class getClassForValue(Object value) { Class valueClass = value.getClass(); // 代理时根据接口获取,遍历时以第一个符合条件的为准 if (Proxy.isProxyClass(valueClass)) { Class[] ifcs = valueClass.getInterfaces(); for (Class ifc : ifcs) { if (!ignoredInterfaces.contains(ifc)) { return ifc; } } } else if (valueClass.getName().lastIndexOf('$') != -1 && valueClass.getDeclaringClass() == null) { // '$' in the class name but no inner class - // assuming it's a special subclass (e.g. by OpenJPA) valueClass = valueClass.getSuperclass(); } return valueClass; } // 数组或结合统一添加后缀List private static String pluralize(String name) { //private static final String PLURAL_SUFFIX = "List"; return name + PLURAL_SUFFIX; } }
1.3 将注解@ModelAttribute方法参数(在@SessionAttributes定义范围内)同步到model中
遍历HandlerMethod的所有参数,找出使用了@ModelAttribute注解的参数
获取参数的名称:注解value值 > 参数类型
核对这个参数名称是否在@SessionAttributes注解内
如果mavContainer中还没有该参数,继续处理
获取缓存在sessionAttributesHandler中的参数值
如果值为空,抛HttpSessionRequiredException
否则同步到mavContainer中
package org.springframework.web.method.annotation; public final class ModelFactory { // ... public void initModel(NativeWebRequest request, ModelAndViewContainer mavContainer, HandlerMethod handlerMethod) throws Exception { // ... for (String name : findSessionAttributeArguments(handlerMethod)) { if (!mavContainer.containsAttribute(name)) { Object value = this.sessionAttributesHandler.retrieveAttribute(request, name); if (value == null) { throw new HttpSessionRequiredException("Expected session attribute '" + name + "'"); } mavContainer.addAttribute(name, value); } } } private List<String> findSessionAttributeArguments(HandlerMethod handlerMethod) { List<String> result = new ArrayList<String>(); // 这边找的是HandlerMethod的参数 for (MethodParameter param : handlerMethod.getMethodParameters()) { if (param.hasParameterAnnotation(ModelAttribute.class)) { String name = getNameForParameter(param); if (this.sessionAttributesHandler.isHandlerSessionAttribute(name, param.getParameterType())) { result.add(name); } } } return result; } public static String getNameForParameter(MethodParameter parameter) { ModelAttribute annot = parameter.getParameterAnnotation(ModelAttribute.class); String attrName = (annot != null) ? annot.value() : null; // 如果value为空,获取参数类型解析属性名称 return StringUtils.hasText(attrName) ? attrName : Conventions.getVariableNameForParameter(parameter); } }
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,简单数据类型
剩下的看代码注释就行了
package org.springframework.web.method.annotation; public final class ModelFactory { // ... public void updateModel(NativeWebRequest request, ModelAndViewContainer mavContainer) throws Exception { if (mavContainer.getSessionStatus().isComplete()){ // 清除 this.sessionAttributesHandler.cleanupAttributes(request); } else { // 不清除,那么就需要同步 this.sessionAttributesHandler.storeAttributes(request, mavContainer.getModel()); } if (!mavContainer.isRequestHandled()) { updateBindingResult(request, mavContainer.getModel()); } } 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到model if (isBindingCandidate(name, value)) { String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + name; if (!model.containsAttribute(bindingResultKey)) { // 不是其他参数绑定的结果 WebDataBinder dataBinder = binderFactory.createBinder(request, value, name); model.put(bindingResultKey, dataBinder.getBindingResult()); } } } } private boolean isBindingCandidate(String attributeName, Object value) { // 不是其他参数绑定的结果 if (attributeName.startsWith(BindingResult.MODEL_KEY_PREFIX)) { return false; } // 是否在@SessionAttributes注解定义中 Class<?> attrType = (value != null) ? value.getClass() : null; if (this.sessionAttributesHandler.isHandlerSessionAttribute(attributeName, attrType)) { return true; } // 不是null,数组,集合,map,简单数据类型,则调用 return (value != null && !value.getClass().isArray() && !(value instanceof Collection) && !(value instanceof Map) && !BeanUtils.isSimpleValueType(value.getClass())); } }
相关文章推荐
- EasyOCR JavaVerify 验证码识别
- Java之Vector特有功能使用
- Java中基础类库使用
- [leetcode]House Robber 分享一个很easy的解法
- Java中final关键字用法总结
- Java中集合类用法总结
- Java学习源代码学习
- Java学习源代码学习(总一天我能够学习源码!)
- java sha1
- 反射(Reflection)
- JavaClientVM与JavaServerVM区别
- .net与Java的WebService互调
- 在 Java 7 中体会 NIO.2 异步执行的快乐
- 关于Eclipse上使用可视化设计界面(Java EE 使用可视化界面设计)
- 碰到的Spring 事务无效
- 自学 java 笔记 day4 (语言语法基础组成3)
- MyEclipse2014/2015 安装Activiti插件
- java开发利器-lombok
- Java提升-责任链模式(四)
- Java提升-观察者模式(五)