Spring4 MVC ContentNegotiatingViewResolver多種輸出格式範例


本文演示支援多種輸出格式,這裡 Spring4 MVC應用程式使用了 Spring ContentNegotiatingViewResolver 。我們將生成應用程式輸出XML,JSON,PDF,XLS和HTML格式,全部採用基於註解組態的。

ContentNegotiatingViewResolver是 ViewResolver 使用所請求的媒體型別的一個實現(基於檔案型別擴充套件,輸出格式URL引數指定型別或接受報頭)來選擇一個合適的檢視一個請求。ContentNegotiatingViewResolver本身並不解決檢視,只不表示為其他的 ViewResolver,您可以組態來處理特定的檢視(XML,JSON,PDF,XLS,HTML,..)。

這裡需要使用到以下技術:

  • Spring 4.0.6.RELEASE
  • jackson-databind 2.4.1.3
  • jackson-annotations 2.4.1
  • lowagie itext 4.2.1
  • Apache POI 3.10-beta2
  • Maven 3
  • JDK 1.6
  • Tomcat 7.0.54
  • Eclipse JUNO Service Release 2

我們現在就開始!

第1步:建立目錄結構

以下將是本範例的最終目錄結構:

我們將使用Spring Java組態(注釋)。現在,讓我們來新增/更新上述專案結構中提到的內容。

第2步:用所需的依賴更新 pom.xml

