跳到主要内容

18、SpringMVC源码分析 - @ModelAttribute的使用及原理

一、使用

1、给ModelMap添加属性

可以通过给方法参数Model添加属性,也可以通过方法上注解@ModelAttribute来指定model的属性名称,返回值会作为属性值,下面方法执行后,在springmvc中ModelMap就会存在两个model。
在Controller中定义方法:

	//指定返回值属性为user1
	@ModelAttribute("user1")
    public User getUser1(Model model) {
   
     
    	//给model添加属性userId
        model.addAttribute("userId", 10);
        User user = new User();
        user.setId(1);
        return user;
    }

2、使用ModelMap中的model
在方法参数上加上@ModelAttribute注解,并值指定名称

	@GetMapping(value = "/modelAttribute/test", produces = {
   
     MediaType.APPLICATION_JSON_VALUE})
    public ResponseEntity modelAttributeTest(Model model, @ModelAttribute(name = "user1") User user1, @ModelAttribute(name = "user2") User user2) throws BaseException {
   
     
        ResponseEntity responseEntity = new ResponseEntity();
        System.out.println("userId: " + model.getAttribute("userId"));
        System.out.println("user1: " + user1);
        System.out.println("user2: " + user2);
        return responseEntity;
    }

结果

userId: 10
user1: User(id=1, username=null, password=null, role=null, email=null, phone=188, realName=null, unit=null)
user2: User(id=2, username=null, password=null, role=null, email=null, phone=188, realName=null, unit=null)

3、全局的ModelAttribute

@RestControllerAdvice
public class ControllerAdviceTest {
   
     

    @ModelAttribute("user2")
    public User getUser() {
   
     
        User user = new User();
        user.setId(2);
        return user;
    }
}

二、@ModelAttribute方法解析

1、查找@ModelAttribute方法
在RequestMappingHandlerAdapter调用invokeHandlerMethod( )方法中,会组装ModelFactory

ModelFactory  modelFactory = getModelFactory(handlerMethod, binderFactory);


	private ModelFactory getModelFactory(HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) {
   
     
		SessionAttributesHandler sessionAttrHandler = getSessionAttributesHandler(handlerMethod);
		Class<?> handlerType = handlerMethod.getBeanType();
		Set<Method> methods = this.modelAttributeCache.get(handlerType);
		if (methods == null) {
   
     
			//查找controller中标识有@ModelAttribute注解,并且没有@RequestMapping注解的方法
			methods = MethodIntrospector.selectMethods(handlerType, MODEL_ATTRIBUTE_METHODS);
			this.modelAttributeCache.put(handlerType, methods);
		}
		List<InvocableHandlerMethod> attrMethods = new ArrayList<>();
		// Global methods first
		//从@ControllerAdvice类中查找全局的@ModelAttribute注解方法
		this.modelAttributeAdviceCache.forEach((controllerAdviceBean, methodSet) -> {
   
     
			if (controllerAdviceBean.isApplicableToBeanType(handlerType)) {
   
     
				Object bean = controllerAdviceBean.resolveBean();
				for (Method method : methodSet) {
   
     
					attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
				}
			}
		});
		for (Method method : methods) {
   
     
			Object bean = handlerMethod.getBean();
			attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
		}
		return new ModelFactory(attrMethods, binderFactory, sessionAttrHandler);
	}

2、调用@ModelAttribute方法

同样在RequestMappingHandlerAdapter调用invokeHandlerMethod( )方法中

modelFactory.initModel(webRequest, mavContainer, invocableMethod);

public void initModel(NativeWebRequest request, ModelAndViewContainer container, HandlerMethod handlerMethod)
			throws Exception {
   
     

		//@SessionAttributes 标识的一些属性名
		Map<String, ?> sessionAttributes = this.sessionAttributesHandler.retrieveAttributes(request);
		//将sessionAttributes合并到modelMap中
		container.mergeAttributes(sessionAttributes);
		//调用@ModelAttribute方法
		invokeModelAttributeMethods(request, container);

		//从sessionAttributesHandler查找属性
		for (String name : findSessionAttributeArguments(handlerMethod)) {
   
     
			//ModelMap中不包含该属性时,从sessionAttributesHandler获取
			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);
			}
		}
	}

