Spring MVC 提供了以下几种途径输出模型数据:
控制器处理方法的返回值如果为 ModelAndView, 则其既包含视图信息,也包含模型数据信息。
下面演示一下ModelAndView的简单使用。
@Controller
public class SpringMVCTest {
public final static String SUCCESS = "success";
/**
* 返回值ModelAndView包含视图信息,也包含模型数据信息。
* SpringMVC会把ModelAndView的数据存放在Request请求域对象中。
*/
@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView() {
String viewName = SUCCESS;
ModelAndView modelAndView = new ModelAndView(viewName);
//添加模型数据到ModelAndView中
modelAndView.addObject("time", new Date());
return modelAndView;
}
}
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Successtitle>
head>
<body>
<h2>Success pageh2>
time: ${requestScope.time }<br/>
body>
html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title heretitle>
head>
<body>
<a href="testModelAndView">Test ModelAndViewa><br/>
body>
html>
控制器方法的入参可以是Map类型,也可以是Model类型或ModelMap类型的参数。
/**
* 目标方法可以添加Map类型(实际也可以是Model类型或ModelMap类型)的参数。
* 处理方法返回时,Map中的数据会自动添加到模型中。
* 我们可以在Request请求域对象中取到Map的值。
*/
@RequestMapping("/testMap")
public String testMap(Map<String, Object> map) {
System.out.println(map.getClass().getName());
//输出org.springframework.validation.support.BindingAwareModelMap
map.put("name", "shoto");
return SUCCESS;
}
name: ${requestScope.name }
注意:在方法testMap体内,开发者可以通过这个入参对象map访问到模型中的所有数据,也可以向模型中添加新的属性数据。另外这里的Map集合实际是BindingAwareModelMap类,其继承体系如下所示:
若希望在多个请求之间共用某个模型属性数据,则可以在控制器类上标注一个@SessionAttributes, Spring MVC将在模型中对应的属性暂存到 HttpSession 中。
SessionAttributes 除了可以通过属性名指定需要放到会话中的属性外(使用注解的value属性),还可以通过模型属性的对象类型指定哪些模型属性需要放到会话中(使用注解的types属性),下面演示一下其具体的用法:
@RequestMapping("/testSessionAttributes")
public String testSessionAttributes(Map<String, Object> map) {
// new User(uid, username, password, email, age)
User user = new User(1, "shoto", "abc123", "[email protected]", 22);
//向map集合中添加user对象以及String类型的值shoto
map.put("user", user);
map.put("name", "shoto");
return SUCCESS;
}
@SessionAttributes(value= {
"user"}, types= {
String.class})
该注解的value的值"user"需要与map.put(“user”, user);语句中的键"user"相同,它表明会将该User对象存放在Session域中(Request域也有)。而tyeps中的值String.class表明会将Map模型中的String类型的值存放在会话域中。
Request user: ${requestScope.user }<br/>
Session user: ${sessionScope.user }<br/>
Request name: ${requestScope.name }<br/>
Session name: ${sessionScope.name }<br/>
假设存在这个场景:在页面的表单上,我需要修改当前的用户密码。我们之前的做法可能是先new一个User对象,然后将表单的数据如密码存储在User对象中,然后再使用该User对象去进行数据库对应数据的更新操作,但是这样在进行数据库更新操作时又需要将对应的数据取出来,过程较为麻烦。
现在我们可以使用另一种方式来解决,即先从数据库获取对应要更新的User对象,然后将表单的数据对应更新到该对象的对应属性中(注意:该步骤SpringMVC自动帮我们完成),然后我们可以使用该对象进行对应的数据库更新操作。
<form action="testModelAttribute" method="POST">
<input type="hidden" name="uid" value="1"/>
password:<input type="text" name="password" value="abc123"/><br/>
<input type="submit" name="提交"/>
form>
@ModelAttribute
public void getUser(@RequestParam(value="uid", required=false) Integer uid, Map<String, Object> map) {
//模拟从数据库获取对象
if (uid != null) {
//根据uid中从数据库中获取对象
User user = new User(uid, "Tom", "123456", "[email protected]", 22);
System.out.println("从数据中获取一个对象:" + user);
//将从数据库获取的user对象存储到数据模型中
map.put("user", user);//这里的键为User类的首字母小写
}
}
@RequestMapping("/testModelAttribute")
public String testModelAttribute(User user) {
System.out.println("修改User对象:" + user);
return SUCCESS;
}
运行流程:
注意: 在@ModelAttribute 修饰的方法中,放入到Map时的键需要和目标方法入参类型的第一个字母小写的字符串一致。上述的Map的键为"user",即为testModelAttribute方法的入参User的的首字母小写对应的字符串。
当然,我们可以在目标方法testModelAttribute的入参处使用 @ModelAttribute 注解,也就是实现了Map键的自定义,而不是非要与目标方法入参类型的第一个字母小写的字符串一致。具体的代码如下所示:
另外补充一种情况,假如现在控制器类SpringMVCTest的定义如下:
@SessionAttributes(value= {
"user"})
@Controller
public class SpringMVCTest {
public final static String SUCCESS = "success";
@RequestMapping("/testModelAttribute")
public String testModelAttribute(User user) {
System.out.println("修改User对象:" + user);
return SUCCESS;
}
}
即当前有@SessionAttributes注解修饰类,且没有@ModelAttribute 修饰的getUser方法,那么此时会发生Session attribute ‘user’ required - not found in session的异常,SpringMVC底层在处理类定义处标注了@SessionAttributes(value= {“user”})时,则会尝试从会话中获取该属性,也就是值"user"所对应的User对象,并将其赋给该入参user,然后再用请求消息填充该入参对象。如果在会话中找不到对应的属性,则抛出 HttpSessionRequiredException 异常。
ModelAttribute运行处理的具体细节可以参考SpringMVC的源代码!以便加深理解。
之前我们在配置文件中配置过视图解析器InternalResourceViewResolver,其具体的解析过程如下所示:
请求处理方法执行完成后,最终返回一个 ModelAndView对象。对于那些返回 String(如"success"),View 或 ModeMap 等类型的处理方法,Spring MVC 也会在内部将它们装配成一个ModelAndView 对象,它包含了逻辑名和模型对象的视图。
Spring MVC 借助视图解析器(ViewResolver)得到最终的视图对象(View),最终的视图可以是 JSP ,也可能是Excel、JFreeChart 等各种表现形式的视图。
视图的作用是渲染模型数据,将模型里的数据以某种形式呈现给客户。为了实现视图模型和具体实现技术的解耦,Spring 在org.springframework.web.servlet 包中定义了一个高度抽象的 View接口。视图对象由视图解析器负责实例化。由于视图是无状态的,也就是每次请求都会创建一个新的视图,所以他们不会有线程安全的问题
SpringMVC 为逻辑视图名的解析提供了不同的策略,可以在 Spring WEB 上下文中配置一种或多种解析策略,并指定他们之间的先后顺序。每一种映射策略对应一个具体的视图解析器实现类。
视图解析器的作用比较单一:将逻辑视图解析为一个具体的视图对象。所有的视图解析器都必须实现 ViewResolver 接口。
下面我们在项目中使用了 JSTL,那么 SpringMVC 会自动把视图由InternalResourceView 转为 JstlView。并且使用 JSTL 的 fmt 标签实现国际化资源文件的使用。
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="i18n"/>
bean>
i18n.username=Username
i18n.password=Password
i18n_zh_CN.properties的内容如下,分别指用户名和密码的中文:i18n.username=\u7528\u6237\u540D
i18n.password=\u5BC6\u7801
@RequestMapping("/testJstlView")
public String testJstlView() {
System.out.println("testJstlView");
return SUCCESS;
}
若希望直接响应通过 SpringMVC 渲染的页面,可以使用 mvc:viewcontroller 标签实现,在配置文件配置如下:
<mvc:view-controller path="testJstlView" view-name="success"/>
<mvc:annotation-driven>mvc:annotation-driven>
注意:需要配置
下面我们演示一下自定义视图类的使用:
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
<property name="order" value="100">property>
bean>
@Component(value="abc")//使用该注解将自定义视图定义为组件,记得在扫描注解的配置时要包含该包下的该类。
public class HelloView implements View {
@Override
public String getContentType() {
return "text/html";
}
@Override
public void render(Map<String, ?> map, HttpServletRequest req, HttpServletResponse resp) throws Exception {
resp.getWriter().write("Hello View");
}
}
@RequestMapping("/testView")
public String testView() {
System.out.println("testView");
return "abc";
}
注意:testView方法的返回值"abc"与HelloView的Component注解的值相同。abc代表该视图名称,若HelloView的Component注解未指明值,则默认为"helloView",testView方法的返回值也应该为"helloView"。
一般情况下,控制器方法返回字符串类型的值会被当成逻辑视图名处理,如果返回的字符串中带 forward: 或 redirect: 前缀时,SpringMVC 会对他们进行特殊处理:将 forward: 和redirect: 当成指示符,其后的字符串作为 URL 来处理。
下面演示一下重定向操作:
在控制器类中定义如下方法:
@RequestMapping("/testRedirect")
public String testRedirect() {
System.out.println("testRedirect");
return "redirect:index.jsp";//重定向
}
此时在index.jsp页面进行请求响应时会进行重定向操作,虽然还是当前页面。