Spring Boot之路(四)--Swagger2(RESTful API统一文档)

现如今的互联网开发,一款成熟的产品往往不只有Web作为前端,有可能要面对多个开发人员或多个开发团队:IOS开发、Android开发或是Web开发等等,现在的多终端时代,有可能我们需要开发所有市面的终端设备。

由于Spring Boot能够快速开发、便捷部署等特性,相信有很大一部分Spring Boot的用户会用来构建RESTful API。而我们构建RESTful API的目的通常都是由于多终端的原因,这些终端会共用很多底层业务逻辑,因此我们会抽象出这样一层来同时服务于多个移动端或者Web前端。

随着互联网技术的发展,现在的网站架构基本都由原来的后端渲染,变成了:前端渲染、先后端分离的形态,而且前端技术和后端技术在各自的道路上越走越远。
前端和后端的唯一联系,变成了API接口;API文档变成了前后端开发人员联系的纽带,变得越来越重要,swagger就是一款让你更好的书写API文档的框架。

这样一来,我们的RESTful API就有可能要面对多个开发人员或多个开发团队:IOS开发、Android开发或是Web开发等。为了减少与其他团队平时开发期间的频繁沟通成本,传统做法我们会创建一份RESTful API文档来记录所有接口细节,然而这样的做法有以下几个问题:

1.由于接口众多,并且细节复杂(需要考虑不同的HTTP请求类型、HTTP头部信息、HTTP请求内容等),高质量地创建这份文档本身就是件非常吃力的事,下游的抱怨声不绝于耳。

2.随着时间推移,不断修改接口实现的时候都必须同步修改接口文档,而文档与代码又处于两个不同的媒介,除非有严格的管理机制,不然很容易导致不一致现象。

如何在开发团队中实现一个RESTful API的接口统一文档?

Swagger2

使用Swagger之前我们先需要构建一个RESTful API来做准备。

RESTful API具体设计如下:

logo

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 
    //利用Collections工具类中的synchronizedMap方法来构建同步Map
    //SynchronizedMap类是定义在Collections中的一个静态内部类。
    //它实现了Map接口,并对其中的每一个方法实现,通过synchronized关键字进行了同步控制
    //这样的好处在于Map不会产生混乱,当有并发请求时这个Map会进行同步锁。
    static Map<Long, User> users = Collections.synchronizedMap(new HashMap<Long, User>()); 

    @RequestMapping(value="/", method=RequestMethod.GET) 
    public List<User> getUserList() { 
        // 处理"/users/"的GET请求,用来获取用户列表 
        // 还可以通过@RequestParam从页面中传递参数来进行查询条件或者翻页信息的传递
        //users.Values()获取用户信息的所有值并传递给一个List集合,然后通过返回集合呈现过我们 
        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"; 
    } 

}

下面针对该Controller编写测试用例验证正确性,具体如下。当然也可以通过浏览器插件等进行请求提交验证。

@RunWith(SpringJUnit4ClassRunner.class) 
//@SpringApplicationConfiguration(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; 

        // 1、get查一下user列表,应该为空 
        request = get("/users/"); 
        mvc.perform(request) 
                .andExpect(status().isOk()) 
                .andExpect(content().string(equalTo("[]"))); 

        // 2、post提交一个user 
        request = post("/users/") 
                .param("id", "1") 
                .param("name", "测试大师") 
                .param("age", "20"); 
        mvc.perform(request) 
                .andExpect(content().string(equalTo("success"))); 

        // 3、get获取user列表,应该有刚才插入的数据 
        request = get("/users/"); 
        mvc.perform(request) 
                .andExpect(status().isOk()) 
                .andExpect(content().string(equalTo("[{\"id\":1,\"name\":\"测试大师\",\"age\":20}]"))); 

        // 4、put修改id为1的user 
        request = put("/users/1") 
                .param("name", "测试终极大师") 
                .param("age", "30"); 
        mvc.perform(request) 
                .andExpect(content().string(equalTo("success"))); 

        // 5、get一个id为1的user 
        request = get("/users/1"); 
        mvc.perform(request) 
                .andExpect(content().string(equalTo("{\"id\":1,\"name\":\"测试终极大师\",\"age\":30}"))); 

        // 6、del删除id为1的user 
        request = delete("/users/1"); 
        mvc.perform(request) 
                .andExpect(content().string(equalTo("success"))); 

        // 7、get查一下user列表,应该为空 
        request = get("/users/"); 
        mvc.perform(request) 
                .andExpect(status().isOk()) 
                .andExpect(content().string(equalTo("[]"))); 

    } 

}

