SpringMVC 视图 详解

请求处理方法执行完成后,最终返回一个 ModelAndView 对象。对于那些返回 String、View 或 ModelMap 等类型的处理方法,Spring MVC 会在内部将它们装配成一个 ModelAndView 对象,它包含视图逻辑名和模型对象的信息
SpringMVC 借助视图解析器 (ViewResolver) 得到最终的视图对象 (View),该视图可以是 JSP、基于FreeMarker、Velocity模版技术的视图、PDF、Excel、XML、JSON 等各种形式的视图

不同视图实现类
SpringMVC 视图 详解

视图解析器实现类
SpringMVC 视图 详解

逻辑视图解析为 URI 资源
SpringMVC 视图 详解

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 提供的宏
SpringMVC 视图 详解
SpringMVC 视图 详解

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 的优先级设置为最高,以保证其优先调用
SpringMVC 视图 详解