<?xml version="1.0"?>
<project
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
	xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

	<modelVersion>4.0.0</modelVersion>
	<groupId>com.yiibai.springmvc</groupId>
	<artifactId>ContentNegotiatingViewResolver</artifactId> <packaging>war</packaging>
	<version>1.0.0</version>
	<name>Spring4MVCContentNegotiatingViewResolverExample</name>

	<properties>
		<springframework.version>4.0.6.RELEASE</springframework.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>${springframework.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-web</artifactId>
			<version>${springframework.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>${springframework.version}</version>
		</dependency>

		<!-- Needed for XML View (with JAXB2) -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-oxm</artifactId>
			<version>${springframework.version}</version>
		</dependency>

		<!-- Needed for JSON View -->
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-databind</artifactId>
			<version>2.4.1.3</version>
		</dependency>
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-annotations</artifactId>
			<version>2.4.1</version>
		</dependency>

		<!-- Needed for PDF View -->
		<dependency>
			<groupId>com.lowagie</groupId>
			<artifactId>itext</artifactId>
			<version>4.2.1</version>
		</dependency>
		
		<!-- Needed for XLS View -->		
		<dependency>
			<groupId>org.apache.poi</groupId>
			<artifactId>poi</artifactId>
			<version>3.10-beta2</version>
		</dependency>

		<!-- Servlet dependencies -->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>javax.servlet-api</artifactId>
			<version>3.1.0</version>
		</dependency>
		<dependency>
		    <groupId>javax.servlet</groupId>
		    <artifactId>jstl</artifactId>
		    <version>1.2</version>
		</dependency>
		<dependency>
			<groupId>javax.servlet.jsp</groupId>
			<artifactId>javax.servlet.jsp-api</artifactId>
			<version>2.3.1</version>
		</dependency>

	</dependencies>


	<build>
		<pluginManagement>
			<plugins>
				<plugin>
					<groupId>org.apache.maven.plugins</groupId>
					<artifactId>maven-war-plugin</artifactId>
					<version>2.4</version>
					<configuration>
						<warSourceDirectory>src/main/webapp</warSourceDirectory>
						<warName>ContentNegotiatingViewResolver</warName> <failOnMissingWebXml>false</failOnMissingWebXml>
					</configuration>
				</plugin>
			</plugins>
		</pluginManagement>

		<finalName>ContentNegotiatingViewResolver</finalName> </build>
</project>
 

上面解析 PDF 的依賴庫有點問題,可修改為以下測試構建就沒有問題:

 <dependency>
             <groupId>com.lowagie</groupId>
          <artifactId>itext</artifactId>
          <version>2.1.7</version>
          <scope>compile</scope>
        </dependency>

spring-oxm是為了支援XML輸出生成(使用JAXB2)。 jackson-databind &jackson-annotations 提供JSON輸出支援。iText的提供PDF生成庫,支援PDF輸出。 Apache POI將有助於產生XLS輸出格式。

第3步:建立Spring組態檔案類

com.yiibai.springmvc.configuration.AppConfig

package com.yiibai.springmvc.configuration;

import java.util.ArrayList;
import java.util.List;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.ContentNegotiatingViewResolver;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;

import com.yiibai.springmvc.model.Pizza;
import com.yiibai.springmvc.viewresolver.ExcelViewResolver;
import com.yiibai.springmvc.viewresolver.JsonViewResolver;
import com.yiibai.springmvc.viewresolver.Jaxb2MarshallingXmlViewResolver;
import com.yiibai.springmvc.viewresolver.PdfViewResolver;

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.yiibai.springmvc")
public class AppConfig extends WebMvcConfigurerAdapter {

	/*
	 * Configure ContentNegotiationManager
	 */
	@Override
	public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
		configurer.ignoreAcceptHeader(true).defaultContentType(
				MediaType.TEXT_HTML);
	}

	/*
	 * Configure ContentNegotiatingViewResolver
	 */
	@Bean
	public ViewResolver contentNegotiatingViewResolver(ContentNegotiationManager manager) {
		ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
		resolver.setContentNegotiationManager(manager);

		// Define all possible view resolvers
		List<ViewResolver> resolvers = new ArrayList<ViewResolver>();

		resolvers.add(jaxb2MarshallingXmlViewResolver());
		resolvers.add(jsonViewResolver());
		resolvers.add(jspViewResolver());
		resolvers.add(pdfViewResolver());
		resolvers.add(excelViewResolver());
		
		resolver.setViewResolvers(resolvers);
		return resolver;
	}

	/*
	 * Configure View resolver to provide XML output Uses JAXB2 marshaller to
	 * marshall/unmarshall POJO's (with JAXB annotations) to XML
	 */
	@Bean
	public ViewResolver jaxb2MarshallingXmlViewResolver() {
		Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
		marshaller.setClassesToBeBound(Pizza.class);
		return new Jaxb2MarshallingXmlViewResolver(marshaller);
	}

	/*
	 * Configure View resolver to provide JSON output using JACKSON library to
	 * convert object in JSON format.
	 */
	@Bean
	public ViewResolver jsonViewResolver() {
		return new JsonViewResolver();
	}

	/*
	 * Configure View resolver to provide PDF output using lowagie pdf library to
	 * generate PDF output for an object content
	 */
	@Bean
	public ViewResolver pdfViewResolver() {
		return new PdfViewResolver();
	}

	/*
	 * Configure View resolver to provide XLS output using Apache POI library to
	 * generate XLS output for an object content
	 */
	@Bean
	public ViewResolver excelViewResolver() {
		return new ExcelViewResolver();
	}

	/*
	 * Configure View resolver to provide HTML output This is the default format
	 * in absence of any type suffix.
	 */
	@Bean
	public ViewResolver jspViewResolver() {
		InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
		viewResolver.setViewClass(JstlView.class);
		viewResolver.setPrefix("/WEB-INF/views/");
		viewResolver.setSuffix(".jsp");
		return viewResolver;
	}

}
讓我們來討論說明上面的類的詳細資訊:

第一步是建立它用於通過委託給ContentNegotiationManager,以確定所請求的媒體型別的請求是 ContentNegotiationStrategy 列表的一個範例。預設情況下PathExtensionContentNegotiationStrategy被查詢(使用URL擴充套件名,例如. .xls, .pdf,.json.),接著ParameterContentNegotiationStrategy(使用請求引數 ‘format=xls’,例如),其次是HeaderContentNegotiationStrategy(使用HTTP接受頭)。

	public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
		configurer.ignoreAcceptHeader(true).defaultContentType(
				MediaType.TEXT_HTML);
	}
 