测试Console内容:

2018-03-25 23:25:23.827  INFO 7832 --- [           main] com.didispace.ApplicationTests           : Starting ApplicationTests on DX-20170223SFUP with PID 7832 (F:\SpringBoot-Learning\Chapter3-1-1\target\test-classes started by Administrator in F:\SpringBoot-Learning\Chapter3-1-1)
2018-03-25 23:25:23.829  INFO 7832 --- [           main] com.didispace.ApplicationTests           : No active profile set, falling back to default profiles: default
2018-03-25 23:25:24.217  INFO 7832 --- [           main] o.s.w.c.s.GenericWebApplicationContext   : Refreshing org.springframework.web.context.support.GenericWebApplicationContext@6d2a209c: startup date [Sun Mar 25 23:25:24 CST 2018]; root of context hierarchy
2018-03-25 23:25:25.640  INFO 7832 --- [           main] com.didispace.ApplicationTests           : Started ApplicationTests in 3.468 seconds (JVM running for 6.448)
2018-03-25 23:25:26.400  INFO 7832 --- [           main] ilder$StaticRequestMappingHandlerMapping : Mapped "{[/hello]}" onto public java.lang.String com.didispace.web.HelloController.index()
2018-03-25 23:25:26.431  INFO 7832 --- [           main] ilder$StaticRequestMappingHandlerMapping : Mapped "{[/users/],methods=[GET]}" onto public java.util.List<com.didispace.domain.User> com.didispace.web.UserController.getUserList()
2018-03-25 23:25:26.432  INFO 7832 --- [           main] ilder$StaticRequestMappingHandlerMapping : Mapped "{[/users/],methods=[POST]}" onto public java.lang.String com.didispace.web.UserController.postUser(com.didispace.domain.User)
2018-03-25 23:25:26.432  INFO 7832 --- [           main] ilder$StaticRequestMappingHandlerMapping : Mapped "{[/users/{id}],methods=[GET]}" onto public com.didispace.domain.User com.didispace.web.UserController.getUser(java.lang.Long)
2018-03-25 23:25:26.432  INFO 7832 --- [           main] ilder$StaticRequestMappingHandlerMapping : Mapped "{[/users/{id}],methods=[PUT]}" onto public java.lang.String com.didispace.web.UserController.putUser(java.lang.Long,com.didispace.domain.User)
2018-03-25 23:25:26.433  INFO 7832 --- [           main] ilder$StaticRequestMappingHandlerMapping : Mapped "{[/users/{id}],methods=[DELETE]}" onto public java.lang.String com.didispace.web.UserController.deleteUser(java.lang.Long)
2018-03-25 23:25:27.204  INFO 7832 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.test.web.servlet.setup.StubWebApplicationContext@44a7bfbc
2018-03-25 23:25:27.498  INFO 7832 --- [           main] o.s.mock.web.MockServletContext          : Initializing Spring FrameworkServlet ''
2018-03-25 23:25:27.499  INFO 7832 --- [           main] o.s.t.web.servlet.TestDispatcherServlet  : FrameworkServlet '': initialization started
2018-03-25 23:25:27.500  INFO 7832 --- [           main] o.s.t.web.servlet.TestDispatcherServlet  : FrameworkServlet '': initialization completed in 1 ms
2018-03-25 23:25:27.890  INFO 7832 --- [           main] ilder$StaticRequestMappingHandlerMapping : Mapped "{[/hello]}" onto public java.lang.String com.didispace.web.HelloController.index()
2018-03-25 23:25:27.896  INFO 7832 --- [           main] ilder$StaticRequestMappingHandlerMapping : Mapped "{[/users/],methods=[GET]}" onto public java.util.List<com.didispace.domain.User> com.didispace.web.UserController.getUserList()
2018-03-25 23:25:27.896  INFO 7832 --- [           main] ilder$StaticRequestMappingHandlerMapping : Mapped "{[/users/],methods=[POST]}" onto public java.lang.String com.didispace.web.UserController.postUser(com.didispace.domain.User)
2018-03-25 23:25:27.897  INFO 7832 --- [           main] ilder$StaticRequestMappingHandlerMapping : Mapped "{[/users/{id}],methods=[GET]}" onto public com.didispace.domain.User com.didispace.web.UserController.getUser(java.lang.Long)
2018-03-25 23:25:27.897  INFO 7832 --- [           main] ilder$StaticRequestMappingHandlerMapping : Mapped "{[/users/{id}],methods=[PUT]}" onto public java.lang.String com.didispace.web.UserController.putUser(java.lang.Long,com.didispace.domain.User)
2018-03-25 23:25:27.897  INFO 7832 --- [           main] ilder$StaticRequestMappingHandlerMapping : Mapped "{[/users/{id}],methods=[DELETE]}" onto public java.lang.String com.didispace.web.UserController.deleteUser(java.lang.Long)
2018-03-25 23:25:27.920  INFO 7832 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.test.web.servlet.setup.StubWebApplicationContext@61fe30
2018-03-25 23:25:27.927  INFO 7832 --- [           main] o.s.mock.web.MockServletContext          : Initializing Spring FrameworkServlet ''
2018-03-25 23:25:27.927  INFO 7832 --- [           main] o.s.t.web.servlet.TestDispatcherServlet  : FrameworkServlet '': initialization started
2018-03-25 23:25:27.927  INFO 7832 --- [           main] o.s.t.web.servlet.TestDispatcherServlet  : FrameworkServlet '': initialization completed in 0 ms
2018-03-25 23:25:27.952  INFO 7832 --- [       Thread-1] o.s.w.c.s.GenericWebApplicationContext   : Closing org.springframework.web.context.support.GenericWebApplicationContext@6d2a209c: startup date [Sun Mar 25 23:25:24 CST 2018]; root of context hierarchy

