即表述性状态转移(REpresentational State Transfer),是一种基于HTTP的结构原则,一种表示被操作的资源的方法。
REST Web服务完全依赖HTTP方法,每一种方法都会对某一种资源进行操作。GET方法常用来获取某一资源或者资源集合。POST方法则用于创建。PUT方法用于更新。DELETE方法用于从系统中删除资源。
本章所用的依赖文件有:
<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.0modelVersion>
<groupId>org.springframework.samples.service.servicegroupId>
<artifactId>SpringAOPTestartifactId>
<version>0.0.1-SNAPSHOTversion>
<packaging>warpackaging>
<properties>
<java.version>1.6java.version>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
<jsp.version>2.3.1jsp.version>
<jstl.version>1.2jstl.version>
<servlet.version>3.1.0servlet.version>
<spring-framework.version>4.3.10.RELEASEspring-framework.version>
<hibernate.version>5.2.10.Finalhibernate.version>
<logback.version>1.2.3logback.version>
<slf4j.version>1.7.25slf4j.version>
<junit.version>4.12junit.version>
<aspectj.version>1.8.10aspectj.version>
<jackson.version>2.9.0jackson.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>${spring-framework.version}version>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>jstlartifactId>
<version>${jstl.version}version>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>javax.servlet-apiartifactId>
<version>${servlet.version}version>
<scope>providedscope>
dependency>
<dependency>
<groupId>javax.servlet.jspgroupId>
<artifactId>javax.servlet.jsp-apiartifactId>
<version>${jsp.version}version>
<scope>providedscope>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-txartifactId>
<version>${spring-framework.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aopartifactId>
<version>${spring-framework.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-coreartifactId>
<version>${spring-framework.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-beansartifactId>
<version>${spring-framework.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>${spring-framework.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webartifactId>
<version>${spring-framework.version}version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-apiartifactId>
<version>${slf4j.version}version>
<scope>compilescope>
dependency>
<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-classicartifactId>
<version>${logback.version}version>
<scope>runtimescope>
dependency>
<dependency>
<groupId>org.hibernategroupId>
<artifactId>hibernate-entitymanagerartifactId>
<version>${hibernate.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>${spring-framework.version}version>
<scope>testscope>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>${junit.version}version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>${aspectj.version}version>
dependency>
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-coreartifactId>
<version>${jackson.version}version>
dependency>
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-databindartifactId>
<version>${jackson.version}version>
dependency>
dependencies>
project>
首先在com.lonelyquantum.wileybookch11.domain包中创建域类User:
public class User {
private int id;
private String name;
public User() {}
public User(int id, String name){
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
}
然后去com.lonelyquantum.wileybookch11.repository包中创建文件UserRepository类存储User对象。
@Repository
public class UserRepository {
private Map users = new HashMap();
@PostConstruct
public void setup(){
users.put(1, new User(1,"Madoka Kaname"));
users.put(2, new User(2,"Homura Akemi"));
}
public void save(User user){
users.put(user.getId(), user);
}
public List findAll(){
return new ArrayList(users.values());
}
public User find(int id){
return users.get(id);
}
public void update(int id, User user){
users.put(id, user);
}
public void delete(int id){
users.remove(id);
}
}
该类通过Map来存储Id,UserName对,并提供了CRUD方法。
接下来在com.lonelyquantum.wileybookch11.controller包中创建UserRestController类进行控制。
@RestController
@RequestMapping("/rest")
public class UserRestController {
@Autowired
private UserRepository userRepository;
@RequestMapping(value = "/users", method = RequestMethod.POST)
public void save(@RequestBody User user){
userRepository.save(user);
}
@RequestMapping(value = "/users", method = RequestMethod.GET)
public List list(){
return userRepository.findAll();
}
@RequestMapping(value = "/users/{id}", method = RequestMethod.GET)
public User get(@PathVariable("id") int id){
User user = userRepository.find(id);
if(user == null){
throw new RestException(1, "User not found!", "User with id: " + id + " not found in the system");
}
return user;
}
@RequestMapping(value = "/users/{id}", method = RequestMethod.PUT)
public void update(@PathVariable("id") int id, @RequestBody User user){
userRepository.update(id, user);
}
@RequestMapping(value = "/users/{id}", method = RequestMethod.DELETE)
public ResponseEntity delete(@PathVariable("id") int id){
userRepository.delete(id);
return new ResponseEntity(Boolean.TRUE, HttpStatus.OK);
}
}
该类将Web请求映射到处理方法上。
接着采用springmvc-servlet.xml来配置servlet。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">
<context:component-scan base-package="com.lonelyquantum.wileybookch11" />
<context:annotation-config />
<mvc:annotation-driven />
beans>
当然,也可以通过在包com.lonelyquantum.wileybookch11.config中创建配置类来配置Servlet:
@Configuration
@ComponentScan(basePackages = {"com.lonelyquantum.wileybookch11"})
@EnableWebMvc
public class AppConfig {
}
然后在web.xml中用URL定义DispatcherServlet:
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<servlet>
<servlet-name>springmvcservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>springmvcservlet-name>
<url-pattern>/*url-pattern>
servlet-mapping>
web-app>
如果是采用Java配置的servlet则应该在配置中引用AppConfig类。
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<servlet>
<servlet-name>springmvcservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextClassparam-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
param-value>
init-param>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>
com.lonelyquantum.wileybookch11.config.AppConfig
param-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>springmvcservlet-name>
<url-pattern>/*url-pattern>
servlet-mapping>
web-app>
将工程部署到Tomcat上运行容器,就可以用SoapUI进行REST调试了。
打开SoapUI后首先File->NEW REST Project
如上填写url。
然后生成如下项目:
之后点击左上角运行图标,将右侧选项卡调整至JSON。可得返回结果:
此处Method为GET,表示采用的是GET方法。在user路径采用该方法返回的是全部用户的User类。
可以右键资源添加新方法以测试其他操作:
我们可以先建立一个名为New User的POST方法来创建新用户:
为了使用这个方法,我们需要输入新用户的参数再执行:
注意输入的数据类型调整正确。左下角会显示相应时间,该方法没有返回值,但是可以再次执行之前的GET方法显示插入的用户。
接下来创建Update方法,采用的是PUT方法,注意,该方法的资源需要指向具体的user,输入类型也应该调整为json。
执行完毕再使用第一个GET方法可以看到user3的内容已经更新:
最后创建DELETE 方法,该方法与UPDATE类似,也需要指定具体user。
执行完毕再用第一个GET方法查看,可发现id=3的user已经被删除。
可以从方法中返回HTTP状态码告知用户方法执行情况。之前delete方法中就返回了
return new ResponseEntity
表示成功。状态码如下:(Boolean.TRUE, HttpStatus.OK);
1xx 信息
2xx 成功
3xx 重定向
4xx 客户端错误
5xx 服务器错误
刚刚的项目中我们的REST方法返回的都是JSON格式的数据,其实也可以用XML格式返回数据。只要给域类添加如下注解:
@XmlRootElement
public class User {
@XmlElement
private int id;
@XmlElement
private String name;
public User() {}
public User(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
先在com.lonelyquantum.wileybookch11.domain创建RestErrorMessage类来表示异常信息。
public class RestErrorMessage {
private HttpStatus status;
private int code;
private String message;
private String detailedMessage;
private String exceptionMessage;
public RestErrorMessage(HttpStatus status, int code, String message, String detailedMessage,
String exceptionMessage) {
super();
this.status = status;
this.code = code;
this.message = message;
this.detailedMessage = detailedMessage;
this.exceptionMessage = exceptionMessage;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
public String getDetailedMessage() {
return detailedMessage;
}
public String getExceptionMessage() {
return exceptionMessage;
}
}
在该项目中创建com.lonelyquantum.wileybookch11.exception包来保存抛出的异常
public class RestException extends RuntimeException{
private int code;
private String message;
private String detailedMessage;
public RestException(int code, String message, String detailedMessage){
this.code = code;
this.message = message;
this.detailedMessage = detailedMessage;
}
public int getCode() {
return code;
}
@Override
public String getMessage() {
// TODO Auto-generated method stub
return super.getMessage();
}
public String getDetailedMessage() {
return detailedMessage;
}
}
以及com.lonelyquantum.wileybookch11.handler包来保存处理异常的方法。
@ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler{
@ExceptionHandler(Exception.class)
protected ResponseEntity
Controller中的异常处理之前已经写好。
这时我们如果试图查找已经被删除的id=3的user就会返回如下错误信息。
通过Spring提供的RestTemplate模板类,我们可以通过测试代码来情锁模拟HTTP请求。二者映射名称如下:
可以在/src/test/java目录下创建com.lonelyquantum.wileybookch11包并在其中创建如下测试类用于测试各个方法:
public class AddUserTest {
@Test
public void addUserWorksOK(){
RestTemplate template = new RestTemplate();
User user = new User(3,"Sayaka Miki");
ResponseEntity resultSave = template.postForEntity("http://localhost:8080/SpringREST/rest/users", user, Void.class);
assertNotNull(resultSave);
}
}
public class DeleteUserTest {
@Test
public void deleteUserWorksOK(){
RestTemplate template = new RestTemplate();
template.delete("http://localhost:8080/SpringREST/rest/users/3");
ResponseEntity resultList = template.getForEntity("http://localhost:8080/SpringREST/rest/users", List.class);
assertNotNull(resultList);
assertNotNull(resultList.getBody());
assertThat(resultList.getBody().size(), is(2));
}
}
public class ListUsersTest {
@Test
public void listUserWorksOK(){
RestTemplate template = new RestTemplate();
ResponseEntity result = template.getForEntity("http://localhost:8080/SpringREST/rest/users", List.class);
assertNotNull(result);
assertNotNull(result.getBody());
assertThat(result.getBody().size(), is(2));
}
}
public class UpdateUserTest {
@Test
public void updateUserWorksOK(){
RestTemplate template = new RestTemplate();
User user = new User(3, "Sakura Kyouko");
template.put("http://localhost:8080/SpringREST/rest/users/3", user);
}
}
@RunWith(Suite.class)
@Suite.SuiteClasses({
ListUsersTest.class,
AddUserTest.class,
UpdateUserTest.class,
DeleteUserTest.class
})
public class UserRestControllerTestSuite {
}
其中最后一个测试类封装了前面四个测试类并按顺序运行他们。这样的顺序保证了运行测试之后不会对数据造成任何修改。