请求处理方法执行完成后,最终返回一个 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 的优先级设置为最高,以保证其优先调用