ModelAttribute 源码详解

@ModelAttribute修饰的方法会在执行每个目标方法之前被springMVC调用!

 先看例子:

模拟修改操作,原始数据为用户id2,姓名zs,密码pass123,年龄12,默认密码不能被修改,表单回显,模拟操作直接在表单填写对应的属性值

<form action="springmvc/testmodelattribute" method="post">
		<input type="hidden" name="id" value="2"/>
		Name:<input type="text" name="name" value="zs"/><br>
		age :<input type="text" name="age" value="12"/><br>
		<input type="submit" value="submit"/>
	</form>
目标方法:

@RequestMapping("/testmodelattribute")
	public String textModelAttribute(User user) {
		System.out.println("修改" + user);
		return "success";
	}
在目标方法的输出结果是
修改User [id=2, name=zs, pass=null, Age=15]
很明显我们没有给password赋值,那么这个值是为null的

接下来看一下加上@ModelAttribute注解修饰的方法,来模拟从数据库中查询了一个对象,然后在页面上修改age的值

@ModelAttribute
	public String gectUserFromDB(@RequestParam(value = "id", required = false) Integer id, Map<String, User> map) {
		if (id != null) {
			User user = new User("zs", "pass123", 12);
			System.out.println("从数据库中获得一个对象" + user);
			map.put("user", user);
		}
		return "success";
	}
再看一下输出结果
从数据库中获得一个对象User [id=2, name=zs, pass=pass123, Age=12]
修改User [id=2, name=zs, pass=pass123, Age=15]
看到了把表单的值赋给了模拟从数据库中查询的记录。
话说回来, 那么到底是什么原理, 翻查源码看一下;我们现在 @ModelAttribute修饰的方法内打一个断点。


看到停在了invokeHandlerMethod这个方法,进入这个方法看一下

public final Object invokeHandlerMethod(Method handlerMethod, Object handler,
			NativeWebRequest webRequest, ExtendedModelMap implicitModel) throws Exception {

		Method handlerMethodToInvoke = BridgeMethodResolver.findBridgedMethod(handlerMethod);
		try {
			boolean debug = logger.isDebugEnabled();
			for (String attrName : this.methodResolver.getActualSessionAttributeNames()) {
				Object attrValue = this.sessionAttributeStore.retrieveAttribute(webRequest, attrName);
				if (attrValue != null) {
					implicitModel.addAttribute(attrName, attrValue);
				}
			}
			for (Method attributeMethod : this.methodResolver.getModelAttributeMethods()) {
				Method attributeMethodToInvoke = BridgeMethodResolver.findBridgedMethod(attributeMethod);
				Object[] args = resolveHandlerArguments(attributeMethodToInvoke, handler, webRequest, implicitModel);
				if (debug) {
					logger.debug("Invoking model attribute method: " + attributeMethodToInvoke);
				}
				String attrName = AnnotationUtils.findAnnotation(attributeMethod, ModelAttribute.class).value();
				if (!"".equals(attrName) && implicitModel.containsAttribute(attrName)) {
					continue;
				}
				ReflectionUtils.makeAccessible(attributeMethodToInvoke);
				Object attrValue = attributeMethodToInvoke.invoke(handler, args);
				if ("".equals(attrName)) {
					Class<?> resolvedType = GenericTypeResolver.resolveReturnType(attributeMethodToInvoke, handler.getClass());
					attrName = Conventions.getVariableNameForReturnType(attributeMethodToInvoke, resolvedType, attrValue);
				}
				if (!implicitModel.containsAttribute(attrName)) {
					implicitModel.addAttribute(attrName, attrValue);
				}
			}
			Object[] args = resolveHandlerArguments(handlerMethodToInvoke, handler, webRequest, implicitModel);
			if (debug) {
				logger.debug("Invoking request handler method: " + handlerMethodToInvoke);
			}
			ReflectionUtils.makeAccessible(handlerMethodToInvoke);
			return handlerMethodToInvoke.invoke(handler, args);
		}
		catch (IllegalStateException ex) {
			// Internal assertion failed (e.g. invalid signature):
			// throw exception with full handler method context...
			throw new HandlerMethodInvocationException(handlerMethodToInvoke, ex);
		}
		catch (InvocationTargetException ex) {
			// User-defined @ModelAttribute/@InitBinder/@RequestMapping method threw an exception...
			ReflectionUtils.rethrowException(ex.getTargetException());
			return null;
		}
	}
