SpringMVC——模型数据处理

一、模型数据处理的方式
 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对象
SpringMVC——模型数据处理_第1张图片
 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 }  

  注意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="[email protected]"/>
    <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="[email protected]"/>
    <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","[email protected]",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","[email protected]",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","[email protected]",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", "[email protected]", 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;
}  

你可能感兴趣的:(SpringMVC)