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

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

 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()));
}
}

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. 【转】linux中inittab文件详解

    原文网址:http://www.2cto.com/os/201108/98426.html linux中inittab文件详解 init的进程号是1(ps -aux | less),从这一点就能看出, ...

  2. Spring中使用JDBC

    Spring中的数据库异常体系 使用JDBC(不使用Spring)的时候,我们需要强制捕获SQLException,否则无法使用JDBC处理任何事情.SQLException表示尝试访问数据库的时候出 ...

  3. 读java并发编程笔记

    同步策略:在共享资源上面加锁 java监视器模式:class对象-与之对应的锁(内置锁)[对象锁与class锁] 执行策略: 取消策略: =============================== ...

  4. C#直接解析Json键值对

    string test_json = "{\"name\":\"tom\",\"nickname\":\"tony\&q ...

  5. Android中的WebView进行直接加载网页(要注意解决权限问题)

    我们都知道Android的网络功能很不错,当然Android中WebView组件也挺不错,可以直接进行加载网页,我们可以把这个看做一个小型的浏览器\ [注]以下的一些内容我翻译了一下文档,可能有些翻译 ...

  6. 迷你MVVM框架 avalonjs 学习教程8、属性操作

    属性操作是DOM操作很大的一块,它包括类名操作,表单元素的value属性操作,元素固有属性的管理,元素自定义属性的管理,某些元素的一些布尔属性的操作.大多数情况下,元素属性的值是字符串类型,我们称之为 ...

  7. orchard cms 项目迁移

    删除Orchard.Web  下的 App_Data 目录,重新安装项目

  8. SpringMvc配置拦截器

    SpringMVC可以通过配置拦截器,进行url过滤等处理. 在spring-mvc.xml的配置文件中,如下示: 其中,在<mvc:interceptors>中可以配置多个拦截器< ...

  9. unity3d开发实战《啪啪三国》技术详解!

     去年11月,上海火溶网络CEO王伟峰以其第一款3d手游产品<啪啪三国>为例,着重讲解了unity3D手机网游开发的经验,其中涉及了团队组成.人员要求.常见的unity3d开发遇到的坑及解 ...

  10. iOS下nil 、NULL、 Nil 、NSNull的区别

    1.nil,定义一个空的实例,指向OC中对象的空指针. 示例代码: NSString *someString = nil; NSURL *someURL = nil; id someObject = ...