24行,在执行@ModelAttribute修饰的方法之前,传入的两个参数一个是传入的id,一个是BindingAwareModelMap


其中一个为我们用@RequestParam接受的参数,为页面输入的ID属性,还有一个就是BindingAwareModelMap的map集合

15行,使用2行参数中的implicitModel进行关联,实际上在执行这个invokeHandlerMethod方法的时候就传入了一个map对象,这个map为ExtendedModelMap类型的implicitModel,这个map就是刚才的那个BindingAwareModelMap

public final Object invokeHandlerMethod(Method handlerMethod, Object handler,
			NativeWebRequest webRequest, ExtendedModelMap implicitModel) throws Exception {

22行,调用@ModelAttribute修饰的方法,把仿从数据库查放到map总的user,放到了implicitModel中(args和implicitModel关联)。


执行完用@ModelAttribute修饰的方法后,那么紧接着就该去执行目标方法了。

我们在页面上修改一个age的值,紧接着在setAge方法打一个断点


看到在setAge的时候调用了刚才方法中的resolveHandlerArguments方法。然后把查出来的记录传入方法(代码太多用省略号表示)。

33行,去执行目标方法,这时候传入的map还是从仿数据库查询出来的记录

private Object[] resolveHandlerArguments(Method handlerMethod, Object handler,
			NativeWebRequest webRequest, ExtendedModelMap implicitModel) throws Exception {
	Class<?>[] paramTypes = handlerMethod.getParameterTypes();
	Object[] args = new Object[paramTypes.length];

	.....

		if (paramName != null) {
			args[i] = resolveRequestParam(paramName, required, defaultValue, methodParam, webRequest, handler);
		}
		else if (headerName != null) {
			args[i] = resolveRequestHeader(headerName, required, defaultValue, methodParam, webRequest, handler);
		}
		else if (requestBodyFound) {
			args[i] = resolveRequestBody(methodParam, webRequest, handler);
		}
		else if (cookieName != null) {
			args[i] = resolveCookieValue(cookieName, required, defaultValue, methodParam, webRequest, handler);
		}
		else if (pathVarName != null) {
			args[i] = resolvePathVariable(pathVarName, methodParam, webRequest, handler);
		}
		else if (attrName != null) {
			WebDataBinder binder =
					resolveModelAttribute(attrName, methodParam, implicitModel, webRequest, handler);
			boolean assignBindingResult = (args.length > i + 1 && Errors.class.isAssignableFrom(paramTypes[i + 1]));
			if (binder.getTarget() != null) {
				doBind(binder, webRequest, validate, validationHints, !assignBindingResult);
			}
			args[i] = binder.getTarget();
			if (assignBindingResult) {
				args[i + 1] = binder.getBindingResult();
				i++;
			}
			implicitModel.putAll(binder.getBindingResult().getModel());
		}
	}
	return args;
}

30行,断点过后先来看一下执行结果

看到执行完这个代码后, 就已经是从修改age后提交表单过来的值,此时现在这个密码也是从数据库中获取的。那这个值是怎么赋的呢?
从这行看得到,是从binder中获取了一个target属性。先来看看这个binder是个什么东西。
25行,通过resolveModelAttribute方法获取的WebDataBinder对象。

private WebDataBinder resolveModelAttribute(String attrName, MethodParameter methodParam,
		ExtendedModelMap implicitModel, NativeWebRequest webRequest, Object handler) throws Exception {

	// Bind request parameter onto object...
	String name = attrName;
	if ("".equals(name)) {
		name = Conventions.getVariableNameForParameter(methodParam);
	}
	Class<?> paramType = methodParam.getParameterType();
	Object bindObject;
	if (implicitModel.containsKey(name)) {
		bindObject = implicitModel.get(name);
	}
	else if (this.methodResolver.isSessionAttribute(name, paramType)) {
		bindObject = this.sessionAttributeStore.retrieveAttribute(webRequest, name);
		if (bindObject == null) {
			raiseSessionRequiredException("Session attribute '" + name + "' required - not found in session");
		}
	}
	else {
		bindObject = BeanUtils.instantiateClass(paramType);
	}
	WebDataBinder binder = createBinder(webRequest, bindObject, name);
	initBinder(handler, name, binder, webRequest);
	return binder;
}

