ModelFactory 组件分析
ModelFactory 是用来维护 Model 的,包含两个功能:
初始化 Model;
更新 Model,处理器执行后将 Model 中的参数更新到 SessionAttributes 中;
1,初始化 Model
初始化 Model:在处理器执行前将数据设置到 Model 中,通过 initModel 方法完成;
ModelFactory#initModel
public void initModel(NativeWebRequest request, ModelAndViewContainer container, HandlerMethod handlerMethod) throws Exception {
// 从SessionAttributes中取出保存的参数,合并到mavContainer中 Map<String, ?> sessionAttributes = this.sessionAttributesHandler.retrieveAttributes(request); container.mergeAttributes(sessionAttributes);
// 执行注视了@ModelAttribute的方法并将结果设置到Model中 invokeModelAttributeMethods(request, container);
// 遍历既有@ModelAttribute注解又在@SessionAttributes注解中的参数,加入mavContainer for (String name : findSessionAttributeArguments(handlerMethod)) { if (!container.containsAttribute(name)) { Object value = this.sessionAttributesHandler.retrieveAttribute(request, name); if (value == null) { throw new HttpSessionRequiredException("Expected session attribute '" + name + "'", name); } container.addAttribute(name, value); } }}
复制代码
主要做了三件事:
从 SessionAttributes 中,取出保存的参数,合并到 mavContainer;
执行注释了 @ModelAttribute 的方法并将结果设置到 Model;
遍历有 @ModelAttribute 注解又在 @SessionAttributes 注解中的参数(参数名或类型被设置);
如果不在 mavContainer 中,使用 sessionAttributesHandler 从 SessionAttributes 中获取,并添加到 mavContainer 中;
invokeModelAttributeMethods
invokeModelAttributeMethodsinvokeModelAttributeMethods 方法:执行了含有 @ModelAttribute 注解的方法,将结果设置到 Model;
invokeModelAttributeMethods 源码:
private void invokeModelAttributeMethods(NativeWebRequest request, ModelAndViewContainer container) throws Exception {
while (!this.modelMethods.isEmpty()) { // 获取有@ModelAttribute的方法 InvocableHandlerMethod modelMethod = getNextModelMethod(container).getHandlerMethod(); // 获取@ModelAttribute注解信息 ModelAttribute ann = modelMethod.getMethodAnnotation(ModelAttribute.class); // 如果container已包含参数名,跳过 if (container.containsAttribute(ann.name())) { if (!ann.binding()) { container.setBindingDisabled(ann.name()); } continue; }
// container不包含参数名,执行方法 Object returnValue = modelMethod.invokeForRequest(request, container); // 判断返回值是否为void类型 // 如果是void,方法自己将参数设置到Model,不处理 // 如果不是void,使用getNameForReturnValue获取参数名,如果不存在container,添加进去 if (!modelMethod.isVoid()){ // 获取方法返回参数名 String returnValueName = getNameForReturnValue(returnValue, modelMethod.getReturnType()); if (!ann.binding()) { container.setBindingDisabled(returnValueName); } // 如果不存在container,添加进去 if (!container.containsAttribute(returnValueName)) { container.addAttribute(returnValueName, returnValue); } } }}
复制代码
遍历每个注释了 @ModelAttribute 的方法,拿到注释信息;
如果参数名存在 mavContainer 中,跳过此方法,否则执行;
执行方法后,潘丹返回值是否为 Void 类型;
getNameForReturnValue
public static String getNameForReturnValue(Object returnValue, MethodParameter returnType) { // 获取@ModelAttribute注解信息 ModelAttribute ann = returnType.getMethodAnnotation(ModelAttribute.class); // 如果有value,直接返回 if (ann != null && StringUtils.hasText(ann.value())) { return ann.value(); } // 如果没有value,使用Conventions.getVariableNameForReturnType根据方法,返回值类型,返回值,来获取 else { // 方法 Method method = returnType.getMethod(); // 方法所属类 Class<?> containingClass = returnType.getContainingClass(); // 返回值类型 Class<?> resolvedType = GenericTypeResolver.resolveReturnType(method, containingClass); // 根据方法,返回值类型,返回值,查找返回参数名 return Conventions.getVariableNameForReturnType(method, resolvedType, returnValue); }}
复制代码
Conventions#getVariableNameForReturnType
public static String getVariableNameForReturnType(Method method, Class<?> resolvedType, Object value) { Assert.notNull(method, "Method must not be null");
// 如果返回值类型是Object,返回实际类型 if (Object.class == resolvedType) { if (value == null) { throw new IllegalArgumentException("Cannot generate variable name for an Object return type with null value"); } return getVariableName(value); }
Class<?> valueClass; boolean pluralize = false; // 返回值是数组或Collection会使用内部实际包装的类型,并在最后添加"List" if (resolvedType.isArray()) { valueClass = resolvedType.getComponentType(); pluralize = true; } else if (Collection.class.isAssignableFrom(resolvedType)) { valueClass = ResolvableType.forMethodReturnType(method).asCollection().resolveGeneric(); if (valueClass == null) { if (!(value instanceof Collection)) { 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"); } Object valueToCheck = peekAhead(collection); valueClass = getClassForValue(valueToCheck); } pluralize = true; } else { valueClass = resolvedType; }
String name = ClassUtils.getShortNameAsProperty(valueClass); return (pluralize ? pluralize(name) : name);}// 获取返回值类型ShortName// 1,先获取去掉报名的类名// 2,判断类名是否大于1个字符,前两个字符是否都是大写// 如果是,直接返回,如果不是,降低一个字母变为小写返回public static String getShortNameAsProperty(Class<?> clazz) { String shortName = getShortName(clazz); int dotIndex = shortName.lastIndexOf(PACKAGE_SEPARATOR); shortName = (dotIndex != -1 ? shortName.substring(dotIndex + 1) : shortName); return Introspector.decapitalize(shortName);} private static final String PLURAL_SUFFIX = "List";
private static String pluralize(String name) { return name + PLURAL_SUFFIX;}
复制代码
findSessionAttributeArguments
findSessionAttributeArguments:获取同时有 @ModelAttribute 注解又在 @SessionAttributes 注解中的参数;
findSessionAttributeArguments 源码:
private List<String> findSessionAttributeArguments(HandlerMethod handlerMethod) { List<String> result = new ArrayList<String>(); // 遍历方法中的所有参数 for (MethodParameter parameter : handlerMethod.getMethodParameters()) { // 如果有@ModelAttribute注解 if (parameter.hasParameterAnnotation(ModelAttribute.class)) { // 获取参数名和参数类型 String name = getNameForParameter(parameter); Class<?> paramType = parameter.getParameterType(); // 根据获取到的参数名和参数类型检查参数是否在@SessionAttributes注释中 if (this.sessionAttributesHandler.isHandlerSessionAttribute(name, paramType)) { // 如果在@SessionAttributes注解中,即为符合要求的参数,将参数名放入集合 result.add(name); } } } return result;}
复制代码
遍历方法中的每一个参数;
判断是否有 @ModelAttribute 注解;
如果有 @ModelAttribute 注解,获取参数名和参数类型,并根据参数名和参数类型,检查参数是否 @SessionAttributes 注解中,如果在 @SessionAttributes 注解中,即为符合要求的参数,将参数名放入集合;
getNameForParameter
获取参数名方法:getNameForParameter
public static String getNameForParameter(MethodParameter parameter) { ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class); String name = (ann != null ? ann.value() : null); return (StringUtils.hasText(name) ? name : Conventions.getVariableNameForParameter(parameter));}
复制代码
获取参数名:
第三步和第一步的区别:
第一步将当前处理器中保存的所有SessionAttributes属性合并到mavContainer中,然后执行有@ModelAttribute注解的方法,并将返回结果合并到mavContainer中最后检查有@ModelAttribute注解且在@SessionAttributes中也设置的参数,是否在mavContainer中如果不在mavContainer中,则从这个SessionAttributes中获取并设置到mavContainer中如果获取不到则抛出异常
复制代码
Model 参数的优先级:
从源码可以看出:1,FlashMap中保存的参数优先级最高,在ModelFactory前执行2,SessionAttributes中保存的参数优先级第二,不能覆盖FlashMap中设置的参数3,拥有@ModelAttribute注解的方法设置的参数优先级第三4,拥有@ModelAttribute注解且从别的处理器的SessionAttributes获取到的参数优先级最低
从创建ModelFactory的过程看,@ModelAttribute注解的方法是全局的优先,处理器自己定义的其次
复制代码
2,更新 Model
从之前的分析,我们知道更新 Model 是通过调用 ModelFactory#updateModel 方法完成的;
ModelFactory#updateModel
public void updateModel(NativeWebRequest request, ModelAndViewContainer container) throws Exception {
// 获取defaultModel ModelMap defaultModel = container.getDefaultModel(); // 如果处理器调用了SessionStatus#setComplete,清空SessionAttributes if (container.getSessionStatus().isComplete()){ this.sessionAttributesHandler.cleanupAttributes(request); } // 将mavContainer的defaultModel中的参数设置到SessionAttributes else { this.sessionAttributesHandler.storeAttributes(request, defaultModel); }
// 如果请求未处理完成,且Model类型为defaultModel,给Model参数设置BindingResult if (!container.isRequestHandled() && container.getModel() == defaultModel) { updateBindingResult(request, defaultModel); }}
复制代码
updateModel 做了两件事:
1,设置SessionAttributes 如果处理器调用了SessionStatus#setComplete,则清空SessionAttributes 否则将mavContainer的defaultModel中的参数设置到SessionAttributes2,如果需要渲染视图,给Model参数设置BindingResult 也可以说是给Model中需要的参数设置BindingResult,给视图渲染备用
复制代码
updateBindingResult
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 if (isBindingCandidate(name, value)) { String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + name; // 如果model中不存在bindingResult if (!model.containsAttribute(bindingResultKey)) { // 通过dataBinderFactory创建WebDataBinder WebDataBinder dataBinder = this.dataBinderFactory.createBinder(request, value, name); // 添加到model model.put(bindingResultKey, dataBinder.getBindingResult()); } } }}
复制代码
遍历Model中板寸的所有参数通过isBindingCandidate方法判断是否需要添加BindingResult如果需要添加且model中不存在bindingResult,使用WebDatabinder获取BindingResult并添加到Model
复制代码
isBindingCandidate
String MODEL_KEY_PREFIX = BindingResult.class.getName() + ".";
private boolean isBindingCandidate(String attributeName, Object value) { // 判断前缀,如果是BindingResult开头,说明是其他参数绑定结果的BindingResult if (attributeName.startsWith(BindingResult.MODEL_KEY_PREFIX)) { return false; } Class<?> attrType = (value != null ? value.getClass() : null); // 判断是否是sessionAttributes管理的属性,如果是返回true if (this.sessionAttributesHandler.isHandlerSessionAttribute(attributeName, attrType)) { return true; }
// 检查如果不是空值,数组,Collection,Map,简单类型,都返回true return (value != null && !value.getClass().isArray() && !(value instanceof Collection) && !(value instanceof Map) && !BeanUtils.isSimpleValueType(value.getClass()));}
复制代码
先判断是否是其他参数绑定结果的BindingResult,如果是则返回false,不需要添加BindingResult
判断是否是sessionAttributes管理的属性,如果是返回true,需要添加BindingResult
判断如果不是空值,数组,Collection,Map,简单类型的其他类型都返回true,需要添加BindingResult
综上:不是BindingResult,空值,数组,Collection,Map,简单类型都返回true如果是这些类型(BindingResult除外),但是在@sessionAttributes中设置了,也返回true其他情况返回false
复制代码
3,结尾
ModelFactory 组件就说完了。下面说 ServletInvocableHandlerMethod;
评论