在我們的例子中,我們將使用URL擴充套件名來幫助確定媒體型別。此外,我們還設定預設媒介型別TEXT_HTML在沒有檔案擴充套件名或當檔案型別是未知時,這意味著JSP檢視解析器將被用於在沒有[known] URL擴充套件中。

下面是 pizza.jsp 預設使用JSP檢視解析器內容
<%@ page language="java" contentType="text/html; charset=utf-8"  pageEncoding="utf-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
	<title>Pizza JSP View</title>
</head>
<body>
	<table border="1">
		<tr>
		<td>NAME</td>
		<td>Flavor</td>
		<td>Toppings</td>
		</tr>
		<tr>
			<td>${pizza.name}</td>
			<td>${pizza.flavor}</td>
			<td>
				<c:forEach var="item" items="${pizza.toppings}">
					<c:out value="${item}"/>&nbsp; 
				</c:forEach>
			</td>
		</tr>
	</table>
</body>
</html>
下一步是組態 ContentNegotaionViewResolver 本身,
	public ViewResolver contentNegotiatingViewResolver(ContentNegotiationManager manager) {
		ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
		resolver.setContentNegotiationManager(manager);

		// Define all possible view resolvers
		List<ViewResolver> resolvers = new ArrayList<ViewResolver>();

		resolvers.add(jaxb2MarshallingXmlViewResolver());
		resolvers.add(jsonViewResolver());
		resolvers.add(jspViewResolver());
		resolvers.add(pdfViewResolver());
		resolvers.add(excelViewResolver());
		
		resolver.setViewResolvers(resolvers);
		return resolver;
	}
我們需要設定 ContentNegotiationManager由Spring 注入,和為每一個應用程式可能會產生輸出格式設定不同的解析器,。
最後,我們已經建立了不同的檢視解析器以對 XML,JSON,PDF,XLS 和 HTML 輸出,我們將在節中討論。

第4步:建立不同的檢視解析器

現在,讓我們建立塔實際檢視解析器

XML View Resolver:

這個檢視解析器依賴於JAXB2編組/解組產生XML輸出。domain類需要和JAXB2注釋進行注釋。

com.yiibai.springmvc.viewresolver.Jaxb2MarshallingXmlViewResolver

package com.yiiibai.springmvc.viewresolver;

import java.util.Locale;

import org.springframework.oxm.Marshaller;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.view.xml.MarshallingView;

public class Jaxb2MarshallingXmlViewResolver implements ViewResolver {

	private Marshaller marshaller;

    
    public Jaxb2MarshallingXmlViewResolver(Marshaller marshaller) {
        this.marshaller = marshaller;
    }
    
    
    @Override
    public View resolveViewName(String viewName, Locale locale) throws Exception {
        MarshallingView view = new MarshallingView();
        view.setMarshaller(marshaller);
        return view;
    }

}
下面是域物件(標註了標準的XML註釋)在我們的例子:

com.yiibai.springmvc.model.Pizza

package com.yiibai.springmvc.model;

import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "pizza")
public class Pizza {
	
	private String name;
	
	private String flavor;
	
	private List<String> toppings = new ArrayList<String>();
	
	public Pizza(){
		
	}
	
	public Pizza(String name){
		this.name = name;
		this.flavor = "spicy";
		this.toppings.add("Cheese");
		this.toppings.add("bakon");
	}
	
	@XmlElement
	public void setName(String name) {
		this.name = name;
	}

	public String getName() {
		return name;
	}

	@XmlElement
	public void setFlavor(String flavor) {
		this.flavor = flavor;
	}

	public String getFlavor() {
		return flavor;
	}

	public List<String> getToppings() {
		return toppings;
	}
	