最后看到23行,创建了这个对象。

protected WebDataBinder createBinder(NativeWebRequest webRequest, Object target, String objectName)
		throws Exception {

	return new WebRequestDataBinder(target, objectName);
}
看到这个binder对象中其中的两个属性就是一个target和objectName。到底是怎么确定这个两个属性的,

回过头来看创建,target 对应bindObject ,objectName对应name。


4行,从方法参数中获取这个名字,发现是一个空的字符串,进入下面的方法,获取了我们的那个类名首字母小写。

这个name是上一个方法传入的。我们回到刚才的方法,在57行,看到可以有ModelAttribute的value属性得到。前提就是24行, 参数是注解的并且注解里面有一个ModelAttribute
此处可略过...其实就是在目标方法的形参上也添加一个ModelAttribute注解。可以看下

@RequestMapping("/testmodelattribute")
public String textModelAttribute(@ModelAttribute(value="user") User user) {
	System.out.println("修改" + user);
	return "success";
}
言归正传

11行,看implicitModel是否有这个属性值。由于实现在@ModelAttribute方法的map中put了user这个key对应的属性。
在执行完12行,后我们来看一下这个bindObject属性

那么implicitModel不存在,14行,会验证当前Handler是否使用了@SessionAttributs进行修饰。若使用了则尝试从session中获取获取attribute属性中获取,如果获取不到这个值为空,还会抛出一个异常。
21行,若Handler没有使用@ModelAttribute修饰,在@SessionAttributs中也没有找到对应的attr属性,那么就会通过反射来创建这个对象。
23行, 把获取到的这个name,和bindObject传入到方法中来创建这个webDataBinder对象。这个name其实就是刚才看到的那个objecctName属性,而这个bindObject就是那个target属性。执行完在回到刚才的resolveHandlerArguments方法来看一下现在的这个创建好的WebDataBinder对象。

看到objectName和target都已经已经有了值。
46行,doBind方法给WebDataBinder赋表单值。赋值后的WebDataBinder为

看到age已经修改为15。
48行,给args属性赋值,就是从WebDataBinder的target属性。这个args其实就是被请求赋过值的那个从数据库查出来的那个user对象
53行,把objectName,和target赋给了implicitModel。

@Override
public Map<String, Object> getModel() {
	Map<String, Object> model = new LinkedHashMap<String, Object>(2);
	// Mapping from name to target object.
	model.put(getObjectName(), getTarget());
	// Errors instance, even if no errors.
	model.put(MODEL_KEY_PREFIX + getObjectName(), this);
	return model;
}

总结:

  • 调用@ModelAttribute注解修饰的方法,实际上把@ModelAttribute方法中map的数据放到了implicitModel中。
  •  解析请求处理器的目标参数,实际上该目标参数来自于WebDataBinder对象的target属性
  • 创建WebDataBinder对象
  1. 确定objectName属性:若传入attrName属性为空字符串,那么objectName就是类名的第一个字母小写(若目标方法的POJO属性使用了@ModelAttribute来修饰,啧attrName则为@ModelAttribute的value属性值)
  2. 确定target属性:先会在implicitModel中查找attrName对应的属性值。如果不存在,则验证当前Handler是否使用了@SessionAttributs进行修饰,如果使用了则会从session中回去attrName对应的属性值。若session中没有对应的属性值,则会抛出一个异常。若两者都不存在的话, 那么就会通过反射来创建一个POJO的对象。
  • springMVC把表单的请求参数赋给了WebDataBinder的target对应的属性。
  • 接着把WebDataBinder的attrName和target给到implicitModel;
  • 到最后WebDataBinder的target作为参数传递给目标方法的如参。

到此整篇文章也算是到了头, 如果有什么不对的地方, 希望各位学习springMVC的各位帮忙指正。







  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值