SpringMVC 视图 详解
请求处理方法执行完成后,最终返回一个 ModelAndView 对象。对于那些返回 String、View 或 ModelMap 等类型的处理方法,Spring MVC 会在内部将它们装配成一个 ModelAndView 对象,它包含视图逻辑名和模型对象的信息
SpringMVC 借助视图解析器 (ViewResolver) 得到最终的视图对象 (View),该视图可以是 JSP、基于FreeMarker、Velocity模版技术的视图、PDF、Excel、XML、JSON 等各种形式的视图
不同视图实现类
视图解析器实现类
逻辑视图解析为 URI 资源
InternalResourceViewResolver 默认使用 InternalResourceView 作为视图实现类,如 JSP页面使用 JSTL 的<fmt:message/> 等标签时,用户需要使用 JstlView 替换默认的视图实现类
<!-- 配置视图解析器,将ModelAndView及字符串解析为具体的页面 -->
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:order="100"
p:viewClass="org.springframework.web.servlet.view.JstlView" p:prefix="/WEB-INF/jsp/" p:suffix=".jsp"/>
FreeMarker 和 Velocity
SpringMVC 可以将 FreeMarker、Velocity 作为视图,使用模版中的数据进行替换工作
在 spring-mvc.xml 中
<!-- FreeMarker基础设施及视图解析器配置 -->
<bean
class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer"
p:templateLoaderPath="/WEB-INF/ftl"
p:defaultEncoding="UTF-8">
<property
name="freemarkerSettings">
<props>
<prop
key="classic_compatible">true</prop>
<!-- 若不设置该属性,若碰到值为null的对象属性时,将抛出异常 -->
</props>
</property>
</bean>
<bean
class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver"
p:order="5"
p:suffix=".ftl"
p:contentType="text/html;
charset=utf-8"/>
order 指定视图解析器的优先级,它将优先于 InternalResourceViewResolver 执行 (因为 InternalResourceViewResolver 默认优先级最低)。
@RequestMapping(value =
"/showUserListByFtl")
public
String showUserListInFtl(ModelMap mm) {
Calendar calendar =
new
GregorianCalendar();
List<User> userList =
new
ArrayList<User>();
User user1 =
new
User();
user1.setUserName("tom");
user1.setRealName("汤姆");
calendar.set(1980,
1,
1);
user1.setBirthday(calendar.getTime());
User user2 =
new
User();
user2.setUserName("john");
user2.setRealName("约翰");
user2.setBirthday(calendar.getTime());
userList.add(user1);
userList.add(user2);
mm.addAttribute("userList", userList);
return "userListFtl";
}
这样 "userListFtl" 的视图名将解析为 "/WEB-INF/ftl/userListFtl.ftl" 的视图对象
userListFtl.ftl 文件
<#import
"spring.ftl"
as
spring
/>
<!-- 引入Spring的FreeMarker宏定义文件 -->
<html>
<head>
<title><@spring.message
"website.title"/></title>
</head>
<body>
<@spring.message
"user.userList.title"/>
<!-- 引入国际化资源 -->
<table>
<#list userList
as
user>
<tr>
<td>
<a
href="<@spring.url
'/user/showUser/${user.userName}.html'/>">${user.userName}</a>
</td>
<td>${user.realName}</td>
<td>
${user.birthday?string("yyyy-MM-dd")}</td>
</tr>
</#list>
<table>
</body>
</html>
Spring 为 FreeMarker 提供的宏
Excel 和PDF
若希望使用 Excel 展示用户列表,仅需要扩展 Spring 的 AbstractExcelView 或 AbstractJExcelView 即可。实现 buildExcelDocument()方法,在方法中使用
PDF 需要扩展 AbstractPdfView
具体步骤 :
1> 配置视图解析器
<!-- Excel及PDF视图解析器配置 -->
<bean
class="org.springframework.web.servlet.view.BeanNameViewResolver"
p:order="10"/>
<bean
id="userListExcel"
class="com.baobaotao.web.UserListExcelView"/>
<bean
id="userListPdf"
class="com.baobaotao.web.UserListPdfView"/>
在 spring-mvc.xml 中
<!-- Excel及PDF视图解析器配置 -->
<bean
class="org.springframework.web.servlet.view.BeanNameViewResolver"
p:order="10"/>
<bean
id="userListExcel"
class="com.baobaotao.web.UserListExcelView"/>
<bean
id="userListPdf"
class="com.baobaotao.web.UserListPdfView"/>
Excel 处理方法
import
java.util.List;
import
java.util.Map;
import
javax.servlet.http.HttpServletRequest;
import
javax.servlet.http.HttpServletResponse;
import
org.apache.commons.lang.time.DateFormatUtils;
import
org.apache.poi.hssf.usermodel.HSSFRow;
import
org.apache.poi.hssf.usermodel.HSSFSheet;
import
org.apache.poi.hssf.usermodel.HSSFWorkbook;
import
org.springframework.stereotype.Component;
import
org.springframework.web.servlet.view.document.AbstractExcelView;
public class
UserListExcelView
extends AbstractExcelView {
@Override
protected void
buildExcelDocument(Map<String, Object> model,
HSSFWorkbook workbook, HttpServletRequest request,
HttpServletResponse response)
throws
Exception {
response.setHeader("Content-Disposition",
"inline; filename="+
new String("用户列表".getBytes(),
"iso8859-1"));
List<User> userList = (List<User>) model.get("userList");
HSSFSheet sheet = workbook.createSheet("users");
HSSFRow header = sheet.createRow(0);
header.createCell(0).setCellValue("帐号");
header.createCell(1).setCellValue("姓名");
header.createCell(2).setCellValue("生日");
int rowNum =
1;
for (User user : userList) {
HSSFRow row = sheet.createRow(rowNum++);
row.createCell(0).setCellValue(user.getUserName());
row.createCell(1).setCellValue(user.getRealName());
String createDate = DateFormatUtils.format(user.getBirthday(),
"yyyy-MM-dd");
row.createCell(2).setCellValue(createDate);
}
}
}
PDF 处理方法
import
java.awt.Color;
import
java.util.List;
import
java.util.Map;
import
javax.servlet.http.HttpServletRequest;
import
javax.servlet.http.HttpServletResponse;
import
org.apache.commons.lang.time.DateFormatUtils;
import
org.springframework.web.servlet.view.document.AbstractPdfView;
import
com.lowagie.text.Cell;
import
com.lowagie.text.Document;
import
com.lowagie.text.Element;
import
com.lowagie.text.Font;
import
com.lowagie.text.Phrase;
import
com.lowagie.text.Table;
import
com.lowagie.text.pdf.BaseFont;
import
com.lowagie.text.pdf.PdfWriter;
public class
UserListPdfView
extends AbstractPdfView {
@Override
protected void
buildPdfDocument(Map<String, Object> model, Document document, PdfWriter writer, HttpServletRequest request, HttpServletResponse response)
throws
Exception {
response.setHeader("Content-Disposition",
"inline; filename="
+
new String("用户列表".getBytes(),
"iso8859-1"));
List<User> userList = (List<User>) model.get("userList");
Table table =
new
Table(3);
table.setWidth(80);
table.setBorder(1);
table.getDefaultCell().setHorizontalAlignment(Element.ALIGN_CENTER);
table.getDefaultCell().setVerticalAlignment(Element.ALIGN_MIDDLE);
// 使用中文字
BaseFont cnBaseFont = BaseFont.createFont("STSongStd-Light",
"UniGB-UCS2-H",
false);
Font cnFont =
new
Font(cnBaseFont, 10, Font.NORMAL,
Color.BLUE);
// 对于中文字符使用中文字段构造 Cell对象,否则会发生乱码
table.addCell(buildFontCell("帐号", cnFont));
table.addCell(buildFontCell("姓名", cnFont));
table.addCell(buildFontCell("生日", cnFont));
for (User user : userList) {
table.addCell(user.getUserName());// 英文字符可直接添加到 Cell 中
table.addCell(buildFontCell(user.getRealName(), cnFont));
String createDate = DateFormatUtils.format(user.getBirthday(), "yyyy-MM-dd");
table.addCell(createDate);
}
document.add(table);
}
private Cell buildFontCell(String content, Font font)
throws
RuntimeException {
try {
Phrase phrase =
new
Phrase(content, font);
return new Cell(phrase);
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
}
在 Controller 中
@RequestMapping(value =
"/showUserListByXls")
public
String showUserListInExcel(ModelMap mm) {
Calendar calendar =
new
GregorianCalendar();
List<User> userList =
new
ArrayList<User>();
User user1 =
new
User();
user1.setUserName("tom");
user1.setRealName("汤姆");
calendar.set(1980,
1,
1);
user1.setBirthday(calendar.getTime());
User user2 =
new
User();
user2.setUserName("john");
user2.setRealName("约翰");
user2.setBirthday(calendar.getTime());
userList.add(user1);
userList.add(user2);
mm.addAttribute("userList", userList);
return "userListExcel";
}
@RequestMapping(value =
"/showUserListByPdf")
public
String showUserListInPdf(ModelMap mm) {
Calendar calendar =
new
GregorianCalendar();
List<User> userList =
new
ArrayList<User>();
User user1 =
new
User();
user1.setUserName("tom");
user1.setRealName("汤姆");
calendar.set(1980,
1,
1);
user1.setBirthday(calendar.getTime());
User user2 =
new
User();
user2.setUserName("john");
user2.setRealName("约翰");
user2.setBirthday(calendar.getTime());
userList.add(user1);
userList.add(user2);
mm.addAttribute("userList", userList);
return "userListPdf";
}
需要引入的 Maven 包配置
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.7</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
XML
Spring MVC 可以将模型中的数据以 xml 的形式输出,其对应的视图对象为 MarshallingView。MarshallingView 使用 Marshaller 将模型数据转换为 XML,通过 marshaller 属性注入到一个 MarshallingView 实例。默认情况下,MarshallingView 会将模型中的所有属性转换为 XML,由于模型属性中包涵许多隐式数据,直接将模型中的所有数据输出一帮情况这种结果不是预期结果,可通过 modelKey 指定模型中的哪个属性需要输出为
xml
在 spring-mvc.xml 中
<bean
id="xmlMarshaller"
class="org.springframework.oxm.xstream.XStreamMarshaller">
<property
name="streamDriver">
<bean
class="com.thoughtworks.xstream.io.xml.StaxDriver"/>
</property>
<property
name="annotatedClasses">
<list>
<value>com.baobaotao.domain.User</value>
</list>
</property>
</bean>
<bean
id="userListXml"
class="org.springframework.web.servlet.view.xml.MarshallingView"
p:modelKey="userList"
p:marshaller-ref="xmlMarshaller"/>
在 Controller 中
@RequestMapping(value =
"/showUserListByXml")
public
String showUserListInXml(ModelMap mm) {
Calendar calendar =
new
GregorianCalendar();
List<User> userList =
new
ArrayList<User>();
User user1 =
new
User();
user1.setUserName("tom");
user1.setRealName("汤姆");
calendar.set(1980,
1,
1);
user1.setBirthday(calendar.getTime());
User user2 =
new
User();
user2.setUserName("john");
user2.setRealName("约翰");
user2.setBirthday(calendar.getTime());
userList.add(user1);
userList.add(user2);
mm.addAttribute("userList", userList);
return "userListXml";
}
JSON
Spring MVC 的 MappingJacksonJsonView 借助 Jsckson 框架的 ObjectMapper 将模型数据转换为 JSON 格式输出。默认情况下,MappingJacksonJsonView 会将模型中所有数据输出为 JSON,可通过 renderedAttributes 指定模型中哪些属性需要输出
在 spring-mvc.xml 中
<bean
id="userListJson"
class="org.springframework.web.servlet.view.json.MappingJacksonJsonView"
p:renderedAttributes="userList"/>
在 Controller 中
@RequestMapping(value =
"/showUserListByJson")
public
String showUserListInJson(ModelMap mm) {
Calendar calendar =
new
GregorianCalendar();
List<User> userList =
new
ArrayList<User>();
User user1 =
new
User();
user1.setUserName("tom");
user1.setRealName("汤姆");
calendar.set(1980,
1,
1);
user1.setBirthday(calendar.getTime());
User user2 =
new
User();
user2.setUserName("john");
user2.setRealName("约翰");
user2.setBirthday(calendar.getTime());
userList.add(user1);
userList.add(user2);
mm.addAttribute("userList", userList);
return "userListJson";
}
使用 XmlViewResolver
若视图对象 Bean 数目太多,可以通过 XmlViewResolver 将视图文件独立在一个 xml 文件中,例如如下配置 :
<!-- XML文件或国际化资源文件定义视图 -->
<bean
class="org.springframework.web.servlet.view.XmlViewResolver"
p:order="20"
p:location="/WEB-INF/views/baobaotao-views.xml"/>
默认情况下,XmlViewResolver 在 WEB-INF/views.xml 中查找视图 Bean 的定义文件,文件中的 bean 和普通的 Spring配置文件没有区别
例如如下代码 :
<?xml version="1.0"
encoding="UTF-8"?>
<bean
id="userListJson1" class="org.springframework.web.servlet.view.json.MappingJacksonJsonView" p:renderedAttributes="message"
/>
<bean
id="userListExcel1"
class="com.baobaotao.web.UserListExcelView"/>
<bean
id="userListPdf1"
class="com.baobaotao.web.UserListPdfView"/>
</beans>
混合使用多种视图
Spring 中支持 Rest 编程风格,Rest 风格的应用对资源的 URL 有严格的要求 : 一个资源对象对应唯一的 URL
若想同一路径对应一个不同的视图,可以使用 HeepMessageConverter 对标注 @ResponseBody 或返回值为 ResponseEntity 的处理器方法进行响应信息转换的内容,Spring MVC 可以根据请求报文头的 Accept属性选择合适的 HttpMessageConverter 将处理方法的返回值以 XML、JSON等不同的形式输出响应。也就是说,调用者可以通过设置请求报文头 Accept 的值控制服务端返回的数据格式,实现对同一资源采用相同 URL 的 REST 编程风格。但是基于
HeepMessageConverter 的实现方式存在以下限制 :
1> 只能通过请求报头的 Accept 的值控制服务端返回的数据格式,如果客户端是浏览器,除非使用 Ajax,否则很难控制 Accept 报文头的值,一般情况下,这个值有浏览器决定
2> 无法通过 URL 扩展名或请求参数控制服务端的资源输出形式,因此无法将其对应 URL 发不出去
3> 如果希望 XML、JSON、一个网页等形式输出资源,HeepMessageConverter 很难达到要求,因为 HeepMessageConverter 很难调用一个视图对象渲染模型,直接负责将资源输出为某一内容形式
如果希望将资源以 XML、JSON等纯数据的格式输出,且不在意使用报文头控制资源输出,那么合适选择 HeepMessageConverter 的实现方式。否则,建议采用 SpringMVC 的 ContentNegotiatingViewResolver 试图解析器,它和 HeepMessageConverter 功能上有些重叠,但 ContentNegotiatingViewResolver 更加灵活
ContentNegotiatingViewResolver 可以根据请求信息上下文选择一个合适的视图解析器负责解析。一般将 ContentNegotiatingViewResolver 的优先级设置为最高,以保证其优先调用