	@XmlElement
	public void setToppings(List<String> toppings) {
		this.toppings = toppings;
	}
	
}

JSON View Resolver:

這個檢視解析器是使用 Spring MappingJackson2JsonView 為了將 POJO 物件轉換成 JSON 檢視。

com.yiibai.springmvc.viewresolver.JsonViewResolver

package com.yiibai.springmvc.viewresolver;

import java.util.Locale;

import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.view.json.MappingJackson2JsonView;

public class JsonViewResolver implements ViewResolver{

	@Override
	public View resolveViewName(String viewName, Locale locale) throws Exception {
		MappingJackson2JsonView view = new MappingJackson2JsonView();
        view.setPrettyPrint(true);       
        return view;
      }

}

PDF View Resolver:

這個檢視解析器使用lowagie iText庫實際生成PDF輸出。還要注意的是實際的檢視,從Spring AbstractPdfView 內部使用 lowagie iText 庫擴充套件。

com.yiibai.springmvc.viewresolver.PdfView

package com.yiibai.springmvc.viewresolver;

import java.awt.Color;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.view.document.AbstractPdfView;

import com.lowagie.text.Document;
import com.lowagie.text.Element;
import com.lowagie.text.pdf.PdfPTable;
import com.lowagie.text.pdf.PdfWriter;
import com.yiibai.springmvc.model.Pizza;

public class PdfView extends AbstractPdfView {

	@Override
	protected void buildPdfDocument(Map<String, Object> model,
			Document document, PdfWriter writer, HttpServletRequest request,
			HttpServletResponse response) throws Exception {

		Pizza pizza = (Pizza) model.get("pizza");

		PdfPTable table = new PdfPTable(3);
		table.getDefaultCell().setHorizontalAlignment(Element.ALIGN_CENTER);
		table.getDefaultCell().setVerticalAlignment(Element.ALIGN_MIDDLE);
		table.getDefaultCell().setBackgroundColor(Color.lightGray);

		table.addCell("Name");
		table.addCell("Flavor");
		table.addCell("Toppings");

		table.addCell(pizza.getName());
		table.addCell(pizza.getFlavor());

		StringBuffer toppings = new StringBuffer("");
		for (String topping : pizza.getToppings()) {
			toppings.append(topping);
			toppings.append(" ");
		}
		table.addCell(toppings.toString());
		document.add(table);

	}

}

com.yiibai.springmvc.viewresolver.PdfViewResolver類程式碼:

package com.yiibai.springmvc.viewresolver;

import java.util.Locale;

import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;

public class PdfViewResolver implements ViewResolver{

	@Override
	public View resolveViewName(String viewName, Locale locale) throws Exception {
		PdfView view = new PdfView();
		return view;
      }
	
}

XLS View Resolver:

這個檢視解析器是使用Apache POI庫實際生成 Microsoft XLS輸出。還要注意的是實際的檢視,從Spring AbstractExcelView 內部使用 Apache POI庫擴充套件。

com.yiibai.springmvc.viewresolver.ExcelView

package com.yiibai.springmvc.viewresolver;

import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.springframework.web.servlet.view.document.AbstractExcelView;

import com.yiibai.springmvc.model.Pizza;

public class ExcelView extends AbstractExcelView {

