• SpringMVC提供了以下几种途径输出模型数据:
    • ModelAndView
    • Map、Model
    • @SessionAttributes:将模型中的某个属性暂存到HttpSession中,以便多个请求之间共享这个属性。
    • @ModelAttribute:将入参的对象放到数据模型中。

1、ModelAndView

package com.xianhuii;

import com.xianhuii.entities.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;

@Controller
@RequestMapping("/springmvc")
public class RequestMappingTest01 {
    private static final String SUCCESS = "success";

    /**
     * 目标方法的返回值可以是ModelAndView类型,其中可以包含视图和模型信息。
     * SpringMVC会吧ModelAndView的model中的数据放入到request域对象中。
     * @return
     */
    @RequestMapping("testModelAndView")
    public ModelAndView testModelAndView() {
        String viewName = SUCCESS;
        ModelAndView modelAndView = new ModelAndView(viewName);
        // 添加模型数据到ModelAndView中
        modelAndView.addObject("time", new Date());
        return modelAndView;
    }
}

2、Map、Model

import java.util.Map;

@Controller
@RequestMapping("/springmvc")
public class RequestMappingTest01 {
    private static final String SUCCESS = "success";

    /**
     * 目标方法可以添加Map类型(实际上也可以是Model、ModelMap类型)对象
     * @param map
     * @return
     */
    @RequestMapping("/testMap")
    public String testMap(Map<String, Object> map) {
        map.put("names", Arrays.asList("Tom", "Jerry", "Mike"));
        return SUCCESS;
    }
}

3、@SessionAttributes

  • 只能放在Controller类上。

  • 源码:

    package org.springframework.web.bind.annotation;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Inherited;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    import org.springframework.core.annotation.AliasFor;
    
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    @Documented
    public @interface SessionAttributes {
        @AliasFor("names")
        String[] value() default {};
    
        @AliasFor("value")
        String[] names() default {};
    
        Class<?>[] types() default {};
    
    }
  • 示例:

    package com.xianhuii;
    
    import com.xianhuii.entities.User;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.*;
    import org.springframework.web.servlet.ModelAndView;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.util.Arrays;
    import java.util.Date;
    import java.util.Map;
    
    @SessionAttributes(value = {"user"}, types = {String.class})
    @Controller
    @RequestMapping("/springmvc")
    public class RequestMappingTest01 {
        private static final String SUCCESS = "success";
    
        /**
         * @SessionAttributes
         *      可以通过属性名指定需要放到会话中的属性(value)
         *      还可以通过模型属性的对象类型指定那些模型属性需要放到会话中(types)
         * @param map
         * @return
         */
        @RequestMapping("/testSessionAttributes")
        public String testSessionAttributes(Map<String, Object> map) {
            User user = new User("Tom", "123456", "tom@qq.com", 18);
            map.put("user", user);
            map.put("school", "tj");
            return SUCCESS;
        }
    
    }

    4、@ModelAttribute

  • 前端:

    <%--模拟修改操作
        1、原始数据为:1, Tom, 123456, tom@qq.com, 18
        2、密码不能被修改。
        3、表单回显,模拟操作直接在表单填写对应的属性值
    --%>
    
    <form action="/springmvc/testModelAttribute" method="post">
      <input type="hidden" name="id", value="1">
      <p>username: <input type="text" name="username" value="Tom"></p>
      <p>email: <input type="text" name="email" value="tom@qq.com"></p>
      <p>age: <input type="text" name="age" value="18"></p>
      <p><input type="submit" value="Submit"></p>
    </form>
  • Controller:

    package com.xianhuii;
    
    import com.xianhuii.entities.User;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.*;
    import org.springframework.web.servlet.ModelAndView;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.util.Arrays;
    import java.util.Date;
    import java.util.Map;
    
    //@SessionAttributes(value = {"user"}, types = {String.class})
    @Controller
    @RequestMapping("/springmvc")
    public class RequestMappingTest01 {
        private static final String SUCCESS = "success";
    
        /**
         * 执行流程:
         *  1、执行@ModelAttribute注解修饰的方法:从数据库中取出对象,把对象放入到Map中,键为:user
         *  2、SpringMVC从Map中取出User对象,并把表单的请求参数赋给该User对象的对应属性。
         *  3、SpringMVC把上述对象传入目标方法的参数。
         *
         * 注意:在@ModelAttribute修饰的方法中,放入到Map时的键,需要和目标方法入参类型的第一个字母小写的字符串一致。
         *
         * 源码分析的流程:
         *  1、调用@ModelAttribute修饰的方法。把@ModelAttribute方法中Map中的数据放在了implicitModel中。
         *  2、解析请求处理器的目标参数。该目标参数来自于WebDataBinder对象的target属性。
         *      2.1、创建WebDataBinder对象:
         *          ①确定objectName属性:若传入的attrName为"",则objectName为类名第一个字母小写。
         *          注意:若目标方法的POJO属性使用了@ModelAttribute修饰,则attrName值即为@ModelAttribute的value属性值。
         *          ②确定target属性:在implicitModel中查找attrName对应的属性值
         *            - 如果存在,ok;
         *            - 如果不存在,则验证当前Handler是否使用了@SessionAttributes进行修饰,
         *              若使用了则尝试从Session中获取attrName锁对应的属性值。若session中没有对应的值,则抛出异常;
         *            - 若Handler没有使用@SessionAttributes进行修饰,或@SessionAttributes中没有使用value值指定
         *              的key和attrName,则通过反射创建POJO对象。
         *      2.2、SpringMVC把表单的请求参数赋给了WebDataBinder的target对应的属性。
         *      2.3、SpringMVC会把WebDataBinder的attrName和target给implicitModel,进而传到request域对象中。
         *      2.4、把WebDataBinder的target作为参数传递给目标方法的入参。
         *
         * 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
         *     所对应的值,若存在则直接传入到目标方法的入参中;若不存在,则将抛出异常。
         *  4、若Handler没有表示@SessionAttributes,或@SessionAttributes的value不包含key,则会通过反射来创
         *     建POJO类型的参数,传入为目标方法的参数。
         *  5、SpringMVC会把key和POJO类型的对象保存到implicitModel中,进而会保存到request中。
         * @param user
         * @return
         */
        @RequestMapping("/testModelAttribute")
        public String testModelAttribute(User user) {
            System.out.println("修改" + user);
            return SUCCESS;
        }
    
        /**
         * 1、有@ModelAttribute标记的方***在每个目标方法执行之前被SpringMVC调用。
         * 2、@ModelAttribute也可以用来修饰目标方法POJO类型的入参,其value属性值有如下的作用:
         * 2.1、SpringMVC会使用value属性值在implicitModel中查找对应的对象,若存在则会直接传入到目标方法的入参中。
         * 2.2、SpringMVC会以value为key,POJO类型的对象为value,存入到request中。
         * @param id
         * @param map
         */
        @ModelAttribute
        public void getUser(@RequestParam(value = "id", required = false) Integer id,
                            Map<String, Object> map) {
            if (id != null) {
                // 模拟从数据库中获取对象
                User user = new User(1, "Tom", "123456", "tom@qq.com", 18);
                map.put("user", user);
            }
        }