Spring是使用最广泛的J2EE框架之一。我们之前已经知道如何使用SpringMVC创建基于Java的web应用程序。今天我们要学习创造Spring Restful Web服务使用Spring Mvc,然后用Rest客户机进行测试。最后,我们还将研究如何使用SpringRestTemplateAPI.
目录
Spring座
我们将使用Spring的最新版本4.0.0.放行利用Spring杰克逊JSON集成以在rest调用响应中发送JSON响应。本教程是在springsts-IDE中开发的,目的是方便地创建Spring Mvc框架代码,然后扩展到实现Restful架构。
在STS中创建一个新的Spring Mvc项目,我们的最终项目将如下图所示。我们将逐一研究每个组件。
springrest配置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.journaldev</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>
<jackson.databind-version>2.2.3</jackson.databind-version>
</properties>
<dependencies>
<!-- Jackson -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.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框架、AspectJ、SLF4J和杰克逊今天的最新版本。大部分部分是公共的,并且是自动生成的,需要注意的是,我在依赖项中添加了Jackson JSON库,因为我们将使用它将对象转换为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>
<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>
<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">
</beans:bean>
<context:component-scan base-package="com.journaldev.spring.controller" />
</beans:beans>
大部分零件是自动生成的,包含锅炉板配置。然而,需要注意的要点是注解驱动的元素来支持基于注解的配置和插件MappingJackson2HttpMessageConverter
to the RequestMappingHandlerAdapter
messageConverters
so that Jackson API kicks in and converts JSON to Java Beans and vice versa. By having this configuration, we will be using JSON in request body and we will receive JSON data in the response.
REST-Spring模型
让我们编写一个简单的POJO类,作为restfulweb服务方法的输入和输出。
package com.journaldev.spring.model;
import java.io.Serializable;
import java.util.Date;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.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
annotation to use DateSerializer
class for Date conversion from Java type to JSON format and vice versa.
Spring Restful Web服务端点
我们将拥有以下restweb服务端点。
序号 | URI | HTTP方法 | 细节 |
---|---|---|---|
1 | /休息/电磁脉冲/假人 | 得到 | 健康检查服务,在雇员数据存储中插入一个虚拟数据 |
2 | /休息/emp/{id} | 得到 | 根据id获取Employee对象 |
三 | /休息/电磁脉冲 | 得到 | 获取数据存储中所有员工的列表 |
4 | /休息/锻炼/创造 | 岗位 | 创建并存储Employee对象 |
5 | /rest/emp/delete/{id} | 把 | 根据id从数据存储中删除Employee对象 |
我们有一个类将所有这些URI定义为字符串常量。
package com.journaldev.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.journaldev.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.journaldev.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映射到处理程序方法。我们还可以指定客户端应用程序调用rest方法时应该使用的HTTP方法。
@ResponseBody注解用于映射响应正文中的响应对象。一旦handler方法返回了response对象,MappingJackson2HttpMessageConverter就会启动并将其转换为JSON响应。
@PathVariable注解是从rest URI提取数据并将其映射到方法参数的简单方法。
@RequestBody注解用于将请求主体JSON数据映射到Employee对象,这同样是通过MappingJackson2HttpMessageConverter映射完成的。
其余的代码都很简单,并且可以自我理解,我们的应用程序已经准备好进行部署和测试了。只需导出为WAR文件并将其复制到servlet容器web app目录中。如果在STS中配置了服务器,那么只需在服务器上运行它就可以部署它。
我使用WizTools RestClient来调用rest调用,但是你也可以使用Chrome扩展Postman。
下面的屏幕截图显示了应用程序公开的restapi的不同调用及其输出。
创建员工休息后呼叫:请确保request Content Type设置为“;application/json”;,否则将得到HTTP错误代码415。
Spring Rest客户端程序
Rest客户机可以很好地测试restweb服务,但是大多数时候,我们需要通过我们的程序调用Rest服务。我们可以用SpringRestTemplate
to invoke these methods easily. Below is a simple program invoking our application rest methods using RestTemplate API.
package com.journaldev.spring;
import java.util.LinkedHashMap;
import java.util.List;
import org.springframework.web.client.RestTemplate;
import com.journaldev.spring.controller.EmpRestURIConstants;
import com.journaldev.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("Pankaj 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方法返回集合时,我们需要使用LinkedHashMap
because JSON to object conversion doesn’t know about the Employee object and converts it to the collection of LinkedHashMap
. We can write a utility method to convert from LinkedHashMap
to our Java Bean object.
当我们运行上述程序时,我们在控制台中得到以下输出。
ID=9999,Name=Dummy,CreatedDate=Tue Mar 04 21:02:41 PST 2014
*****
ID=1,Name=Pankaj Kumar,CreatedDate=Tue Mar 04 21:02:41 PST 2014
*****
ID=1,Name=Pankaj Kumar,CreatedDate=Tue Mar 04 21:02:41 PST 2014
*****
2
ID=1,Name=Pankaj Kumar,CreatedDate=1393995761654
ID=9999,Name=Dummy,CreatedDate=1393995761381
另一点是RestTemplate
put methods doesn’t have option to set response object because PUT method should be used to store something on the server and a simple HTTP 200 status code should be sufficient.
以上是Spring Restful Web应用程序教程的全部内容。从上面的链接下载示例项目,并与它一起玩以了解更多。
更新:由于有许多请求提供类似的XML示例以及同时支持XML和JSON,我在中扩展了这个应用程序Spring REST XML JSON示例支持XML和JSON请求和响应。我强烈建议您通过这个过程来了解spring框架的美妙之处,以及实现这一点的容易程度。