在 Spring MVC 应用程序中同时支持 Json 和 XML 序列化
本文介绍了一个支持 JSON 和 XML 序列化的 Spring MVC REST 服务应用程序示例。
引言
本文介绍了一个支持 JSON 和 XML 序列化的 Spring MVC REST 服务应用程序示例。
背景
有两种流行的序列化方法用于处理 REST 服务:JSON 和 XML。本文介绍了一个在单个 Spring MVC 应用程序中同时支持 JSON 和 XML 序列化的 REST 服务示例。下图是 Eclipse 项目浏览器中的示例 Maven 项目。
- "com.song.data.model" 包实现了 MVC 应用程序的数据模型;
- "com.song.web.controller.api" 包实现了 MVC 应用程序的控制器;
- "com.song.web.filter.NocacheFilter" 类实现了一个 servlet 过滤器,该过滤器禁用了 Web 内容的客户端和代理缓存;
- "index.jsp" 是一个用作 REST 服务测试客户端的 jsp 页面。
此 Web 应用程序已在 Windows 环境中,使用 Java 1.7、Tomcat 7 和 Spring 4.0.7.RELEASE 进行过测试。本示例使用的开发环境是 Eclipse Java EE IDE for Web Developers,版本 Kepler Service Release 2。
Maven POM
此 Maven 应用程序的 POM 文件实现如下。
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.song.example</groupId>
<artifactId>spring-web-application</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<spring.version>4.0.7.RELEASE</spring.version>
<jackson.version>1.9.13</jackson.version>
<tomcat.version>7.0.55</tomcat.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-servlet-api</artifactId>
<version>${tomcat.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>2.4</version>
<configuration>
<warSourceDirectory>WebContent</warSourceDirectory>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</build>
</project>
- "spring-core"、"spring-web"、"spring-webmvc" 依赖是构建 Spring MVC 应用程序的最小依赖集;
- "jackson-mapper-asl" 是 Jackson 依赖,它提供了用于数据序列化的类;
- "tomcat-servlet-api" 是用于编译使用任何 servlet 功能的代码的依赖。由于大多数运行时环境(如 Tomcat)都包含此包中的类,因此该依赖的作用域被设置为 "provided"。
NocacheFilter Servlet 过滤器
在许多情况下,禁用 Web 应用程序的浏览器和代理缓存是一个好主意,这样客户端在发送 Web 请求时就能始终获取到更新的数据。"NocacheFilter" 类实现了一个禁用缓存的 servlet 过滤器。
package com.song.web.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
public class NocacheFilter implements Filter {
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletResponse httpResponse = (HttpServletResponse)response;
httpResponse.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
httpResponse.setHeader("Pragma", "no-cache");
httpResponse.setDateHeader("Expires", 0);
chain.doFilter(request, response);
}
public void destroy() {}
public void init(FilterConfig fConfig) throws ServletException {}
}
此过滤器将在 "web.xml" 文件中配置,我们将在其中设置 url-pattern 匹配,以便它仅应用于匹配的 Web 请求。
数据模型类
本示例应用程序实现了两个数据模型类。"Student" 类实现如下。
package com.song.data.model;
import java.io.Serializable;
import java.util.ArrayList;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import org.codehaus.jackson.annotate.JsonIgnore;
import org.codehaus.jackson.annotate.JsonPropertyOrder;
@XmlRootElement(name = "student")
@XmlType(propOrder = {"id", "name", "graduationTime", "courses"})
@JsonPropertyOrder({"id", "name", "graduationTime", "courses"})
public class Student implements Serializable {
private static final long serialVersionUID = 1L;
private int id;
private String name;
private String graduationTime;
private ArrayList<Course> courses = new ArrayList<Course>();
@XmlElement
public int getId() { return id; }
@XmlElement
public String getName() { return name; }
@XmlElement
public String getGraduationTime() { return graduationTime; }
@XmlElement
public ArrayList<Course> getCourses() { return courses; }
public void setId(int value) { this.id = value; }
public void setName(String value) { this.name = value; }
public void setGraduationTime(String value) { this.graduationTime = value; }
public void setCourses(ArrayList<Course> value) { this.courses = value; }
@JsonIgnore
public String toString() {
return this.name + " - "
+ graduationTime == null? "Unknown" : graduationTime.toString();
}
public Student() {}
public Student(int id, String name, String graduationTime) {
this.id = id;
this.name = name;
this.graduationTime = graduationTime;
}
}
"Course" 类实现如下。
package com.song.data.model;
import java.io.Serializable;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import org.codehaus.jackson.annotate.JsonPropertyOrder;
@XmlRootElement(name = "course")
@XmlType(propOrder = {"courseName", "score"})
@JsonPropertyOrder({"courseName", "score"})
public class Course implements Serializable {
private static final long serialVersionUID = 1L;
private String courseName;
private Integer score;
public @XmlElement String getCourseName() { return courseName; }
public @XmlElement Integer getScore() { return score; }
public void setCourseName(String value) { courseName = value; }
public void setScore(Integer value) { score = value; }
public Course() {}
public Course(String courseName, Integer score) {
this.courseName = courseName;
this.score = score;
}
}
- 这两个类是简单的 Java Bean 类;
- "@XmlElement" 注解告诉 XML 序列化器将该字段添加为 XML 元素添加到 XML 文档中;
- "@JsonIgnore" 注解告诉 JSON 序列化器在将对象序列化为 JSON 时忽略此字段。
MVC 控制器
MVC 控制器类 "StudentController" 实现如下。
package com.song.web.controller.api;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import com.song.data.model.Student;
import com.song.data.model.Course;
@Controller
public class StudentController {
@ResponseBody
@RequestMapping(value = "/getstudent/{id}/{name}",
method = RequestMethod.GET,
produces={"application/json", "application/xml"})
public Student getStudent(HttpServletRequest request, HttpServletResponse response,
@PathVariable("id") final int id,
@PathVariable("name") final String name) {
// Create a new student object and return it
SimpleDateFormat dateFormatter = new SimpleDateFormat("MM/dd/yyyy");
Student student = new Student(id, name, dateFormatter.format(new Date()));
List<Course> cources = student.getCourses();
cources.add(new Course("Math", 15));
cources.add(new Course("Politics", 100));
return student;
}
@ResponseBody
@RequestMapping(value = "/echostudent",
method = RequestMethod.POST,
produces={"application/json", "application/xml"},
consumes={"application/json", "application/xml"})
public Student echoStudent(@RequestBody Student student,
HttpServletRequest request, HttpServletResponse response) {
// Just echo the same student back
return student;
}
}
- action 方法 "getStudent" 接收一个 HTTP GET 请求;
- action 方法 "echoStudent" 接收一个 HTTP POST 请求;
- "@RequestMapping" 注解中的 "produces" 和 "consumes" 属性告诉 Spring Framework,action 方法支持 JSON 和 XML 序列化。
Web.xml
配置 Spring MVC 应用程序的中心位置是 "web.xml" 文件。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">
<display-name>Spring Web Example</display-name>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<filter>
<filter-name>nocachefilter</filter-name>
<filter-class>
com.song.web.filter.NocacheFilter
</filter-class>
</filter>
<filter-mapping>
<filter-name>nocachefilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>mvc-dispatcher</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mvc-dispatcher</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>
<context-param>
<param-name>BaseUrl</param-name>
<param-value>
https://:8080/spring-web-application/
</param-value>
</context-param>
</web-app>
- MVC 控制器的入口点是名为 "mvc-dispatcher" 的 servlet,其 url-pattern 设置为 "/api/*";
- "NocacheFilter" 配置为应用于所有 Web 请求。这是因为在开发环境中,我们不断更改代码。在生产环境中,您可能需要考虑选择性地禁用某些 Web 内容的缓存。允许缓存 JavaScript 文件、CSS 文件和图像文件以获得更好的性能是一个更好的主意。
"mvc-dispatcher" servlet 在 "mvc-dispatcher-servlet.xml" 文件中进一步配置。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd">
<context:component-scan base-package="com.song.web.controller" />
<mvc:annotation-driven />
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver" >
<property name="prefix">
<value>/views/</value>
</property>
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
</beans>
- "component-scan" 告诉 Spring 需要查找 MVC 控制器的 Java 包;
- "viewResolver" 告诉 Spring 在哪里查找 MVC 视图。在此应用程序中,我们只实现了 REST 服务,因此此应用程序中没有用于 HTML 内容的 MVC 视图。
测试客户端
"index.jsp" 页面实现了一个简单的 REST 服务测试客户端。
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <%String baseUrl = getServletContext().getInitParameter("BaseUrl");%> <!DOCTYPE html> <html> <head> <meta charset="ISO-8859-1"> <title>Spring Rest Client</title> <link rel="stylesheet" type="text/css" href="<%out.print(baseUrl); %>Styles/site.css"> <script type="text/javascript" src="<%out.print(baseUrl); %>Scripts/jquery-2.1.1.min.js"></script> <script type="text/javascript" src="<%out.print(baseUrl); %>Scripts/vkbeautify.0.99.00.beta.js"></script> <script type="text/javascript" src="<%out.print(baseUrl); %>Scripts/jsonxml.js"></script> <script type="text/javascript" src="<%out.print(baseUrl); %>Scripts/index.js"></script> <script type="text/javascript"> var urlbase = "<%out.print(baseUrl); %>"; $(document).ready(function() { pageobject.initpage(urlbase); }); </script> </head> <body> <div> <div> <textarea id="txtResult" readonly="readonly"></textarea> </div> <div> <span id="spanMessage" class="msgNormal"> Click the buttons to test the rest calls... </span> </div> <div> <input type="button" id="btnGetXML" value="GET XML" /> <input type="button" id="btnGetJson" value="GET Json" /> <input type="button" id="btnPostJsonAcceptXML" value="POST Json Accept XML" /> <input type="button" id="btnPOSTXMLAcceptJson" value="POST XML Accept Json" /> </div> </div> </body> </html>
REST 服务调用通过 "index.js" JavaScript 文件中的 jQuery 进行。
var pageobject = function() {
var getUrl = null;
var postUrl = null;
var dataToPost = {
id: 2014,
name: "Hilary Clinton",
graduationTime: "09/18/2014",
courses: [
{
courseName: "Math",
score: 15
},
{
courseName: "Politics",
score: 100
}
]
};
var done = function(data, status) {
var type = Object.prototype.toString.call(data);
var formatedText = '';
if (type === "[object XMLDocument]"
|| type === "[object Document]") {
var text = (new XMLSerializer()).serializeToString(data);
formatedText = vkbeautify.xml(text);
}
else {
var text = JSON.stringify(data);
formatedText = vkbeautify.json(text);
}
var date = new Date();
var msg = 'Rest call successful '
+ date.toLocaleDateString() + ' ' + date.toLocaleTimeString();
$('#spanMessage').html(msg)
.removeClass('msgError').addClass('msgNormal');
$('#txtResult').html(formatedText);
};
var fail = function(xhr, status, error) {
$('#txtResult').html('');
var date = new Date();
var msg = 'Rest call failed (Error code: ' + error + ') - '
+ date.toLocaleDateString() + ' ' + date.toLocaleTimeString();
$('#spanMessage').html(msg)
.removeClass('msgNormal').addClass('msgError');;
};
var initpage = function(baseUrl) {
getUrl = urlbase + 'api/getstudent/2014/Hilary%20Clinton';
postUrl = urlbase + 'api/echostudent';
$('#btnGetXML').off('click');
$('#btnGetJson').off('click');
$('#btnPostJsonAcceptXML').off('click');
$('#btnPOSTXMLAcceptJson').off('click');
$('#btnGetXML').on('click', function() {
$.ajax({
url: getUrl,
headers: {
Accept: 'application/xml'
}
}).done(done).fail(fail);
});
$('#btnGetJson').on('click', function() {
$.ajax({
url: getUrl,
headers: {
Accept: 'application/json'
}
}).done(done).fail(fail);
});
$('#btnPostJsonAcceptXML').on('click', function() {
$.ajax({
url: postUrl,
type: 'post',
data: JSON.stringify(dataToPost),
headers: {
Accept: 'application/xml',
'Content-Type': 'application/json'
}
}).done(done).fail(fail);
});
$('#btnPOSTXMLAcceptJson').on('click', function() {
var xmlData = '<student>'
+ jsonxml.json2xml(dataToPost) + '</student>';
$.ajax({
url: postUrl,
type: 'post',
data: xmlData,
headers: {
Accept: 'application/json',
'Content-Type': 'application/xml'
}
}).done(done).fail(fail);
});
};
return {
initpage: initpage
};
}();
将 Maven 项目导入 Eclipse
由于这是一个 Maven 项目,您可以简单地执行 "mvn clean install" 命令来生成一个 "war" 文件并将其部署到 servlet 容器中来测试应用程序。您也可以将项目导入 Eclipse。我已经使用 Eclipse Java IDE for Web Developers Version: Kepler Service Release 2 和 "jdk1.7.0_60" 测试了导入。
- 下载 zip 文件并解压缩到您选择的文件夹;
- 在另一个位置创建一个空文件夹,并为其命名。这个空文件夹将作为您的 Eclipse 工作区。
然后您可以启动 Eclipse 并打开空工作区。在 Eclipse 菜单中,您可以选择 "File" -> "Import..." -> "Maven" -> "Existing Maven Projects"。
然后您可以浏览到 "pom.xml" 所在文件夹,Eclipse 就可以找到 POM 文件。
如果一切顺利,您应该可以在点击 "Finish" 按钮后将 Maven 项目导入 Eclipse。Eclipse 和 Maven 并非总是能很好地协同工作,但附件的项目编写方式应能确保您在使用正确的 Eclipse 版本时不会遇到太多导入问题。
运行应用程序
如果一切顺利,您就可以在开发环境中测试运行此示例了。我已经使用 Tomcat 7 测试了该应用程序。如果您不知道如何在 Eclipse 中运行 Tomcat,可以参考我 之前的帖子。
上图显示了当数据以 XML 格式发送到服务器时,服务器回显的 JSON 结果。我已在 Chrome、Firefox 和最新版本的 Safari 中测试了此应用程序。由于使用了 jQuery 2.1.1 版本,测试客户端不支持 IE 8 或更低版本。由于我的电脑上没有更高版本的 IE,所以我没有在 IE 上进行任何测试。
关注点
- 本文介绍了一个支持 JSON 和 XML 序列化的 Spring MVC REST 服务应用程序示例;
- 在 Java 环境中,我们有很多 IDE 选择,如果您不想使用 Eclipse,可以在其他 IDE 中将项目导入到您的环境中。我认为 Netbeans 对 Maven 项目有很好的支持;
- 如果您在将项目导入 IDE 时遇到困难,您可以随时执行 "mvn clean install" 命令来生成一个 'war' 文件并将其部署到 servlet 容器来运行应用程序。如果您是 Maven 新手,可以参考我 之前的帖子。
- 我希望您喜欢我的文章,希望本文能以某种方式帮助您。
历史
第一次修订 - 2014/9/19