一、模型数据处理的方式 1、将控制器方法的返回值类型设置为ModelAndView:通过ModelAndView对象我们既可以设置视图,也可以设置模型数据,模型数据可以在视图(如jsp页面)中通过EL表达式或者jsp表达式获取:
@RequestMapping(value = "/testModelAndView") public ModelAndView testModelAndView(ModelAndView mv) { mv.addObject("modelKey", "modelValue");//模型数据 mv.setViewName("success");//设置视图名 return mv; } //ModelAndView对象可以通过SpringMVC注入也可以自己创建 @RequestMapping(value="/testModelAndView",method=RequestMethod.GET) public ModelAndView testModelAndView(){ ModelAndView mv = new ModelAndView(); mv.addObject("modelKey", "modelValue");//模型数据 mv.setViewName("ok");//设置视图名 return mv; }在视图中获取模型数据:
${modelKey} ${requestScope.modelKey} <%=request.getAttribute("modelKey") %>Tip: 1️⃣ModelAndView.addObject(k,v)方法的本质是request.setAttribute(k,v),所以在视图中可以在request域中获取到设置的k的值 2️⃣ModelAndView对象可以由SpringMVC注入,也可以自己创建
2、处理器方法入参中的Map、Model和ModelMap:处理器方法中的这三个入参是由SpringMVC注入的,也叫做隐含域,我们可以通过它们向模型中放数据。这三个其实是同一个对象,在底层都是BindingAwareModelMap类型的对象,在一个方法中用Map、Model和ModelMap保存的键值对会保存在同一块内存中,在存放数据的时候都是调用了request.setAttribute(k,v);也就是说隐含域操作的是Request对象
@RequestMapping(value = "/testMapModel") public ModelAndView testMapModel(ModelAndView mv, Map<String, Object> m, Model model, ModelMap mm) { m.put("k", "v1"); model.addAttribute("k", "v2"); String k = (String) m.get("k"); System.out.println(k);// 结果是v2 ModelMap modelMap = mv.getModelMap(); Object object = modelMap.get("k"); System.out.println(object);//null System.out.println(modelMap == m);// false System.out.println(model == m);// true System.out.println(mm == m);// true mv.setViewName("success"); return mv; }Tip:通过打印的结果可以看出来Map、Model、ModelMap操作的是同一个对象,但是令人意外的是,SpringMVC为我们注入的这三个对象竟然不是ModelAndView对象的Model或ModelMap属性值,只不过它们都是操作的Request对象 3、@SessionAttributes:这个注解只能放在类上,不能放在方法上,它的value属性值表示将key值为value值指定的这些对象放在Session域中而不再是默认的Request域中(即ModelAndView以及SpringMVC为我们注入的Map、Model、ModelMap中如果放置指定key的对象时,这些对象将会被放在Session中),types属性的值是指定将某一类所有的对象都放在Session域中(key在放置的时候指定),然后根据key的值就可以在视图的Session域中获取到,一般采用value的方式
@Controller @SessionAttributes(value={"user"}) public class Hello { @RequestMapping(value="/testSession") public String testSession(Map<String,Object> map){ map.put("user", "hello every body"); return "hello"; } } ${sessionScope.user } <!--user是key的值-->注意session域中的键值对在requestScope中也可以获取到,因为EL表达式中默认从小的范围开始查找,小的范围找不到就会去大的域中查找 示例:会将key值为user和password以及类型为Hello和String的键值对置于Session域中,前提是放置这些数据的map必须是SpringMVC注入的,若是我们自己创建的Map是不起作用的
@Controller @SessionAttributes(value={"user","password"},types={Hello.class,String.class}) public class Hello { @RequestMapping(value="/testSession") public String testSession(Map<String,Object> map){ System.out.println("hello"); map.put("user", "hello every body"); map.put("hello",new Hello()); return "hello"; } }4、@ModelAttribute:模型属性,看下面的示例——模拟一个根据id修改User对象的操作 ①使用@ModelAttribute之前的代码 表单:
<form action="${pageContext.request.contextPath }/testModelAttr" method="post"> <input type="hidden" name="_method" value="put"/> <input type="hidden" name="id" value="11"/> <input type="text" name="username" value="老王"/> <input type="text" name="email" value="z3@qq.com"/> <input type="submit" value="提交"/> </form>控制器方法:
@RequestMapping(value="/testModelAttr",method=RequestMethod.PUT) public String testModelAttr(User user){ System.out.println(user); return "hello"; }但仅仅这样的话是有问题的,会导致部分数据的丢失,比如说:如果User对象除了id、username、email这三个属性之外还有其他属性,比如age等,但我们在提交表单的时候只给User的id、username、email这三个属性赋值了,这是根据id将username、email的值修改之后是会将其他表单中没有出现的属性的值都置为null的。这就不是修改某个属性了,同时也将别的不相关的属性的值置空了,所以不能这么做。 ②使用@ModelAttribute注解:所有控制器方法执行之前都会调用该注解标注的方法 表单:和之前没什么变化
<form action="${pageContext.request.contextPath }/testModelAttr" method="post"> <input type="hidden" name="_method" value="put"/> <input type="hidden" name="id" value="11"/> <input type="text" name="username" value="老王"/> <input type="text" name="email" value="z3@qq.com"/> <input type="submit" value="提交"/> </form>控制器方法:和之前没什么变化
@RequestMapping(value="/testModelAttr",method=RequestMethod.PUT) public String testModelAttr(User user){ System.out.println(user); return "hello"; }在Controller中多了一个使用@ModelAttribute注解的方法
@ModelAttribute public void getUserById(@RequestParam(value="id") Integer id,Map<String,Object> map){ User user = new User(id,"小李","123456","xli@qq.com",23); map.put("user", user); System.out.println(user); }@ModelAttribute标注的方法的作用就是为了充实被修改的对象的其他属性,以免其他属性的值被置空。由@ModelAttribute注解的方法在每一个控制器方法执行之前都会被调用,因此若想某些控制器方法执行的时候不执行该方法的内部,可以在该方法中的逻辑代码执行之前加一个if条件
@ModelAttribute public void getUserById(@RequestParam(value="id") Integer id,Map<String,Object> map){ if(id != null){ //模拟到数据库中查询:service.getUserById(id); User user = new User(id,"小李","123456","xli@qq.com",23); //这个key是调用正式方法的入参,这一步的作用就是为了充实user对象 map.put("user", user); System.out.println(user); } }加了if判断之后虽然其他没有id请求参数的控制器方法也会调用该@ModelAttribute注解的方法,但是由于不满足代码执行的条件,其内部的代码是不会执行的,相对就提升了效率。要注意的是Map中key的值必须是简单类名的首字母小写,比如:map对象是由SpringMVC注入的那个对象
@Controller @SessionAttributes(types={Hello.class,String.class}) public class Hello { @RequestMapping(value="/testModelAttr",method=RequestMethod.PUT) public String testModelAttr(User user1){ System.out.println(user1); return "hello"; } @ModelAttribute public void getUserById(@RequestParam(value="id") Integer id,Map<String,Object> map){ if(id != null){ //模拟到数据库中查询:service.getUserById(id); User user12 = new User(id,"小李","123456","xli@qq.com",23); map.put("user", user12);//注意key的值必须是简单类名的首字母小写 System.out.println(user12); } } }流程解析:入参中的@RequestParam(value=“id”) Integer id:由@RequestParam标注的形参是从前台传送过来的 ①先调用由@ModelAttribute注解的方法,从数据库中查询要修改的对象,这个对象中的所有属性的值和数据库是保持一致的 ②前端传入的参数注入到由①查询出来的这个对象中,替换同名参数的值 ③将前端参数注入之后的对象传给处理器的处理方法
@ModelAttribute入参的方式:使用入参的方式可以指定key为map中定义的key,就是说不必再强制map中的key一定要是简单类名的首字母小写了
@Controller public class Hello { @RequestMapping(value = "/testModelAttr", method = RequestMethod.PUT) public String testModelAttr(@ModelAttribute(value = "user32") User user12) { System.out.println(user12); return "hello"; } @ModelAttribute public void getUserById(@RequestParam(value = "id") Integer id, Map<String, Object> map) { if (id != null) { // 模拟到数据库中查询:service.getUserById(id); User user1 = new User(id, "小李", "123456", "xli@qq.com", 23); map.put("user32", user1); System.out.println(user1); } } }在开发中其实用到@ModelAttribute的时候并不多,有其他的方式可以杜绝修改不出现在请求参数中的属性值
二、模型数据处理源码分析 模型数据处理主要是调用了底层HandlerMethodInvoker.class类中的两个方法:解析控制器方法的形参的方法resolveHandlerArguments()和解析模型类的属性的方法resolveModelAttribute() 1、resolveHandlerArguments():解析控制器方法的形参
private Object[] resolveHandlerArguments(Method handlerMethod, Object handler,NativeWebRequest webRequest, ExtendedModelMap implicitModel) throws Exception { Class<?>[] paramTypes = handlerMethod.getParameterTypes();//1、获取控制器类方法的参数类型的数组 Object[] args = new Object[paramTypes.length];//2、根据数组的长度创建一个Object数组 for (int i = 0; i < args.length; i++) { //3、根据形参位置和控制器方法创建方法的形参 MethodParameter methodParam = new MethodParameter(handlerMethod, i); //4、根据发现的形参名字初始化形参 methodParam.initParameterNameDiscovery(this.parameterNameDiscoverer); GenericTypeResolver.resolveParameterType(methodParam, handler.getClass()); //4、以下定义是参数的类型 String paramName = null; String headerName = null; boolean requestBodyFound = false; String cookieName = null; String pathVarName = null; String attrName = null; boolean required = false; String defaultValue = null; boolean validate = false; Object[] validationHints = null; int annotationsFound = 0; //5、获取形参的注解 Annotation[] paramAnns = methodParam.getParameterAnnotations(); //6、遍历注解参数 for (Annotation paramAnn : paramAnns) { //7、根据注解类型解析 if (RequestParam.class.isInstance(paramAnn)) { RequestParam requestParam = (RequestParam) paramAnn; paramName = requestParam.value(); required = requestParam.required(); defaultValue = parseDefaultValueAttribute(requestParam.defaultValue()); annotationsFound++; } else if (RequestHeader.class.isInstance(paramAnn)) { RequestHeader requestHeader = (RequestHeader) paramAnn; headerName = requestHeader.value(); required = requestHeader.required(); defaultValue = parseDefaultValueAttribute(requestHeader.defaultValue()); annotationsFound++; } else if (RequestBody.class.isInstance(paramAnn)) { requestBodyFound = true; annotationsFound++; } else if (CookieValue.class.isInstance(paramAnn)) { CookieValue cookieValue = (CookieValue) paramAnn; cookieName = cookieValue.value(); required = cookieValue.required(); defaultValue = parseDefaultValueAttribute(cookieValue.defaultValue()); annotationsFound++; } else if (PathVariable.class.isInstance(paramAnn)) { PathVariable pathVar = (PathVariable) paramAnn; pathVarName = pathVar.value(); annotationsFound++; } //8、当注解类型是ModelAttribute时的操作 else if (ModelAttribute.class.isInstance(paramAnn)) { ModelAttribute attr = (ModelAttribute) paramAnn; //9、将该参数的名字赋值为注解中的value值 attrName = attr.value(); annotationsFound++; } else if (Value.class.isInstance(paramAnn)) { defaultValue = ((Value) paramAnn).value(); } else if (paramAnn.annotationType().getSimpleName().startsWith("Valid")) { validate = true; Object value = AnnotationUtils.getValue(paramAnn); validationHints = (value instanceof Object[] ? (Object[]) value : new Object[] {value}); } } //10、如果有入参注解不唯一的操作,则抛出异常:同一个形参在多个注解中存在 if (annotationsFound > 1) { throw new IllegalStateException("Handler parameter annotations are exclusive choices - " + "do not specify more than one such annotation on the same parameter: " + handlerMethod); } //11、如果没有注解 if (annotationsFound == 0) { Object argValue = resolveCommonArgument(methodParam, webRequest); if (argValue != WebArgumentResolver.UNRESOLVED) { args[i] = argValue; } else if (defaultValue != null) { args[i] = resolveDefaultValue(defaultValue); } else { Class<?> paramType = methodParam.getParameterType(); if (Model.class.isAssignableFrom(paramType) || Map.class.isAssignableFrom(paramType)) { if (!paramType.isAssignableFrom(implicitModel.getClass())) { throw new IllegalStateException("Argument [" + paramType.getSimpleName() + "] is of type " + "Model or Map but is not assignable from the actual model. You may need to switch " + "newer MVC infrastructure classes to use this argument."); } args[i] = implicitModel; } else if (SessionStatus.class.isAssignableFrom(paramType)) { args[i] = this.sessionStatus; } else if (HttpEntity.class.isAssignableFrom(paramType)) { args[i] = resolveHttpEntityRequest(methodParam, webRequest); } else if (Errors.class.isAssignableFrom(paramType)) { throw new IllegalStateException("Errors/BindingResult argument declared " + "without preceding model attribute. Check your handler method signature!"); } else if (BeanUtils.isSimpleProperty(paramType)) { paramName = ""; } else { //12、没有注解又不是上面的几种情况,就将属性名的值赋值为空串"" attrName = ""; } } } //13、形参列表不为空时的操作 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); } //14、属性不为null时的操作 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) { //15、这行代码负责将web资源(请求参数)赋值给之前绑定的对象 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; }2、resolveModelAttribute方法分析:解析模型类的属性,先在隐含域中找,再到session中找,若还是找不到的话则利用反射创建一个对象
private WebDataBinder resolveModelAttribute(String attrName,MethodParameter methodParam,ExtendedModelMap implicitModel, NativeWebRequest webRequest, Object handler) throws Exception { // Bind request parameter onto object...将请求参数绑定到一个对象 String name = attrName; //1、如果请求参数名为""时,请求参数名在resolveHandlerArguments方法中确定 if ("".equals(name)) { //就赋值为参数的类型名首字母小写 name = Conventions.getVariableNameForParameter(methodParam); } Class<?> paramType = methodParam.getParameterType(); Object bindObject; //2、如果请求参不空时,会先在隐含域(@ModelAttribute中放在map中的)中查找,找不到再到session中查找 if (implicitModel.containsKey(name)) { bindObject = implicitModel.get(name); } //3、在session中查找,该name是注解@SessionAttribute中的value, //如果找到了但是却并没有放入到session中与该value对应的对象的话就会抛出异常,有的话就绑定到对象 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 { //如果session中也没有的话就会创建一个Object对象兜底:代码的体现在下一层的方法中 bindObject = BeanUtils.instantiateClass(paramType); } WebDataBinder binder = createBinder(webRequest, bindObject, name); initBinder(handler, name, binder, webRequest); return binder; }