	@Override
	protected void buildExcelDocument(Map<String, Object> model,
			HSSFWorkbook workbook, HttpServletRequest request,
			HttpServletResponse response) throws Exception {

		Pizza pizza = (Pizza) model.get("pizza");

		Sheet sheet = workbook.createSheet("sheet 1");
		CellStyle style = workbook.createCellStyle();
		style.setFillForegroundColor(IndexedColors.GREY_40_PERCENT.index);
		style.setFillPattern(CellStyle.SOLID_FOREGROUND);
		style.setAlignment(CellStyle.ALIGN_CENTER);
		Row row = null;
		Cell cell = null;
		int rowCount = 0;
		int colCount = 0;

		// Create header cells
		row = sheet.createRow(rowCount++);

		cell = row.createCell(colCount++);
		cell.setCellStyle(style);
		cell.setCellValue("Name");

		cell = row.createCell(colCount++);
		cell.setCellStyle(style);
		cell.setCellValue("Flavor");

		cell = row.createCell(colCount++);
		cell.setCellStyle(style);
		cell.setCellValue("Toppings");

		// Create data cells
		row = sheet.createRow(rowCount++);
		colCount = 0;
		row.createCell(colCount++).setCellValue(pizza.getName());
		row.createCell(colCount++).setCellValue(pizza.getFlavor());

		StringBuffer toppings = new StringBuffer("");
		for (String topping : pizza.getToppings()) {
			toppings.append(topping);
			toppings.append(" ");
		}
		row.createCell(colCount++).setCellValue(toppings.toString());

		for (int i = 0; i < 3; i++)
			sheet.autoSizeColumn(i, true);

	}

}

com.yiibai.springmvc.viewresolver.ExcelViewResolver

package com.yiibai.springmvc.viewresolver;

import java.util.Locale;

import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;

public class ExcelViewResolver implements ViewResolver{

	@Override
	public View resolveViewName(String viewName, Locale locale) throws Exception {
		ExcelView view = new ExcelView();
		return view;
      }
	
}
這是所有 ContentNegotaingViewResolver 需要的組態。
完成這個例子並讓它可以執行,讓我們新增缺少 Spring MVC 組態。

第5步:建立控制器類

下面是一個簡單的基於REST的控制器作為我們的範例。

com.yiibai.springmvc.controller.AppController

package com.yiibai.springmvc.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import com.yiibai.springmvc.model.Pizza;

@Controller
public class AppController {

	@RequestMapping(value="/pizzavalley/{pizzaName}", method = RequestMethod.GET)
	public String getPizza(@PathVariable String pizzaName, ModelMap model) {
 
		Pizza pizza = new Pizza(pizzaName);
		model.addAttribute("pizza", pizza);
 
		return "pizza";
 
	}
	
}

第6步:建立初始化類

新增一個初始化類實現WebApplicationInitializer如下圖所示(在這種情況下,作為替代在 web.xml 中定義的任何 Spring 組態)。在Servlet 3.0的容器啟動時,這個類會被載入併範例及其 onStartup方法將通過servlet容器呼叫。

com.yiibai.springmvc.configuration.AppInitializer

package com.yiibai.springmvc.configuration;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;

import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

public class AppInitializer implements WebApplicationInitializer {

	public void onStartup(ServletContext container) throws ServletException {

		AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
		ctx.register(AppConfig.class);
		ctx.setServletContext(container);

		ServletRegistration.Dynamic servlet = container.addServlet(
				"dispatcher", new DispatcherServlet(ctx));

		servlet.setLoadOnStartup(1);
		servlet.addMapping("/");
	}

}
 

更新:請注意,上面的類可以寫成更加簡潔[和它的首選方式],通過擴充套件 AbstractAnnotationConfigDispatcherServletInitializer 基礎類別,如下所示:

package com.yiibai.springmvc.configuration;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

	@Override
	protected Class<?>[] getRootConfigClasses() {
		return new Class[] { AppConfig.class };
	}
 
	@Override
	protected Class<?>[] getServletConfigClasses() {
		return null;
	}
 
	@Override
	protected String[] getServletMappings() {
		return new String[] { "/" };
	}

}

第7步:構建和部署應用程式

現在構建 war(通過Eclipse或Maven [ mvn clean install])。部署 war 到Servlet3.0容器。

執行。以下是執行範例觸發不同模式輸出的快照(注意URL擴充套件),存取如下URL:http://localhost:8080/ContentNegotiatingViewResolver/pizzavalley/margherita.xml





到這裡,所有教學講解完畢!
程式碼下載:http://pan.baidu.com/s/1skqkZBf