这个是用MockMVC来做数据分析,当然我们也可以直接用浏览器发送请求来实战测试,这就需要浏览器发送GET请求和POST请求
那么按照我们常规的方法是编写一个网站来发送POST请求(因为POST请求是不直接表达在URL上的)
所以这就需要借助浏览器的一些插件来实现发送POST请求

我们开发的工具是Google的Chrome浏览器,我们通过应用市场下载POSTMAN插件就可以实现浏览器发送POST请求
做WEB端开发的人员必须要下载这个插件作为调试页面访问请求的数据显示。

Postman插件概述

Postman插件是什么?postman插件是一款chrome插件,是谷歌浏览器的网页调试插件,这款插件可以利用Chrome插件的形式把各种模拟用户HTTP请求的数据发送到服务器,以便开发人员能够及时地作出正确的响应,或者是对产品发布之前的错误信息提前处理,进而保证产品上线之后的稳定性和安全性。Postman是一种网页调试与发送网页http请求的chrome插件。我们可以用来很方便的模拟get或者post或者其他方式的请求来调试接口。

  • Postman插件功能介绍

当开发人员需要调试一个网页是否运行正常,并不是简简单单地调试网页的HTML、CSS、脚本等信息是否运行正常,更加重要的是网页能够正确是处理各种HTTP请求,毕竟网页的HTTP请求是网站与用户之间进行交互的非常重要的一种方式,在动态网站中,用户的大部分数据都需要通过HTTP请求来与服务器进行交互。可以利用Chrome插件的形式把各种模拟用户HTTP请求的数据发送到服务器,以便开发人员能够及时地作出正确的响应,或者是对产品发布之前的错误信息提前处理,进而保证产品上线之后的稳定性和安全性。

POSTman教程及安装:http://www.cnplugins.com/devtool/postman/

它可以轻松的整合到Spring Boot中,并与Spring MVC程序配合组织出强大RESTful API文档。它既可以减少我们创建文档的工作量,同时说明内容又整合入实现代码中,让维护文档和修改代码整合为一体,可以让我们在修改代码逻辑的同时方便的修改文档说明。另外Swagger2也提供了强大的页面测试功能来调试每个RESTful API。具体效果如下图所示:

坚持原创技术分享,您的支持将鼓励我继续创作!
+