REST是REpresentational State Transfer的缩写(通常中文翻译为表述性状态转移),REST 是一种体系结构,而 HTTP 是一种包含了 REST 架构属性的协议,为了便于理解,咱们把它的首字母拆分红不一样的几个部分:html
简单地说,REST 就是将资源的状态以适合客户端或服务端的形式从服务端转移到客户端(或者反过来)。在 REST 中,资源经过 URL 进行识别和定位,而后经过行为(即 HTTP 方法)来定义 REST 来完成怎样的功能。java
在平时的 Web 开发中,method 经常使用的值是 GET 和 POST,可是实际上,HTTP 方法还有 PATCH、DELETE、PUT 等其余值,这些方法又一般会匹配为以下的 CRUD 动做:git
CRUD 动做 | HTTP 方法 |
---|---|
Create | POST |
Read | GET |
Update | PUT 或 PATCH |
Delete | DELETE |
尽管一般来说,HTTP 方法会映射为 CRUD 动做,但这并非严格的限制,有时候 PUT 也能够用来建立新的资源,POST 也能够用来更新资源。实际上,POST 请求非幂等的特性(即同一个 URL 能够获得不一样的结果)使其成一个很是灵活地方法,对于没法适应其余 HTTP 方法语义的操做,它都可以胜任。github
在使用 RESTful 风格以前,咱们若是想要增长一条商品数据一般是这样的:web
/addCategory?name=xxx
可是使用了 RESTful 风格以后就会变成:spring
/category
这就变成了使用同一个 URL ,经过约定不一样的 HTTP 方法来实施不一样的业务,这就是 RESTful 风格所作的事情了,为了有一个更加直观的理解,引用一下来自how2j.cn的图:编程
下面我使用 SpringBoot 结合文章:http://blog.didispace.com/springbootrestfulapi/ 来实例演示如何在 SpringBoot 中使用 RESTful 风格的编程并如何作单元测试json
RESTful API 具体设计以下:api
User实体定义:浏览器
public class User { private Long id; private String name; private Integer age; // 省略setter和getter }
实现对User对象的操做接口
@RestController @RequestMapping(value="/users") // 经过这里配置使下面的映射都在/users下 public class UserController { // 建立线程安全的Map static Map<Long, User> users = Collections.synchronizedMap(new HashMap<Long, User>()); @RequestMapping(value="/", method=RequestMethod.GET) public List<User> getUserList() { // 处理"/users/"的GET请求,用来获取用户列表 // 还能够经过@RequestParam从页面中传递参数来进行查询条件或者翻页信息的传递 List<User> r = new ArrayList<User>(users.values()); return r; } @RequestMapping(value="/", method=RequestMethod.POST) public String postUser(@ModelAttribute User user) { // 处理"/users/"的POST请求,用来建立User // 除了@ModelAttribute绑定参数以外,还能够经过@RequestParam从页面中传递参数 users.put(user.getId(), user); return "success"; } @RequestMapping(value="/{id}", method=RequestMethod.GET) public User getUser(@PathVariable Long id) { // 处理"/users/{id}"的GET请求,用来获取url中id值的User信息 // url中的id可经过@PathVariable绑定到函数的参数中 return users.get(id); } @RequestMapping(value="/{id}", method=RequestMethod.PUT) public String putUser(@PathVariable Long id, @ModelAttribute User user) { // 处理"/users/{id}"的PUT请求,用来更新User信息 User u = users.get(id); u.setName(user.getName()); u.setAge(user.getAge()); users.put(id, u); return "success"; } @RequestMapping(value="/{id}", method=RequestMethod.DELETE) public String deleteUser(@PathVariable Long id) { // 处理"/users/{id}"的DELETE请求,用来删除User users.remove(id); return "success"; } }
参考文章:http://tengj.top/2017/12/28/springboot12/#Controller单元测试
看过这几篇文章以后以为好棒,还有这么方便的测试方法,这些之前都没有接触过...
下面针对该Controller编写测试用例验证正确性,具体以下。固然也能够经过浏览器插件等进行请求提交验证,由于涉及一些包的导入,这里给出所有代码:
package cn.wmyskxz.springboot; import cn.wmyskxz.springboot.controller.UserController; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.mock.web.MockServletContext; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.RequestBuilder; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import static org.hamcrest.Matchers.equalTo; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * @author: @我没有三颗心脏 * @create: 2018-05-29-上午 8:39 */ @RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = MockServletContext.class) @WebAppConfiguration public class ApplicationTests { private MockMvc mvc; @Before public void setUp() throws Exception { mvc = MockMvcBuilders.standaloneSetup(new UserController()).build(); } @Test public void testUserController() throws Exception { // 测试UserController RequestBuilder request = null; // 一、get查一下user列表,应该为空 request = get("/users/"); mvc.perform(request) .andExpect(status().isOk()) .andExpect(content().string(equalTo("[]"))); // 二、post提交一个user request = post("/users/") .param("id", "1") .param("name", "测试大师") .param("age", "20"); mvc.perform(request) .andExpect(content().string(equalTo("success"))); // 三、get获取user列表,应该有刚才插入的数据 request = get("/users/"); mvc.perform(request) .andExpect(status().isOk()) .andExpect(content().string(equalTo("[{\"id\":1,\"name\":\"测试大师\",\"age\":20}]"))); // 四、put修改id为1的user request = put("/users/1") .param("name", "测试终极大师") .param("age", "30"); mvc.perform(request) .andExpect(content().string(equalTo("success"))); // 五、get一个id为1的user request = get("/users/1"); mvc.perform(request) .andExpect(content().string(equalTo("{\"id\":1,\"name\":\"测试终极大师\",\"age\":30}"))); // 六、del删除id为1的user request = delete("/users/1"); mvc.perform(request) .andExpect(content().string(equalTo("success"))); // 七、get查一下user列表,应该为空 request = get("/users/"); mvc.perform(request) .andExpect(status().isOk()) .andExpect(content().string(equalTo("[]"))); } }
MockMvc实现了对HTTP请求的模拟,从示例的代码就可以看出MockMvc的简单用法,它可以直接使用网络的形式,转换到Controller的调用,这样使得测试速度快、不依赖网络环境,并且提供了一套验证的工具,这样可使得请求的验证统一并且很方便。
须要注意的就是在MockMvc使用以前须要先用MockMvcBuilders构建MockMvc对象,若是对单元测试感兴趣的童鞋请戳上面的连接哦,这里就不细说了
运行测试类,控制台返回的信息以下:
__ __ __ /\ \ __/\ \ /\ \ \ \ \/\ \ \ \ ___ ___ __ __ ____\ \ \/'\ __ _ ____ \ \ \ \ \ \ \ /' __` __`\/\ \/\ \ /',__\\ \ , < /\ \/'\/\_ ,`\ \ \ \_/ \_\ \/\ \/\ \/\ \ \ \_\ \/\__, `\\ \ \ \`\\/> </\/_/ /_ \ `\___x___/\ \_\ \_\ \_\/`____ \/\____/ \ \_\ \_\/\_/\_\ /\____\ '\/__//__/ \/_/\/_/\/_/`/___/> \/___/ \/_/\/_/\//\/_/ \/____/ /\___/ \/__/ 2018-05-29 09:28:18.730 INFO 5884 --- [ main] cn.wmyskxz.springboot.ApplicationTests : Starting ApplicationTests on SC-201803262103 with PID 5884 (started by Administrator in E:\Java Projects\springboot) 2018-05-29 09:28:18.735 INFO 5884 --- [ main] cn.wmyskxz.springboot.ApplicationTests : No active profile set, falling back to default profiles: default 2018-05-29 09:28:18.831 INFO 5884 --- [ main] o.s.w.c.s.GenericWebApplicationContext : Refreshing org.springframework.web.context.support.GenericWebApplicationContext@7c37508a: startup date [Tue May 29 09:28:18 CST 2018]; root of context hierarchy 2018-05-29 09:28:19.200 INFO 5884 --- [ main] cn.wmyskxz.springboot.ApplicationTests : Started ApplicationTests in 1.184 seconds (JVM running for 2.413) 2018-05-29 09:28:19.798 INFO 5884 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/users/{id}],methods=[PUT]}" onto public java.lang.String cn.wmyskxz.springboot.controller.UserController.putUser(java.lang.Long,cn.wmyskxz.springboot.pojo.User) 2018-05-29 09:28:19.800 INFO 5884 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/users/],methods=[GET]}" onto public java.util.List<cn.wmyskxz.springboot.pojo.User> cn.wmyskxz.springboot.controller.UserController.getUserList() 2018-05-29 09:28:19.800 INFO 5884 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/users/],methods=[POST]}" onto public java.lang.String cn.wmyskxz.springboot.controller.UserController.postUser(cn.wmyskxz.springboot.pojo.User) 2018-05-29 09:28:19.801 INFO 5884 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/users/{id}],methods=[DELETE]}" onto public java.lang.String cn.wmyskxz.springboot.controller.UserController.deleteUser(java.lang.Long) 2018-05-29 09:28:19.801 INFO 5884 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/users/{id}],methods=[GET]}" onto public cn.wmyskxz.springboot.pojo.User cn.wmyskxz.springboot.controller.UserController.getUser(java.lang.Long) 2018-05-29 09:28:19.850 INFO 5884 --- [ main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.test.web.servlet.setup.StubWebApplicationContext@42f8285e 2018-05-29 09:28:19.924 INFO 5884 --- [ main] o.s.mock.web.MockServletContext : Initializing Spring FrameworkServlet '' 2018-05-29 09:28:19.925 INFO 5884 --- [ main] o.s.t.web.servlet.TestDispatcherServlet : FrameworkServlet '': initialization started 2018-05-29 09:28:19.926 INFO 5884 --- [ main] o.s.t.web.servlet.TestDispatcherServlet : FrameworkServlet '': initialization completed in 1 ms
经过控制台信息,咱们得知经过 RESTful 风格能成功调用到正确的方法而且能获取到或者返回正确的参数,没有任何错误,则说明成功!
若是你想要看到更多的细节信息,能够在每次调用 perform()
方法后再跟上一句 .andDo(MockMvcResultHandlers.print())
,例如:
// 一、get查一下user列表,应该为空 request = get("/users/"); mvc.perform(request) .andExpect(status().isOk()) .andExpect(content().string(equalTo("[]"))) .andDo(MockMvcResultHandlers.print());
就能看到详细的信息,就像下面这样:
MockHttpServletRequest: HTTP Method = GET Request URI = /users/ Parameters = {} Headers = {} Body = <no character encoding set> Session Attrs = {} Handler: Type = cn.wmyskxz.springboot.controller.UserController Method = public java.util.List<cn.wmyskxz.springboot.pojo.User> cn.wmyskxz.springboot.controller.UserController.getUserList() Async: Async started = false Async result = null Resolved Exception: Type = null ModelAndView: View name = null View = null Model = null FlashMap: Attributes = null MockHttpServletResponse: Status = 200 Error message = null Headers = {Content-Type=[application/json;charset=UTF-8]} Content type = application/json;charset=UTF-8 Body = [] Forwarded URL = null Redirected URL = null Cookies = []
咱们仍然使用 @RequestMapping 注解,但不一样的是,咱们指定 method 属性来处理不一样的 HTTP 方法,而且经过 @PathVariable 注解来将 HTTP 请求中的属性绑定到咱们指定的形参上。
事实上,Spring 4.3 以后,为了更好的支持 RESTful 风格,增长了几个注解:@PutMapping、@GetMapping、@DeleteMapping、@PostMapping,从名字也能大概的看出,其实也就是将 method 属性的值与 @RequestMapping 进行了绑定而已,例如,咱们对UserController中的deleteUser方法进行改造:
-----------改造前----------- @RequestMapping(value="/{id}", method=RequestMethod.DELETE) public String deleteUser(@PathVariable Long id) { // 处理"/users/{id}"的DELETE请求,用来删除User users.remove(id); return "success"; } -----------改造后----------- @DeleteMapping("/{id}") public String deleteUser(@PathVariable Long id) { // 处理"/users/{id}"的DELETE请求,用来删除User users.remove(id); return "success"; }
参考文章:http://blog.didispace.com/springbootswagger2/
RESTful 风格为后台与前台的交互提供了简洁的接口API,而且有利于减小与其余团队的沟通成本,一般状况下,咱们会建立一份RESTful API文档来记录全部的接口细节,可是这样作有如下的几个问题:
Swagger2的出现就是为了解决上述的这些问题,而且可以轻松的整合到咱们的SpringBoot中去,它既能够减小咱们建立文档的工做量,同时说明内容又能够整合到代码之中去,让维护文档和修改代码整合为一体,可让咱们在修改代码逻辑的同时方便的修改文档说明,这太酷了,另外Swagger2页提供了强大的页面测试功能来调试每一个RESTful API,具体效果以下:
让咱们赶忙来看看吧:
在 pom.xml
中加入Swagger2的依赖:
<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.2.2</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.2.2</version> </dependency>
在SpringBoot启动类的同级目录下建立Swagger2的配置类 Swagger2
:
@Configuration @EnableSwagger2 public class Swagger2 { @Bean public Docket createRestApi() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors.basePackage("cn.wmyskxz.springboot")) .paths(PathSelectors.any()) .build(); } private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("Spring Boot中使用Swagger2构建RESTful APIs") .description("原文地址连接:http://blog.didispace.com/springbootswagger2/") .termsOfServiceUrl("http://blog.didispace.com/") .contact("@我没有三颗心脏") .version("1.0") .build(); } }
如上面的代码所示,经过 @Configuration
注解让Spring来加载该配置类,再经过 @EnableSwagger2
注解来启动Swagger2;
再经过 createRestApi
函数建立 Docket
的Bean以后,apiInfo()
用来建立该API的基本信息(这些基本信息会展示在文档页面中),select()
函数返回一个 ApiSelectorBuilder
实例用来控制哪些接口暴露给Swagger来展示,本例采用指定扫描的包路径来定义,Swagger会扫描该包下全部的Controller定义的API,并产生文档内容(除了被 @ApiIgnore
指定的请求)
在完成了上述配置后,其实已经能够生产文档内容,可是这样的文档主要针对请求自己,而描述主要来源于函数等命名产生,对用户并不友好,咱们一般须要本身增长一些说明来丰富文档内容。以下所示,咱们经过@ApiOperation
注解来给API增长说明、经过@ApiImplicitParams
、@ApiImplicitParam
注解来给参数增长说明。
@RestController @RequestMapping(value="/users") // 经过这里配置使下面的映射都在/users下,可去除 public class UserController { static Map<Long, User> users = Collections.synchronizedMap(new HashMap<Long, User>()); @ApiOperation(value="获取用户列表", notes="") @RequestMapping(value={""}, method=RequestMethod.GET) public List<User> getUserList() { List<User> r = new ArrayList<User>(users.values()); return r; } @ApiOperation(value="建立用户", notes="根据User对象建立用户") @ApiImplicitParam(name = "user", value = "用户详细实体user", required = true, dataType = "User") @RequestMapping(value="", method=RequestMethod.POST) public String postUser(@RequestBody User user) { users.put(user.getId(), user); return "success"; } @ApiOperation(value="获取用户详细信息", notes="根据url的id来获取用户详细信息") @ApiImplicitParam(name = "id", value = "用户ID", required = true, dataType = "Long") @RequestMapping(value="/{id}", method=RequestMethod.GET) public User getUser(@PathVariable Long id) { return users.get(id); } @ApiOperation(value="更新用户详细信息", notes="根据url的id来指定更新对象,并根据传过来的user信息来更新用户详细信息") @ApiImplicitParams({ @ApiImplicitParam(name = "id", value = "用户ID", required = true, dataType = "Long"), @ApiImplicitParam(name = "user", value = "用户详细实体user", required = true, dataType = "User") }) @RequestMapping(value="/{id}", method=RequestMethod.PUT) public String putUser(@PathVariable Long id, @RequestBody User user) { User u = users.get(id); u.setName(user.getName()); u.setAge(user.getAge()); users.put(id, u); return "success"; } @ApiOperation(value="删除用户", notes="根据url的id来指定删除对象") @ApiImplicitParam(name = "id", value = "用户ID", required = true, dataType = "Long") @RequestMapping(value="/{id}", method=RequestMethod.DELETE) public String deleteUser(@PathVariable Long id) { users.remove(id); return "success"; } }
完成上述代码添加以后,启动Spring Boot程序,访问:http://localhost:8080/swagger-ui.html,就能看到前文展现的RESTful API的页面,咱们能够点开具体的API请求,POST类型的/users请求为例,可找到上述代码中咱们配置的Notes信息以及参数user的描述信息,以下图所示:
在上图请求的页面中,咱们能够看到一个Value的输入框,而且在右边的Model Schema中有示例的User对象模板,咱们点击右边黄色的区域Value框中就会自动填好示例的模板数据,咱们能够稍微修改修改,而后点击下方的 “Try it out!”
按钮,便可完成一次请求调用,这太酷了。
对比以前用文档来记录RESTful API的方式,咱们经过增长少许的配置内容,在原有代码的基础上侵入了忍受范围内的代码,就能够达到如此方便、直观的效果,能够说是使用Swagger2来对API文档进行管理,是个很不错的选择!
欢迎转载,转载请注明出处!
简书ID:@我没有三颗心脏
github:wmyskxz 欢迎关注公众微信号:wmyskxz_javaweb 分享本身的Java Web学习之路以及各类Java学习资料