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中的参数设置到SessionAttributes
2,如果需要渲染视图,给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;
评论