干什么
modelFactory 主要是维护model的,有两个作用:
- 初始化model
- 更新model,在处理器处理完了之后,把参数更新的到sessionAttributes中
初始化
在 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter中的invokeHandlerMethod(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod)方法中调用。看一下源码
public void initModel(NativeWebRequest request, ModelAndViewContainer container,
HandlerMethod handlerMethod) throws Exception {
//取出sessionAttribute中保存的参数合并到model中
Map<String, ?> sessionAttributes = this.sessionAttributesHandler.retrieveAttributes(request);
container.mergeAttributes(sessionAttributes);
//执行注释了标记了@modelAttribute的方法
invokeModelAttributeMethods(request, container);
//看看要执行的方法,入参有没有被@modelAttribute修饰的,如果有在@sessionAttribute中找,找到给赋值,主要在创建一个 有@modelAttribute修饰的入参的name
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);
}
}
首先细说一下findSessionAttributeArguments(handlerMethod)这个方法,这个方法主要是取得被@modelAttribute标签修饰的入参的属性名
private List<String> findSessionAttributeArguments(HandlerMethod handlerMethod) {
List<String> result = new ArrayList<String>();
//取得所有的入参
for (MethodParameter parameter : handlerMethod.getMethodParameters()) {
//判断入参是否有被修饰
if (parameter.hasParameterAnnotation(ModelAttribute.class)) {
//又得话取名字
String name = getNameForParameter(parameter);
Class<?> paramType = parameter.getParameterType();
if (this.sessionAttributesHandler.isHandlerSessionAttribute(name, paramType)) {
result.add(name);
}
}
}
return result;
}
//取名字
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));
}
//到这说明没有在标签中直接配置数据名,需要根据类型来命名
public static String getVariableNameForParameter(MethodParameter parameter) {
Assert.notNull(parameter, "MethodParameter must not be null");
Class<?> valueClass;
boolean pluralize = false;
//判断是不是数组
if (parameter.getParameterType().isArray()) {
//如果是数组的话,就得到数据的元素class
valueClass = parameter.getParameterType().getComponentType();
pluralize = true;
}
// 是集合的话
else if (Collection.class.isAssignableFrom(parameter.getParameterType())) {
//得到集合的参数类型
valueClass = GenericCollectionTypeResolver.getCollectionParameterType(parameter);
if (valueClass == null) {
throw new IllegalArgumentException(
"Cannot generate variable name for non-typed Collection parameter type");
}
pluralize = true;
}
else {
//都不是的话
valueClass = parameter.getParameterType();
}
//从类名得到短类名,就是去掉报名,看首字母是不是大写,是就是变小写返回
String name = ClassUtils.getShortNameAsProperty(valueClass);
return (pluralize ? pluralize(name) : name);
}
接下来我们在看一下invokeModelAttributeMethods(request, container);源码如下
invokeModelAttributeMethods(request, container);
private void invokeModelAttributeMethods(NativeWebRequest request, ModelAndViewContainer container)
throws Exception {
//首先看我们前面查找收集完的被@modelAttribute的方法是不是为空,为空说明没有
while (!this.modelMethods.isEmpty()) {
//看下面详细解析
InvocableHandlerMethod modelMethod = getNextModelMethod(container).getHandlerMethod();
ModelAttribute ann = modelMethod.getMethodAnnotation(ModelAttribute.class);
if (container.containsAttribute(ann.name())) {
if (!ann.binding()) {
container.setBindingDisabled(ann.name());
}
continue;
}
Object returnValue = modelMethod.invokeForRequest(request, container);
if (!modelMethod.isVoid()){
String returnValueName = getNameForReturnValue(returnValue, modelMethod.getReturnType());
if (!ann.binding()) {
container.setBindingDisabled(returnValueName);
}
if (!container.containsAttribute(returnValueName)) {
container.addAttribute(returnValueName, returnValue);
}
}
}
}
先来看看getNextModelMethod方法
private ModelMethod getNextModelMethod(ModelAndViewContainer container) {
//先循环所有的modelMethod方法
for (ModelMethod modelMethod : this.modelMethods) {
//检查依赖,如果这个container里面有这个modelMethod入参需要全部的参数,就可以执行
if (modelMethod.checkDependencies(container)) {
if (logger.isTraceEnabled()) {
logger.trace("Selected @ModelAttribute method " + modelMethod);
}
//直接返回执行
this.modelMethods.remove(modelMethod);
return modelMethod;
}
}
//如果不满足,说明入参在这个container中有一些没找到,打印日志
ModelMethod modelMethod = this.modelMethods.get(0);
if (logger.isTraceEnabled()) {
logger.trace("Selected @ModelAttribute method (not present: " +
modelMethod.getUnresolvedDependencies(container)+ ") " + modelMethod);
}
this.modelMethods.remove(modelMethod);
return modelMethod;
}
public boolean checkDependencies(ModelAndViewContainer mavContainer) {
// 先看dependencies怎么来的,看看当前这个modelmethod需要入参,这个model有没有
for (String name : this.dependencies) {
if (!mavContainer.containsAttribute(name)) {
return false;
}
}
return true;
}
//this.dependencies在构造函数里面加载的
public ModelMethod(InvocableHandlerMethod handlerMethod) {
this.handlerMethod = handlerMethod;
//取得@modelAttribute修饰方法的入参
for (MethodParameter parameter : handlerMethod.getMethodParameters()) {
//看看@modelAttribute是不是入参也有@modelAttribute修饰,这样就有依赖关系了
if (parameter.hasParameterAnnotation(ModelAttribute.class)) {
this.dependencies.add(getNameForParameter(parameter));
}
}
}
总结下来就是,一共三步
- 先从sessionAttribute中取得参数,合并到model中
- 在执行标记的modelAttribute的方法
- 最后给注释了@modelAttribute的入参赋值