标签:SpringMVC @ModelAttribute注解 @RequestAttribute注解 @SessionAttribute注解 ModelAndView
通过ModelAndView对象返回数据到视图在SpringMVC中有一个ModelAndView对象,如其名,Model代表模型,View代表视图,这个名字就很好地解释了该类的作用——它用来存储模型数据以及显示该数据的视图名称。在控制器中调用完模型层处理完用户的请求后,我们可以把结果数据存储在该对象的model属性中,把要返回的视图信息存储在该对象的view属性中,然后让把ModelAndView对象返回给SpringMVC框架。框架则会通过调用Spring配置文件中定义的视图解析器,对该对象进行解析,最后把结果数据传递到指定的视图上,这样我们就可以在视图中获得结果数据并显示出来了。
Spring的配置文件内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<context:component-scan base-package="org.zero01"/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:prefix="/WEB-INF/pages/" p:suffix=".jsp"
/>
</beans>
下例将简单介绍如何使用ModelAndView来存储数据,控制器代码如下:
package org.zero01.test;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class Test {
@RequestMapping("/test.do")
// SpringMVC会自动把 ModelAndView 对象传递到方法参数上
public ModelAndView testModelAndView(ModelAndView modelAndView){
// 设置视图名称
modelAndView.setViewName("index");
// 添加数据
modelAndView.addObject("name","Jon");
modelAndView.addObject("age","15");
modelAndView.addObject("address","USA");
return modelAndView;
}
}
SpringMVC最后会把ModelAndView里的数据拿出来存储到request对象中,所以在视图中我们可以通过EL表达式中直接获取数据,index.jsp内容如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Test</title>
</head>
<body>
<div>
<p>name::
<span>${requestScope.name}</span>
</p>
<p>age::
<span>${requestScope.age}</span>
</p>
<p>address::
<span>${requestScope.address}</span>
</p>
</div>
</body>
</html>
浏览器访问结果如下:
如果不想在方法上声明ModelAndView参数,也可以自己new一个,并且可以直接在构造器中指定视图名称,示例:
package org.zero01.test;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class Test {
@RequestMapping("/test.do")
public ModelAndView testModelAndView(){
// 构造器中可以设置视图名称
ModelAndView modelAndView = new ModelAndView("index");
// 添加数据
modelAndView.addObject("name","Jon");
modelAndView.addObject("age","15");
modelAndView.addObject("address","USA");
return modelAndView;
}
}
以上只是使用到了其中一个构造器,ModelAndView总共提供了7个构造器,这些多样的构造器让ModelAndView使用起来更便利。
例如,如果当我们只需要返回一个模型数据时,可以使用以下这个构造器:
public class ModelAndView {
...
public ModelAndView(String viewName, String modelName, Object modelObject) {
this.view = viewName;
this.addObject(modelName, modelObject);
}
...
}
示例:
package org.zero01.test;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class Test {
@RequestMapping("/test.do")
public ModelAndView testModelAndView() {
return new ModelAndView("index","name","Jon");
}
}
如果模型层处理完数据之后,返回的是一个Map的实现类对象,例如HashMap集合等,就可以使用以下这个构造器:
public class ModelAndView {
...
public ModelAndView(String viewName, Map<String, ?> model) {
this.view = viewName;
if (model != null) {
this.getModelMap().addAllAttributes(model);
}
}
...
}
示例:
package org.zero01.test;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import java.util.Map;
@Controller
public class Test {
@RequestMapping("/test.do")
public ModelAndView testModelAndView() {
// 模型层返回了一个Map的实现类对象
Map<String, Object> dataMap = new TestModule().getModuleData();
return new ModelAndView("index", dataMap);
}
}
除了以上介绍的ModelAndView可以返回数据到视图之外,SpringMVC中的Model也可以返回数据到视图。虽然两者都可以完成返回数据到视图的任务,但是它们区别挺大的,ModelAndView是一个实体类,而Model则是一个接口,Model没有指定视图的功能,也就是不能像ModelAndView那样指定视图名称。
而且执行到AnnotationMethodHandlerAdapter类中的invokeHandlerMethod方法时,Model中的数据最终还是会被存储到ModelAndView里。而作为存储模型数据以及视图名称的ModelAndView对象会在DispatcherServlet中被取出,然后DispatcherServlet会先把模型数据存储在request对象中,接着通过视图解析器转发到具体的视图上。
虽然Model是个接口,不过我们并不需要去实现Model接口,只需要在方法参数上进行声明,SpringMVC就会自动帮我们把Model对象传递过来,然后调用相应的方法存储数据即可。
代码示例:
package org.zero01.test;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class Test {
@RequestMapping("/test.do")
public String testModel(Model model) {
model.addAttribute("name","Jon");
model.addAttribute("age","15");
model.addAttribute("address","USA");
return "index";
}
}
我们来看一下详细的执行过程,看看模型数据最后是否真的会被存储在request对象中。在以上代码中的 return "index";
那一行打个断点,然后通过debug运行。如下,我们可以看到,在DispatcherServlet的doDispatch方法中视图名称以及模型数据是存储在ModelAndView对象中的,而不是Model中:
而ModelAndView对象中的模型数据则会被存储在HttpServletRequest对象中,所以我们才可以在视图上直接获取结果数据。这一点我们也可以通过debug来看到:
1.首先ModelAndView对象被拿出来之后,就会调用processDispatchResult方法,将ModelAndView对象传递到该方法中进行处理:
2.在processDispatchResult方法中,如果ModelAndView对象不为空的话,就会调用render方法,并把ModelAndView对象传递过去:
3.在render方法中,会把ModelAndView对象中的模型数据拿出来,传递到View对象中的render方法中(这个View的实现类是AbstractView):
4.在view对象中的render方法中,会把模型数据传递到createMergedOutputModel方法中进行合并:
5.在createMergedOutputModel方法中会把几个数据合并到一个集合里,但是这里除了model之外其他都为空,所以只合并了model数据:
在控制台中可以看到mergedModel对象里的数据如下:
6.得到mergedModel对象后,继续往下执行,接着就会调用renderMergedOutputModel方法,把mergedModel、request以及response对象都传递过去:
7.但是renderMergedOutputModel是一个抽象方法,所以该方法的调用被传递到了它的一个子类中(该子类是InternalResourceView),这个子类实现的renderMergedOutputModel方法中调用了exposeModelAsRequestAttributes方法并把模型数据和request对象传递了过去:
8.而exposeModelAsRequestAttributes方法没有被子类重写,所以调用的是父类的,也就是AbstractView类的,所以调用被传递到了AbstractView类的exposeModelAsRequestAttributes方法中。就是在这个方法中,模型数据被一个一个的放入到了HttpServletRequest对象中:
我们可以来看看将模型数据添加到request对象中的具体过程:
第一个数据:
控制台:
第二个数据:
控制台:
第三个数据:
控制台:
以上的一系列复杂的流程走完之后,我们在视图中,才可以直接使用EL表达式进行拿值:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Test</title>
</head>
<body>
<div>
<p>name::
<span>${requestScope.name}</span>
</p>
<p>age::
<span>${requestScope.age}</span>
</p>
<p>address::
<span>${requestScope.address}</span>
</p>
</div>
</body>
</html>
浏览器访问结果:
使用Map返回数据与使用Model类似,也是只需要在方法上声明Map参数,然后添加数据即可。SpringMVC会自动把对象传递进来,而且返回的数据也是一样会存储到request对象中,示例:
package org.zero01.test;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.Map;
@Controller
public class Test {
@RequestMapping("/test.do")
public String testMap(Map map) {
map.put("name","Jon");
map.put("age","15");
map.put("address","USA");
return "index";
}
}
jsp代码和之前一样,略。浏览器访问结果如下:
从以上的实验中,我们可以得知,默认情况下SpringMVC会将模型中的数据存储到request对象中。而request对象里存储的数据是一次性的,当一个请求结束后,数据就失效了,如果要跨页面使用,那么就需要使用到session了。@SessionAttributes注解就是用来将模型中的数据存储一份到session对象中,这个注解是写在类上的。
这个注解中有两个属性:names和types,names属性用于指定哪些名称的数据需要存储到session对象中,如下示例:
package org.zero01.test;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttributes;
@SessionAttributes(names = {"name", "age", "address"})
@Controller
public class Test {
@RequestMapping("/test.do")
public String testModel(Model model) {
model.addAttribute("name", "Max");
model.addAttribute("age", "20");
model.addAttribute("address", "北京");
return "index";
}
}
index.jsp内容如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Test</title>
</head>
<body>
<div>
<p>request_name::
<span>${requestScope.name}</span>
</p>
<p>request_age::
<span>${requestScope.age}</span>
</p>
<p>request_address::
<span>${requestScope.address}</span>
</p>
<hr>
<p>session_name::
<span>${sessionScope.name}</span>
</p>
<p>session_age::
<span>${sessionScope.age}</span>
</p>
<p>session_address::
<span>${sessionScope.address}</span>
</p>
</div>
</body>
</html>
浏览器访问结果如下:
types属性则是指定哪些类型的数据需要存储到session对象中,如下示例:
package org.zero01.test;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttributes;
// 只要是Student类型的数据就存储一份到session中
@SessionAttributes(types = Student.class)
@Controller
public class Test {
@RequestMapping("/test.do")
public String testModel(Model model) {
Student student = new Student();
student.setSname("Max");
student.setAge(20);
student.setAddress("北京");
model.addAttribute("student", student);
return "index";
}
}
index.jsp内容如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Test</title>
</head>
<body>
<div>
<p>request_name::
<span>${requestScope.student.sname}</span>
</p>
<p>request_age::
<span>${requestScope.student.age}</span>
</p>
<p>request_address::
<span>${requestScope.student.address}</span>
</p>
<hr>
<p>session_name::
<span>${sessionScope.student.sname}</span>
</p>
<p>session_age::
<span>${sessionScope.student.age}</span>
</p>
<p>session_address::
<span>${sessionScope.student.address}</span>
</p>
</div>
</body>
</html>
浏览器访问结果和之前一样,略。
这个@SessionAttribute注解与上面介绍的@SessionAttributes注解名字相似但作用相反,它用于在session对象中取值,并且是写在方法参数上的,如下示例:
package org.zero01.test;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttribute;
import org.springframework.web.bind.annotation.SessionAttributes;
@SessionAttributes(types = Student.class)
@Controller
public class Test {
@RequestMapping("/test.do")
public String testModel(Model model) {
Student student = new Student();
student.setSname("Max");
student.setAge(20);
student.setAddress("北京");
model.addAttribute("student", student);
return "redirect:/test2.do";
}
@RequestMapping("/test2.do")
public String testModel2(@SessionAttribute Student student) {
System.out.println(student.getSname());
System.out.println(student.getAge());
System.out.println(student.getAddress());
return "index";
}
}
先访问/test.do,控制台打印结果如下:
Max
20
北京
@RequestAttribute注解使用在方法的参数上,该注解可以从request对象中拿取预先存在的数据,然后绑定到配置该注解的参数上。
我们需要一个过滤器事先在request对象中存储一些数据,过滤器代码如下:
package org.zero01.test;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
@WebFilter("/test.do")
public class TestFilter implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
request.setAttribute("name", "zero");
request.setAttribute("age", 15);
chain.doFilter(request, response);
}
public void destroy() {
}
}
控制器代码如下:
package org.zero01.test;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class Test {
@RequestMapping("/test.do")
public String testModel(
@RequestAttribute("name") String name,
@RequestAttribute("age") int age
) {
System.out.println("name is: " + name);
System.out.println("age is: " + age);
return "index";
}
}
控制台打印结果如下:
name is: zero
age is: 15
注意:这个注解在Spring MVC5版本以上才支持,5以下的版本是不支持的,例如4版本虽然也有这个注解,但却是无效的,无法获取到request对象中的数据。
这个@ModelAttribute注解可以写在方法上或参数上,当该注解写在方法上时,那么配置了该注解的方法就会比配置@RequestMapping注解的方法要先执行。所以我们通过这个注解的特性可以事前配置一些公共的数据,或补全一些数据参数什么的。如果该注解是写在方法参数上,则是从Model对象中取出预先存在的数据绑定对应的参数上。示例:
package org.zero01.test;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
@Controller
public class Test {
@ModelAttribute
public void beforeTestModel(HttpServletRequest request, Model model) {
System.out.println("beforeTestModel方法执行了...");
request.setAttribute("name", "zero");
model.addAttribute("age", 15);
}
@RequestMapping("/test.do")
public String testModel(@RequestAttribute("name") String name, @ModelAttribute("age") String age) {
System.out.println("testModel方法执行了...");
System.out.println("reques --- name is: " + name);
System.out.println("model --- age is: " + age);
return "index";
}
}
控制台打印结果如下:
beforeTestModel方法执行了...
testModel方法执行了...
reques --- name is: zero
model --- age is: 15
如上,从控制台打印结果的结果,可以看到,@ModelAttribute注解配置的方法的确是先执行的。如果存在多个@ModelAttribute注解配置的方法,则是会按从上至下的顺序进行执行。
标签:SpringMVC @ModelAttribute注解 @RequestAttribute注解 @SessionAttribute注解 ModelAndView
原文地址:http://blog.51cto.com/zero01/2088722