Spring Restful Web服务示例,带有JSON,Jackson和客户端程序
Spring是使用最广泛的Java EE框架之一。
前面我们已经看到了如何使用Spring MVC创建基于Java的Web应用程序。
今天,我们将学习使用Spring MVC创建Spring Restful Web服务,然后使用Rest客户端对其进行测试。
最后,我们还将研究如何使用Spring RestTemplate API调用Spring Restful Web服务。
SpringREST
我们将使用Spring最新版本4.0.0.RELEASE并利用Spring Hymanson JSON集成在其余调用响应中发送JSON响应。
该教程是在Spring STS IDE中开发的,用于轻松创建Spring MVC框架代码,然后扩展为实现Restful架构。
在STS中创建一个新的Spring MVC项目,我们的最终项目将如下图所示。
我们将逐一研究每个组件。
Spring REST配置XML文件
我们的pom.xml文件如下所示。
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.theitroad</groupId> <artifactId>SpringRestExample</artifactId> <name>SpringRestExample</name> <packaging>war</packaging> <version>1.0.0-BUILD-SNAPSHOT</version> <properties> <java-version>1.6</java-version> <org.springframework-version>4.0.0.RELEASE</org.springframework-version> <org.aspectj-version>1.7.4</org.aspectj-version> <org.slf4j-version>1.7.5</org.slf4j-version> <Hymanson.databind-version>2.2.3</Hymanson.databind-version> </properties> <dependencies> <!-- Hymanson --> <dependency> <groupId>com.fasterxml.Hymanson.core</groupId> <artifactId>Hymanson-databind</artifactId> <version>${Hymanson.databind-version}</version> </dependency> <!-- Spring --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${org.springframework-version}</version> <exclusions> <!-- Exclude Commons Logging in favor of SLF4j --> <exclusion> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${org.springframework-version}</version> </dependency> <!-- AspectJ --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>${org.aspectj-version}</version> </dependency> <!-- Logging --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>${org.slf4j-version}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>${org.slf4j-version}</version> <scope>runtime</scope> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>${org.slf4j-version}</version> <scope>runtime</scope> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.15</version> <exclusions> <exclusion> <groupId>javax.mail</groupId> <artifactId>mail</artifactId> </exclusion> <exclusion> <groupId>javax.jms</groupId> <artifactId>jms</artifactId> </exclusion> <exclusion> <groupId>com.sun.jdmk</groupId> <artifactId>jmxtools</artifactId> </exclusion> <exclusion> <groupId>com.sun.jmx</groupId> <artifactId>jmxri</artifactId> </exclusion> </exclusions> <scope>runtime</scope> </dependency> <!-- @Inject --> <dependency> <groupId>javax.inject</groupId> <artifactId>javax.inject</artifactId> <version>1</version> </dependency> <!-- Servlet --> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!-- Test --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.7</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <artifactId>maven-eclipse-plugin</artifactId> <version>2.9</version> <configuration> <additionalProjectnatures> <projectnature>org.springframework.ide.eclipse.core.springnature</projectnature> </additionalProjectnatures> <additionalBuildcommands> <buildcommand>org.springframework.ide.eclipse.core.springbuilder</buildcommand> </additionalBuildcommands> <downloadSources>true</downloadSources> <downloadJavadocs>true</downloadJavadocs> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.5.1</version> <configuration> <source>1.6</source> <target>1.6</target> <compilerArgument>-Xlint:all</compilerArgument> <showWarnings>true</showWarnings> <showDeprecation>true</showDeprecation> </configuration> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>1.2.1</version> <configuration> <mainClass>org.test.int1.Main</mainClass> </configuration> </plugin> </plugins> </build> </project>
STS工具会为我们生成pom.xml文件。
但是,我已经将Spring Framework,AspectJ,SLF4J和Hymanson的版本更新为今天的最新版本。
大多数部分是通用的并且是自动生成的,需要注意的重要一点是,我在依赖项中添加了Hymanson JSON库,因为我们将使用它来将Objects转换为JSON,反之亦然。
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="https://java.sun.com/xml/ns/javaee" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://java.sun.com/xml/ns/javaee https://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <!-- The definition of the Root Spring Container shared by all Servlets and Filters --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring/root-context.xml</param-value> </context-param> <!-- Creates the Spring Container shared by all Servlets and Filters --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- Processes application requests --> <servlet> <servlet-name>appServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>appServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
该文件是自动生成的,对此我没有做任何更改。
但是,如果要更改上下文配置文件及其位置,则可以在web.xml文件中进行操作。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="https://www.springframework.org/schema/beans" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- Root Context: defines shared resources visible to all other web components --> </beans>
该文件包含对所有Web组件可见的共享资源,我们将开发一个简单的Rest服务,这就是为什么我在这里没有进行任何更改的原因。
<?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns="https://www.springframework.org/schema/mvc" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xmlns:beans="https://www.springframework.org/schema/beans" xmlns:context="https://www.springframework.org/schema/context" xsi:schemaLocation="https://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd https://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd https://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure --> <!-- Enables the Spring MVC @Controller programming model --> <annotation-driven <!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory --> <resources mapping="/resources/**" location="/resources/" <!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory --> <beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <beans:property name="prefix" value="/WEB-INF/views/" <beans:property name="suffix" value=".jsp" </beans:bean> <!-- Configure to plugin JSON as request and response in method handler --> <beans:bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"> <beans:property name="messageConverters"> <beans:list> <beans:ref bean="jsonMessageConverter" </beans:list> </beans:property> </beans:bean> <!-- Configure bean to convert JSON to POJO and vice versa --> <beans:bean id="jsonMessageConverter" class="org.springframework.http.converter.json.MappingHymanson2HttpMessageConverter"> </beans:bean> <context:component-scan base-package="com.theitroad.spring.controller" </beans:beans>
大部分零件是自动生成的,包含样板配置。
然而,需要注意的重要点是注释驱动的元素,以支持基于注释的配置,并将MappingHymanson2HttpMessageConverter插入到RequestMappingHandlerAdaptermessageConverters中,以便Hymanson API启动并将JSON转换为Java Bean,反之亦然。
通过进行此配置,我们将在请求正文中使用JSON,并将在响应中接收JSON数据。
Spring REST模型类
让我们编写一个简单的POJO类,作为我们的Restful Web服务方法的输入和输出。
package com.theitroad.spring.model; import java.io.Serializable; import java.util.Date; import com.fasterxml.Hymanson.databind.annotation.JsonSerialize; import com.fasterxml.Hymanson.databind.ser.std.DateSerializer; public class Employee implements Serializable{ private static final long serialVersionUID = -7788619177798333712L; private int id; private String name; private Date createdDate; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @JsonSerialize(using=DateSerializer.class) public Date getCreatedDate() { return createdDate; } public void setCreatedDate(Date createdDate) { this.createdDate = createdDate; } }
要注意的唯一重要一点是使用@JsonSerialize批注以将DateSerializer类用于从Java类型到JSON格式的日期转换,反之亦然。
Spring Restful Web服务端点
我们将具有以下其余Web服务终结点。
Sl. No | URI | HTTP Method | Details |
---|---|---|---|
1 | /rest/emp/dummy | GET | Health Check service, to insert a dummy data in the Employees data storage |
2 | /rest/emp/{id} | GET | To get the Employee object based on the id |
3 | /rest/emps | GET | To get the list of all the Employees in the data store |
4 | /rest/emp/create | POST | To create the Employee object and store it |
5 | /rest/emp/delete/{id} | PUT | To delete the Employee object from the data storage based on the id |
我们有一个将所有这些URI定义为String常量的类。
package com.theitroad.spring.controller; public class EmpRestURIConstants { public static final String DUMMY_EMP = "/rest/emp/dummy"; public static final String GET_EMP = "/rest/emp/{id}"; public static final String GET_ALL_EMP = "/rest/emps"; public static final String CREATE_EMP = "/rest/emp/create"; public static final String DELETE_EMP = "/rest/emp/delete/{id}"; }
Spring Restful Web服务控制器类
我们的EmployeeController类将发布上述所有Web服务端点。
让我们看一下该类的代码,然后我们将详细了解每种方法。
package com.theitroad.spring.controller; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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.theitroad.spring.model.Employee; /** * Handles requests for the Employee service. */ @Controller public class EmployeeController { private static final Logger logger = LoggerFactory.getLogger(EmployeeController.class); //Map to store employees, ideally we should use database Map<Integer, Employee> empData = new HashMap<Integer, Employee>(); @RequestMapping(value = EmpRestURIConstants.DUMMY_EMP, method = RequestMethod.GET) public @ResponseBody Employee getDummyEmployee() { logger.info("Start getDummyEmployee"); Employee emp = new Employee(); emp.setId(9999); emp.setName("Dummy"); emp.setCreatedDate(new Date()); empData.put(9999, emp); return emp; } @RequestMapping(value = EmpRestURIConstants.GET_EMP, method = RequestMethod.GET) public @ResponseBody Employee getEmployee(@PathVariable("id") int empId) { logger.info("Start getEmployee. ID="+empId); return empData.get(empId); } @RequestMapping(value = EmpRestURIConstants.GET_ALL_EMP, method = RequestMethod.GET) public @ResponseBody List<Employee> getAllEmployees() { logger.info("Start getAllEmployees."); List<Employee> emps = new ArrayList<Employee>(); Set<Integer> empIdKeys = empData.keySet(); for(Integer i : empIdKeys){ emps.add(empData.get(i)); } return emps; } @RequestMapping(value = EmpRestURIConstants.CREATE_EMP, method = RequestMethod.POST) public @ResponseBody Employee createEmployee(@RequestBody Employee emp) { logger.info("Start createEmployee."); emp.setCreatedDate(new Date()); empData.put(emp.getId(), emp); return emp; } @RequestMapping(value = EmpRestURIConstants.DELETE_EMP, method = RequestMethod.PUT) public @ResponseBody Employee deleteEmployee(@PathVariable("id") int empId) { logger.info("Start deleteEmployee."); Employee emp = empData.get(empId); empData.remove(empId); return emp; } }
为简单起见,我将所有员工数据存储在HashMap empData中。
@RequestMapping批注用于将请求URI映射到处理程序方法。
我们还可以指定客户端应用程序应使用的HTTP方法来调用rest方法。
@ResponseBody批注用于将响应对象映射到响应主体中。
处理程序方法返回响应对象后,MappingHymanson2HttpMessageConverter会启动并将其转换为JSON响应。
@PathVariable注释是从其余URI提取数据并将其映射到method参数的简单方法。
@RequestBody批注用于将请求正文JSON数据映射到Employee对象,同样由MappingHymanson2HttpMessageConverter映射完成。
其余代码简单易懂,我们的应用程序已准备好进行部署和测试。
只需将其导出为WAR文件并将其复制到servlet容器的Web应用程序目录中即可。
如果在STS中配置了服务器,则只需在服务器上运行它即可进行部署。
我正在使用WizTools RestClient调用其余的调用,但您也可以使用Chrome扩展程序Postman。
下面的屏幕快照显示了我们的应用程序公开的其余API的不同调用及其输出。
健康检查–获得虚拟员工休息电话
创建员工POST休息呼叫:确保请求Content-Type设置为" application/json",否则您将获得HTTP错误代码415。
Spring Rest客户程序
Rest客户端很好地测试了我们的Rest Web服务,但是大多数时候,我们需要通过程序来调用Rest服务。
我们可以使用SpringRestTemplate
轻松地调用这些方法。
以下是一个使用RestTemplate API调用我们的应用程序rest方法的简单程序。
package com.theitroad.spring; import java.util.LinkedHashMap; import java.util.List; import org.springframework.web.client.RestTemplate; import com.theitroad.spring.controller.EmpRestURIConstants; import com.theitroad.spring.model.Employee; public class TestSpringRestExample { public static final String SERVER_URI = "https://localhost:9090/SpringRestExample"; public static void main(String args[]){ testGetDummyEmployee(); System.out.println("*"); testCreateEmployee(); System.out.println("*"); testGetEmployee(); System.out.println("*"); testGetAllEmployee(); } private static void testGetAllEmployee() { RestTemplate restTemplate = new RestTemplate(); //we can't get List<Employee> because JSON convertor doesn't know the type of //object in the list and hence convert it to default JSON object type LinkedHashMap List<LinkedHashMap> emps = restTemplate.getForObject(SERVER_URI+EmpRestURIConstants.GET_ALL_EMP, List.class); System.out.println(emps.size()); for(LinkedHashMap map : emps){ System.out.println("ID="+map.get("id")+",Name="+map.get("name")+",CreatedDate="+map.get("createdDate"));; } } private static void testCreateEmployee() { RestTemplate restTemplate = new RestTemplate(); Employee emp = new Employee(); emp.setId(1);emp.setName("hyman Kumar"); Employee response = restTemplate.postForObject(SERVER_URI+EmpRestURIConstants.CREATE_EMP, emp, Employee.class); printEmpData(response); } private static void testGetEmployee() { RestTemplate restTemplate = new RestTemplate(); Employee emp = restTemplate.getForObject(SERVER_URI+"/rest/emp/1", Employee.class); printEmpData(emp); } private static void testGetDummyEmployee() { RestTemplate restTemplate = new RestTemplate(); Employee emp = restTemplate.getForObject(SERVER_URI+EmpRestURIConstants.DUMMY_EMP, Employee.class); printEmpData(emp); } public static void printEmpData(Employee emp){ System.out.println("ID="+emp.getId()+",Name="+emp.getName()+",CreatedDate="+emp.getCreatedDate()); } }
大部分程序易于理解,但是当调用rest方法返回Collection时,我们需要使用LinkedHashMap,因为JSON到对象的转换并不了解Employee对象,而是将其转换为LinkedHashMap的集合。
我们可以编写一个实用程序方法来将LinkedHashMap转换为Java Bean对象。
当我们运行上面的程序时,我们在控制台中获得以下输出。
ID=9999,Name=Dummy,CreatedDate=Tue Mar 04 21:02:41 PST 2014 * ID=1,Name=hyman Kumar,CreatedDate=Tue Mar 04 21:02:41 PST 2014 * ID=1,Name=hyman Kumar,CreatedDate=Tue Mar 04 21:02:41 PST 2014 * 2 ID=1,Name=hyman Kumar,CreatedDate=1393995761654 ID=9999,Name=Dummy,CreatedDate=1393995761381
另一点是," RestTemplate"放置方法没有设置响应对象的选项,因为应使用PUT方法在服务器上存储某些内容,并且简单的HTTP 200状态代码就足够了。