private void invokeModelAttributeMethods(NativeWebRequest request, ModelAndViewContainer container)
			throws Exception {
   
     

		while (!this.modelMethods.isEmpty()) {
   
     
			InvocableHandlerMethod modelMethod = getNextModelMethod(container).getHandlerMethod();
			ModelAttribute ann = modelMethod.getMethodAnnotation(ModelAttribute.class);
			Assert.state(ann != null, "No ModelAttribute annotation");
			if (container.containsAttribute(ann.name())) {
   
     
				if (!ann.binding()) {
   
     
					container.setBindingDisabled(ann.name());
				}
				continue;
			}
			
			//调用方法
			Object returnValue = modelMethod.invokeForRequest(request, container);
			if (modelMethod.isVoid()) {
   
     
				if (StringUtils.hasText(ann.value())) {
   
     
					if (logger.isDebugEnabled()) {
   
     
						logger.debug("Name in @ModelAttribute is ignored because method returns void: " +
								modelMethod.getShortLogMessage());
					}
				}
				continue;
			}

			//参数名
			String returnValueName = getNameForReturnValue(returnValue, modelMethod.getReturnType());
			if (!ann.binding()) {
   
     
				container.setBindingDisabled(returnValueName);
			}
			if (!container.containsAttribute(returnValueName)) {
   
     
				//添加到ModelMap中
				container.addAttribute(returnValueName, returnValue);
			}
		}
	}

二、@ModelAttribute参数解析

在ModelAttributeMethodProcessor类中

public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
   
     

		Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer");
		Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory");

		//参数名
		String name = ModelFactory.getNameForParameter(parameter);
		ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
		if (ann != null) {
   
     
			mavContainer.setBinding(name, ann.binding());
		}

		Object attribute = null;
		BindingResult bindingResult = null;

		if (mavContainer.containsAttribute(name)) {
   
     
			//从ModelMap中获取参数值
			attribute = mavContainer.getModel().get(name);
		}
		else {
   
     
			// Create attribute instance
			try {
   
     
				attribute = createAttribute(name, parameter, binderFactory, webRequest);
			}
			catch (BindException ex) {
   
     
				if (isBindExceptionRequired(parameter)) {
   
     
					// No BindingResult parameter -> fail with BindException
					throw ex;
				}
				// Otherwise, expose null/empty value and associated BindingResult
				if (parameter.getParameterType() == Optional.class) {
   
     
					attribute = Optional.empty();
				}
				else {
   
     
					attribute = ex.getTarget();
				}
				bindingResult = ex.getBindingResult();
			}
		}

		if (bindingResult == null) {
   
     
			// Bean property binding and validation;
			// skipped in case of binding failure on construction.
			//创建绑定器
			WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
			if (binder.getTarget() != null) {
   
     
				if (!mavContainer.isBindingDisabled(name)) {
   
     
					bindRequestParameters(binder, webRequest);
				}
				//参数校验
				validateIfApplicable(binder, parameter);
				if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
   
     
					throw new BindException(binder.getBindingResult());
				}
			}
			// Value type adaptation, also covering java.util.Optional
			//参数类型不一致时进行转换
			if (!parameter.getParameterType().isInstance(attribute)) {
   
     
				attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
			}
			bindingResult = binder.getBindingResult();
		}

		// Add resolved attribute and BindingResult at the end of the model
		Map<String, Object> bindingResultModel = bindingResult.getModel();
		mavContainer.removeAttributes(bindingResultModel);
		mavContainer.addAllAttributes(bindingResultModel);

		return attribute;
	}