2021-03-29@ModelAttribute注解源码分析

运行流程

  1. 执行@ModelAttribute注解修饰的方法:从数据库中取出对象,把对象放入到Map中,键为:user
  2. SpringMVC从Map中取出User对象,并把表单的请求参数赋给该User对象的对应的属性。
  3. SpringMVC把上述对象传入目标方法的参数。
    注意:
    1. 在@ModelAttribute修饰的方法中,放入到Map时的键需要和目标方法入参类型的第一个字母小写的字符串一致。
    2. SpringMVC在调用目标方法之前,会逐个调用在方法上标注了@ModelAttribute的方法。

代码

@ModelAttribute注解修饰的方法:

@ModelAttribute
	public void getUser(@RequestParam(value="id",required=false) Integer id, 
			Map<String, Object> map){
		System.out.println("modelAttribute method");
		if(id != null){
			//模拟从数据库中获取对象
			User user = new User(1, "Tom", "123456", "tom@atguigu.com", 12);
			System.out.println("从数据库中获取一个对象: " + user);
			
			map.put("user", user);
		}
	}

目标处理方法:

@RequestMapping("/testModelAttribute")
	public String testModelAttribute(User user){
		System.out.println("修改: " + user);
		return SUCCESS;
	}

源码分析流程

  • 调用@ModelAttribute注解修饰的方法,实际上把@ModelAttribute方法中Map中的数据放在了implicitModel中。
  • 解析请求处理器的目标参数,实际上该目标参数来自于WebDataBinder对象的target属性。
    (1)创建WebDataBinder对象:
    ①确定objectName属性:若传入的attrName属性值为“”,则objectName为类名第一个字母小写。
    注意:attrName,若目标方法的POJO属性使用了@ModelAttribute来修饰,则attrName值即为@ModelAttributevalue属性值。
    ②确定target属性:
  • implicitModel中查找attrName对应的属性值,若存在,ok。
  • 若不存在:则验证当前Handler是否使用了@SessionAttributes进行修饰,若使用了,则尝试从Session中获取attrName所对应的属性值,若session中没有对应的属性值,则抛出了异常。
  • Handler没有使用@SessionAttribute进行修饰,或@SessionAttribute中没有使用value值指定的keyattrName相匹配,则通过反射创建了POJO对象。
    (2)SpringMVC把表单的请求参数赋给了WebDataBindertarget对应的属性。
    (3)SpringMVC会把WebDataBinderattrNametarget给到implicitModel。进而传到request域对象中。
    (4)把WebDataBindertarget作为参数传递给目标方法的入参。

结合代码分析

HandlerMethodInvoker.classinvokeHandlerMethod方法源码

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);
				//此时args中的参数还是在数据库中取的
				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);
				}
			}
			//解析请求处理器的目标参数,执行完之后args中的值进行了修改
			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;
		}
	}

首先调用@ModelAttribute注解修饰的方法,将在@ModelAttribute中往Map中放的键值对放入了invokeHandlerMethod方法中的implicitModel参数中,调用该方法时传入了参数implicitModel
在这里插入图片描述
解析请求处理器的目标参数:
在这里插入图片描述
resolveHandlerArguments中的有用操作:创建binder并为args赋值,此时args中的值为表单请求中的了。
在这里插入图片描述
WebDataBinder的创建过程:
在这里插入图片描述
主要有两个属性:target以及objectName
在这里插入图片描述
binder的两个属性target以及objectName赋值:
在这里插入图片描述
确定objectName:
如果name为空,获取类名的第一个字母的小写:name值为user
注意:如果存在@ModelAttribute注解,则attrName值即为@ModelAttribute的value属性值。
在这里插入图片描述

确定target:

  1. 先去implicitModel中去看,有没有name对应的value值,这里是有的,即为在数据库中获取的值。
  2. 不存在的话:若不存在:则验证当前Handler是否使用了@SessionAttributes进行修饰,若使用了,则尝试从Session中获取attrName所对应的属性值,若session中没有对应的属性值,则抛出了异常。
  3. 如果都不存在,就通过反射来创建一个对象。
    此时bindertarget为在数据库中获取的对象:SpringMVC调用doBind方法将表单的请求参数赋值给bindertarget属性。
    age赋表单值,在doBind中调用setAge()方法:
    在这里插入图片描述
    此时args变为已经被表单请求参数赋完值的从数据库中取的对象,
    实际上该目标参数来自于WebDataBinder对象的target属性。
    WebDataBindertarget作为参数传递给目标方法的入参。
    将当前binder中的ObjectNametarget赋值给implicitModel
implicitModel.putAll(binder.getBindingResult().getModel());

其中getModel的具体操作:将当前binder中的ObjectNametarget返回:
在这里插入图片描述

SpringMVC确定目标方法POJO类型入参的过程

  1. 确定一个key
    (1)若目标方法的POJO类型的参数没有使用@ModelAttribute作为修饰,则key为POJO类名第一个字母的小写。
    (2)若使用了@ModelAttribute来修饰,则key@ModelAttribute注解的value属性值。
  2. implicitModel中查找key对应的对象,若存在,则作为入参传入。
    (1)若在@ModelAttribute标记的方法中在Map中保存过,且key和1确定的key一致,则会获取到。
  3. implicitModel中不存在key对应的对象,则检查当前的Handler是否使用@SessionAttributes注解修饰,若使用了该注解,且@SessionAttributes注解的value属性值中包含了key,则会从HttpSession中来获取key所对应的value值,若存在则直接传入到目标方法的入参中,若不存在则将抛出异常。
  4. Handler没有标识@SessionAttributes注解或@SessionAttributes注解的value值中不包含key,则会通过反射来创建POJO类型的参数,传入为目标方法的参数。
  5. SpringMVC会把key和POJO类型的对象保存到implicitModel中,进而会保存到request中。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值