Beginning Spring学习笔记——第11章 使用Spring开发REST风格的Web服务
来源:互联网 发布:memcached php 编辑:程序博客网 时间:2024/06/15 01:52
REST
即表述性状态转移(REpresentational State Transfer),是一种基于HTTP的结构原则,一种表示被操作的资源的方法。
REST Web服务完全依赖HTTP方法,每一种方法都会对某一种资源进行操作。GET方法常用来获取某一资源或者资源集合。POST方法则用于创建。PUT方法用于更新。DELETE方法用于从系统中删除资源。
使用REST Web服务进行CRUD操作
本章所用的依赖文件有:
<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>org.springframework.samples.service.service</groupId> <artifactId>SpringAOPTest</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>war</packaging> <properties> <!-- Generic properties --> <java.version>1.6</java.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <!-- Web --> <jsp.version>2.3.1</jsp.version> <jstl.version>1.2</jstl.version> <servlet.version>3.1.0</servlet.version> <!-- Spring --> <spring-framework.version>4.3.10.RELEASE</spring-framework.version> <!-- Hibernate / JPA --> <hibernate.version>5.2.10.Final</hibernate.version> <!-- Logging --> <logback.version>1.2.3</logback.version> <slf4j.version>1.7.25</slf4j.version> <!-- Test --> <junit.version>4.12</junit.version> <!-- AspectJ --> <aspectj.version>1.8.10</aspectj.version> <!-- JSON Evaluation --> <jackson.version>2.9.0</jackson.version> </properties> <dependencies> <!-- Spring MVC --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring-framework.version}</version> </dependency> <!-- Other Web dependencies --> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>${jstl.version}</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>${servlet.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>${jsp.version}</version> <scope>provided</scope> </dependency> <!-- Spring and Transactions --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>${spring-framework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${spring-framework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring-framework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>${spring-framework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring-framework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring-framework.version}</version> </dependency> <!-- Logging with SLF4J & LogBack --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>${slf4j.version}</version> <scope>compile</scope> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>${logback.version}</version> <scope>runtime</scope> </dependency> <!-- Hibernate --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-entitymanager</artifactId> <version>${hibernate.version}</version> </dependency> <!-- Test Artifacts --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring-framework.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency> <!-- AspectJ --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>${aspectj.version}</version> </dependency> <!-- JSON Evaluation --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>${jackson.version}</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <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对象。
@Repositorypublic class UserRepository { private Map<Integer, User> users = new HashMap<Integer, User>(); @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<User> findAll(){ return new ArrayList<User>(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<User> 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<Boolean> delete(@PathVariable("id") int id){ userRepository.delete(id); return new ResponseEntity<Boolean>(Boolean.TRUE, HttpStatus.OK); }}
该类将Web请求映射到处理方法上。
接着采用springmvc-servlet.xml来配置servlet。
<?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: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"})@EnableWebMvcpublic 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>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-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>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextClass</param-name> <param-value> org.springframework.web.context.support.AnnotationConfigWebApplicationContext </param-value> </init-param> <init-param> <param-name>contextConfigLocation</param-name> <param-value> com.lonelyquantum.wileybookch11.config.AppConfig </param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-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>(Boolean.TRUE, HttpStatus.OK);
表示成功。状态码如下:
1xx 信息
2xx 成功
3xx 重定向
4xx 客户端错误
5xx 服务器错误
使用XML响应的REST Web服务
刚刚的项目中我们的REST方法返回的都是JSON格式的数据,其实也可以用XML格式返回数据。只要给域类添加如下注解:
@XmlRootElementpublic 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; }
就可以在查找某个user时返回XML格式的输出
使用异常处理机制
先在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包来保存处理异常的方法。
@ControllerAdvicepublic class RestExceptionHandler extends ResponseEntityExceptionHandler{ @ExceptionHandler(Exception.class) protected ResponseEntity<Object> handleInvalidRequest(RestException e, ServletWebRequest request){ RestErrorMessage error = new RestErrorMessage(HttpStatus.valueOf(request.getResponse().getStatus()), e.getCode(), e.getMessage(), e.getDetailedMessage(), e.toString()); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); return handleExceptionInternal(e, error, headers, HttpStatus.OK, request); }}
Controller中的异常处理之前已经写好。
这时我们如果试图查找已经被删除的id=3的user就会返回如下错误信息。
对REST风格服务进行单元测试
通过Spring提供的RestTemplate模板类,我们可以通过测试代码来情锁模拟HTTP请求。二者映射名称如下:
- GET:getForObject(string, Class,String…)
- PUT: put(String, Object, String)
- POST:postForLocation(String, Object, String…)
- DELETE:delete(String, String)
- HEAD:headForHeaders(String,String)
- OPTIONS:optionsForAllow(String, String)
可以在/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<Void> 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<List> 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<List> 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 {}
其中最后一个测试类封装了前面四个测试类并按顺序运行他们。这样的顺序保证了运行测试之后不会对数据造成任何修改。
- Beginning Spring学习笔记——第11章 使用Spring开发REST风格的Web服务
- Beginning Spring学习笔记——第7章 使用Spring进行测试驱动开发
- Beginning Spring学习笔记——第2章(三)Spring的Bean管理
- Beginning Spring学习笔记——第4章(一)Spring JDBC连接的配置
- Beginning Spring学习笔记——第5章(二)Spring的JPA支持
- Beginning Spring学习笔记——第1章
- Beginning Spring学习笔记——第9章 SpEL
- Beginning Spring学习笔记——第10章 缓存
- Beginning Spring学习笔记——第4章(二)使用Spring执行数据访问操作
- Beginning Spring学习笔记——第6章(二)使用Spring进行声明式事务管理
- Beginning Spring学习笔记——第6章(三)使用Spring进行编程式事务管理
- Beginning Spring学习笔记——第2章(一)Spring IoC容器
- Beginning Spring学习笔记——第3章(一)Spring MVC基础
- Beginning Spring学习笔记——第6章(一)Spring事务管理基础
- Beginning Spring学习笔记——第8章 Spring AOP
- Beginning Spring学习笔记——第2章(二)依赖注入
- Beginning Spring学习笔记——第3章(二)表单处理
- Beginning Spring学习笔记——第5章(一)ORM和JPA基础
- 数据结构之算法时间复杂度
- pgpool状态显示不正确时建议退出重新连接再检查
- 模拟退火算法
- 实现Runnable的线程类和继承Tread的线程类之间的区别
- 任务队列
- Beginning Spring学习笔记——第11章 使用Spring开发REST风格的Web服务
- KBuild MakeFile介绍(转)
- search-for-a-range
- 对Scanner的使用
- HGDB 流复制一主两备切换注意事项
- 1756:八皇后(2.5基本算法之搜索)
- linux中的子进程和父进程
- Java List集合的遍历
- 编程范式12 笔记 编译预处理 宏展开