SpringMVC 使用 @ModelAttribute 和 @SessionAttributes 在不同的模型(model)和控制器之间共享数据。

@ModelAttribute 主要有两种使用方式,一种是标注在方法上,一种是标注在 Controller 方法参数上。

当 @ModelAttribute 标记在方法上的时候,该方法将在处理器方法执行之前执行,然后把返回的对象存放在 session 或模型属性中,属性名称可以使用 @ModelAttribute("attributeName") 在标记方法的时候指定,若未指定,则使用返回类型的类名称(首字母小写)作为属性名称。

关于 @ModelAttribute 标记在方法上时对应的属性是存放在 session 中还是存放在模型中,我们来做一个实验,看下面一段代码。

@Controller
@RequestMapping("/myTest")
public class MyController 
{

    @ModelAttribute("world")
    public String getModel() 
    {
       System.out.println("-------------world---------");
       return "world" ;
    }

    @ModelAttribute("age")
    public int getInteger() 
    {
       System.out.println("-------------age---------------");
       return 20;
    }

    @RequestMapping("sayHello")
    public void sayHello(@ModelAttribute("world") String world, @ModelAttribute("age") int age, @ModelAttribute("user") User user, Writer writer, HttpSession session) throws IOException 
    {
       writer.write( "Hello " + world + " ! My name is " + user.getUsername() + ", I am  " + age + " years old.");
       writer.write( "\r" );
       Enumeration enume = session.getAttributeNames();
       while (enume.hasMoreElements())
       {
            writer.write(enume.nextElement() + "\r" );
       }
    }

    @ModelAttribute("user")
    public User getUser()
    {
       System.out.println( "---------getUser-------------" );
       return new User("Tom",20 );
    }
}

当我们请求 /myTest/sayHello.do 的时候使用 @ModelAttribute 标记的方法会先执行,然后把它们返回的对象存放到模型中。最终访问到 sayHello 方法的时候,使用 @ModelAttribute 标记的方法参数都能被正确的注入值。执行结果如下所示:

Hello world! My name is Tom, I am 20 years old.

由执行结果我们可以看出来,此时 session 中没有包含任何属性,也就是说上面的那些对象都是存放在模型属性中,而不是存放在 session 属性中。那要如何才能存放在 session 属性中呢?这个时候我们先引入一个新的概念 @SessionAttributes 。我们在 MyController 类上加上 @SessionAttributes 属性标记哪些是需要存放到 session 中的。看下面的代码:

@Controller
@RequestMapping("/myTest")
@SessionAttributes(value={"userName" , "age"}, types={User.class})
public class MyController 
{

    @ModelAttribute("world")
    public String getModel() 
    {
       System.out.println("-------------world---------");
       return "world" ;
    }

    @ModelAttribute("age")
    public int getInteger() 
    {
       System.out.println( "-------------age---------------" );
       return 20;
    }
   
    @RequestMapping("sayHello")
    public void sayHello(@ModelAttribute( "world") String world, @ModelAttribute("age") int age, @ModelAttribute("user") User user, Writer writer, HttpServletRequest request) throws IOException 
    {
       writer.write( "Hello " + world + " ! My name is " + user.getUsername() + ", I am  " + age + " years old.");
       writer.write( "\r" );
       HttpSession session = request.getSession();
       Enumeration enume = session.getAttributeNames();
       while (enume.hasMoreElements())
       {
            writer.write(enume.nextElement() + "\r" );
       }
       System.out.println(session);
    }

    @ModelAttribute("user")
    public User getUser() 
    {
       System.out.println( "---------getUser-------------" );
       return new User("Tom",20 );
    }
}

在上面代码中我们指定了属性为 userName 或 age 或者类型为 User 的都会放到 Session中,利用上面的代码当我们访问 /myTest/sayHello.do 的时候,结果如下:

Hello world! My name is Tom, I am 20 years old.

仍然没有打印出任何 session 属性,这是怎么回事呢?怎么定义了把模型中属性名为 age 的对象和类型为 User 的对象存到 session 中,而实际上没有加进去呢?难道我们错啦?我们当然没有错,只是在第一次访问 /myTest/sayHello.do 的时候 @SessionAttributes 定义了需要存放到 session 中的属性,而且这个模型中也有对应的属性,但是这个时候还没有加到 session 中,所以 session 中不会有任何属性,等处理器方法执行完成后 Spring 才会把模型中对应的属性添加到 session 中。所以当请求第二次的时候就会出现如下结果:

Hello world! My name is Tom, I am 20 years old.

user
userName
age

当 @ModelAttribute 标记在处理器方法参数上的时候,表示该参数的值将从模型或者 Session 中取对应名称的属性值,该名称可以通过 @ModelAttribute("attributeName") 来指定,若未指定,则使用参数类型的类名称(首字母小写)作为属性名称。

补充:与 Model 相关的常见面试题

SpringMvc中函数的返回值是什么?

答:返回值可以有很多类型有String、ModelAndView,但一般用String比较好。

SpringMvc用什么对象从后台向前台传递数据的?

答:通过ModelMap对象,可以在这个对象里面用put方法,把对象加到里面,前台就可以通过el表达式拿到。

SpringMvc中有个类把视图和数据都合并的一起的,叫什么?

答:ModelAndView。

怎么样把ModelMap里面的数据放入Session里面?

答:可以在类上面加上@SessionAttributes注解,里面包含的字符串就是要放入session里面的key。