zoukankan      html  css  js  c++  java
  • (九) SpringBoot起飞之路-整合/集成Swagger 2 And 3

    兴趣的朋友可以去了解一下其他几篇,你的赞就是对我最大的支持,感谢大家!

    (一) SpringBoot起飞之路-HelloWorld

    (二) SpringBoot起飞之路-入门原理分析

    (三) SpringBoot起飞之路-YAML配置小结(入门必知必会)

    (四) SpringBoot起飞之路-静态资源处理

    (五) SpringBoot起飞之路-Thymeleaf模板引擎

    (六) SpringBoot起飞之路-整合JdbcTemplate-Druid-MyBatis

    (七) SpringBoot起飞之路-整合SpringSecurity

    (八) SpringBoot起飞之路-整合Shiro

    说明:

    • 3.0 的版本没怎么用过,只是进行了简单的整合,或许会有一些不完善的地方,欢迎大家交流分享
    • SpringBoot 起飞之路 系列文章的源码,均同步上传到 github 了,有需要的小伙伴,随意去 down

    一 初识 Swagger

    跳过铺垫,请直接翻越到 第二大点 ~

    (一) 先谈谈前后端分离

    在最早的 JavaWeb 时代的时候,如果想要返回一个页面,你需要一行一行的去 print,如果遇到变量,还需要自己进行字符串的拼接

    Public class TestServlet extends HttpServlet {
    	@Override  
        public void doGet(HttpServletRequest request, HttpServletResponseresp response) 
                throws ServletException, IOException {  
            String name = req.getParameter("name");  
            String age = req.getParameter("age");  
            PrintWriter out = resp.getWriter();  
            out.println("<html>");  
            out.println("<head><title>展示数据</title></head>");  
            out.println("<body>name:" + name + "<br/> age: " + age +"</body>");  
            out.println("</html>");       
        } 
    }
    

    接着 JSP 就出现了,其本质虽然还是一个 Servlet,不过编写一个 JSP 的方式和编写 HTML 的是基本一致的,但是 JSP 开始允许我们在页面中通过 %% 引入 Java 代码,也就是说我们可以通过在 JSP 页面中通过书写 Java 代码达到显示动态内容的效果,例如在 JSP 中定义方法、书写控制台输出语句等等,大部分你能想到的逻辑都可以在这里来做

    后来一看这不行啊,业务逻辑和视图逻辑都混一块了,越写越乱,一个页面就几百行甚至上千行,真可怕啊,后来大家就想了个法子,用 MVC 来解耦啊,把 JSP 的业务抽取出来交给后台的 Serlvet 去做,JSP 用来承载数据,展示页面

    如下代码:从 session 中取到数据,然后再 JSP 中进行遍历,不过这段代码有简单用了一些标签,这是我们后面要说的

    最后 JSP 会被编译成 Servlet

    <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    <%@page import="cn.ideal.pojo.Good" %>
    <!-- 
       请求 /index.do 进行初始化
    -->
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
    <html>  
       <body>
        <img src="img/cart.jpg" height="60px" width="60px">
        <a href="cart.view">已采购
        <c:out value="${sumNum}" default="没有任何"/>商品
        </a><br>
       <br>
    	<table border="1">    
       <%
          ArrayList<Good> goodList = (ArrayList<Good>)session.getAttribute("goodList");
          for (Good good : goodList){ 
            out.println("<tr>") ; 
            out.println("<td> <img src='" + good.getGoodImg() + "'> </td>");
            String id = good.getGoodId();
            out.println("<td> <a href=shopping.view?id=" + id + ">采购</a></td>");
            out.println("</tr>"); 
           } 
        %>
        </table></body>
    </html>
    

    虽然 JSP 不处理业务逻辑了,看起来也没那么臃肿了,但是如上所示, JSP 想获取 Servlet传来 的一些内容,仍然需要通过 %% 去获取,例如拿到后遍历,判断等内容还需要自己在JSP中写 Java 代码

    EL 和 JSTL 表达式就来了,通过一些定义好的标签,简单的拿到数据以及展示

    <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
    <!-- 
       请求 /index.do 进行初始化
    -->
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
    <html>  
       <body>
        <img src="img/cart.jpg" height="60px" width="60px">
        <a href="showCart.jsp">已采购
        
        <c:out value="${sessionScope.cart.sumNum}" default="没有任何"/>品
        </a><br>
       <br>
    	<table border="1">    
        <c:forEach var="good" items="${cart.goodList}">
        <tr>
        	<td><img src='${good.goodImg}'></td>
        	<td><c:out value='${good.getPrice()}'/> 元</td>
       		<td><a href="shopping.view?id=${good.bookId}&type=1&method=1">采购</a></td>
        </tr>
        </c:forEach>
        </table>
        </body>
    </html>
    

    再到后来,Freemarker、Thymeleaf、Velocity 等,模板引擎就来了,他们和 JSP 的表达式写法很相似,有自己的一套表达式来拿到并且处理数据,例如这样:

    <table border="1">
      <tr>
        <th>Name</th>
        <th>Age</th>
      </tr>
      <tr th:each="user : ${userList}">
        <td th:text="${user.nickname}">NULL</td>
        <td th:text="${user.age}">0</td>
      </tr>
    </table>
    

    这样一套老开发模式,实际上大部分压力都放在了后端人员上,因为例如前端发来一份 html,后端还需要修改为 JSP ,也就是用标签替换其中一些固定的内容,实现动态效果,但是项目比较大的情况下,不管从人力亦或是开发成本来看都是不合适的,而且术业有专攻,如果后端只需要管自己后台业务的事情就行了该多好

    这个时候前端就开始异军突起了,他们开始只使用简单 HTML、CSS 来展示数据,也就是纯粹的页面展示,通过 JS,把数据请求得到的数据进行填充,最开始可能会使用操作 DOM,但是随着前端框架的完善,例如现在常见的 Vue、Angular,前端都开始用 MVC 这套,也就是 html 作为视图,js作为控制器,异步请求,通过标签来展示数据,直接把数据填充到 html 页面中

    <!DOCTYPE html>
    <html lang="en" xmlns:v-on="http://www.w3.org/1999/xhtml">
    <head>
      <meta charset="UTF-8">
      <title>Title</title>
      <style>
        [v-clock] {
          display: none;
        }
      </style>
    </head>
    <body>
    <div id="hello-8" v-clock>
      <div>{{info.name}}</div>
      <div>{{info.blog}}</div>
      <div>{{info.about.country}}</div>
      <div>{{info.about.phone}}</div>
      <div>{{info.students[0].name}}</div>
    </div>
    
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    
    <script>
        var vm = new Vue({
            el: "#hello-8",
            data() {
                return {
                    info: {}
                }
            },
            mounted() {
                axios.get("../json/data.json").then(response => (this.info = response.data));
            }
        })
    </script>
    </body>
    </html>
    

    这就意味着,后端只需要给出根据前端接口需要的,返回对应数据(例如 JSON)就可以了

    这种通过 API 进行交互的方式实际上也是松耦合的,双方都比较灵活且独立

    (二) 为什么用 Swagger

    前面感觉一直在说题外话,但是问题的关键就在于这一点,这种模块开发,前后分离的情况下,前端或者后端的沟通就会极为重要,有时候没能做到及时的协商,就会出现很多意想不到的问题,同时前后端分离大行其道,也就代表着接口调用会大量的出现,如果你的 API 同时对接 Web、IOS、Android 等多个开发,为了减少沟通的代价,那必然就是要写文档的,用来记录所有接口的细节

    但是如何创建一个繁多且复杂的文档就是一个非常吃力的事情了,毕竟要考虑的地方非常多,同时随着代码的迭代,API 文档更新管理一定要严格把控,否则都会带来不必要的麻烦,显然传统的 Wiki

    手写文档一定是一个非常麻烦的过程,Swagger 他就来了

    (三) 什么是 Swagger

    Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务

    其总体目标是使客户端和文件系统作为服务器以同样的速度来更新。文件的方法,参数和模型紧密集成到服务器端的代码,允许API来始终保持同步。

    刚开始的时候,Swagger 只是一种规范,产品开发迭代的时候,通过更新描述文件就可以生成接口文档和客户/服务端代码,但是开发人员时不时就会忘记更新此文件,直接改代码,后序,作为大头的 Spring 就将其正式纳入麾下,建立了 Spring-swagger项目,即现在的 springfox(swagger2)

    其最大的优点就是,能实时同步 API 和文档,只需要通过简单的整合亦配合一些注解,就可以体验到其丰富的功能

    通常就目前为止,大部分项目中还是在用 Swagger2,通过maven仓库也可以看到,2.9.2 是使用率最高的,同样后面我们还会演示一下 Swagger3 的版本,因为它毕竟是今年刚出的,其简化了很多配置,个人感觉还算香

    说明

    二 Springboot 集成 Swagger 2

    (一) 依赖及初始化

    先初始化一个 Springboot 项目

    (1) 引入依赖

    <!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
    <dependency>
       <groupId>io.springfox</groupId>
       <artifactId>springfox-swagger2</artifactId>
       <version>2.9.2</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
    <dependency>
       <groupId>io.springfox</groupId>
       <artifactId>springfox-swagger-ui</artifactId>
       <version>2.9.2</version>
    </dependency>
    

    (2) 编写Controller

    @RestController
    @RequestMapping("test/")
    public class TestController {
        @RequestMapping("/testA")
        public String testA(){
            return "This is a test";
        }
    }
    

    (3) 创建 Swagger 配置类

    @Configuration // 配置类
    @EnableSwagger2 // 开启Swagger2的自动配置
    public class SwaggerConfig {  
        // 暂时为空
    }
    

    注:我们使用的是 Swagger2

    (4) 访问测试

    先测试一下 controller 有没有问题,接着通过如下地址就可以访问到 swagger 的界面

    http://localhost:8080/swagger-ui.html

    可以看到大概这个页面分为三个部分,上面就是一些介绍信息,中间就是一个一个的接口信息,下面是实体

    (二) 配置 Swagger 信息

    我们需要在我们自定义的 Swagger 配置类中配置一些内容,就需要引入一个 Bean,Swagger 的实例Bean 就是 Docket,所以我们需要实例化一个 Docket

    /**
     * 配置 docket 和 Swagger 的具体参数
     * @return
     */
    @Bean 
    public Docket docket() {
        return new Docket(DocumentationType.SWAGGER_2);
    }
    

    大家可以点开 Docket 源码,然后可以看到它引入了 ApiInfo 这个类,其中定义了一些基本的信息例如,版本,标题等等

    public static final Contact DEFAULT_CONTACT = new Contact("", "", "");
      public static final ApiInfo DEFAULT = new ApiInfo("Api Documentation", "Api Documentation", "1.0", "urn:tos",
              DEFAULT_CONTACT, "Apache 2.0", "http://www.apache.org/licenses/LICENSE-2.0", new ArrayList<VendorExtension>());
    
      private final String version;
      private final String title;
      private final String description;
      private final String termsOfServiceUrl;
      private final String license;
      private final String licenseUrl;
      private final Contact contact;
      private final List<VendorExtension> vendorExtensions;
    

    大家可能已经看出来了,这个地方的内容,就是负责刚开我们打开那个 swagger-ui 页面的头部文档信息的,其默认值例如 Api Documentation 、1.0 、Apache 2.0 大家可以自己对照一下

    好了,知道了它的类和基本结构,我们就要自定义这些信息了

    /**
     * 配置文档信息
     * @return
     */
    private ApiInfo apiInfo() {
        Contact contact = new Contact("BWH_Steven", "https://www.ideal-20.cn", "ideal_bwh@163.com");
        return new ApiInfo(
                "Swagger2学习——理想二旬不止", // title-标题
                "学习演示如何配置Swagger2", // description-描述
                "v1.0", // version-版本
                "https://www.ideal-20.cn", // termsOfServiceUrl-组织链接
                contact, // contact-联系人信息
                "Apach 2.0 许可", // license-许可
                "许可链接", // licenseUrl-许可连接
                new ArrayList<>()// vendorExtensions-扩展
        );
    }
    

    配置完后,我们还没有把它和 Docket 实例挂起钩来

    /**
     * 配置 docket 和 Swagger 的具体参数
     * @return
     */
    @Bean
    public Docket docket() {
       return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo());
    }
    

    再次访问地址,看看效果

    http://localhost:8080/swagger-ui.html

    (三) 配置自定义扫描方式配置

    (1) 如何配置

    留心的朋友大家看到,第一次访问的时候上面还有一个默认的 basic-seeor-controller,而后面则没有了,这是因为我在演示配置信息的时候,忘记把我自定义扫描方式给注释掉了

    所以,我们现在来提一下如何自己定义扫描哪些接口

    在 Docket 中可以通过调用 select() 方法来配置扫描的接口,要使用这个方式就必须在其后跟上 build,这是设计模式中的一种,建造者模式,具体如何配置呢,只需要在 select 和 build 中 apis 即可,因为 apis 中需要一个 requestHandlerSelector,所以 requestHandlerSelector 就是我们最终要具体配置的地方

    先上一份例子,对照上面的解释看

    /**
     * 配置 docket 和 Swagger 的具体参数
     * @return
     */
    @Bean
    public Docket docket() {
        return new Docket(DocumentationType.SWAGGER_2)
        	.apiInfo(apiInfo())
            // 通过.select()方法,去配置扫描接口
            .select()
            // RequestHandlerSelectors 配置扫描接口的具体方式
            .apis(RequestHandlerSelectors.basePackage("cn.ideal.controller")) 
            .build();
    }
    

    (2) 可选扫描方式

    大家可以点进去 RequestHandlerSelectors 看一下,除了 basePackage 还有哪些

    any()

    • 扫描项目中的所有接口

    none()

    • 不扫描任何接口

    withMethodAnnotation(final Class<? extends Annotation> annotation)

    • 通过方法上的注解扫描,如 withMethodAnnotation(GetMapping.class) 只扫描 get 请求

    withClassAnnotation(final Class<? extends Annotation> annotation)

    • 通过类上的注解扫描,如 .withClassAnnotation(Controller.class) 只扫描有 controller 注解的类中的接口

    basePackage(final String basePackage)

    • 根据包路径扫描接口

    (3) 配置扫描过滤

    当你写好 select 和 build 后,其实两者中间就只提供调用 apis 和 paths 了,而 paths 就是现在要说的扫描过滤

    /**
     * 配置 docket 和 Swagger 的具体参数
     * @return
     */
    @Bean
    public Docket docket() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("cn.ideal.controller"))
                // 配置如何通过 path 过滤
            	// 这里只扫描请求以/test 开头的接口
                .paths(PathSelectors.ant("/test/**"))
                .build();
    }
    

    可选方式

    any()

    • 扫描所有请求

    none()

    • 任何请求都不扫描

    regex(final String pathRegex)

    • 根据正则表达式

    ant(final String antPattern)

    • 通过 ant() 控制(如上代码,可以使用一些通配符)

    (四) 配置 Swagger 的开启和关闭

    通过在 Docket 中调用 enable(boolean externallyConfiguredFlag) 可以控制 Swagger 的开和关,也就是说,如果配置 .enable(false) 浏览器中访问就会出错

    讲个实际点的例子,一般我们会在开发以及测试阶段开启 Swagger ,但是正式上线就会把它关掉

    所以,我们得动态的让其进行配置

    首先,我们先分别创建 dev 和 test 以及 prod,三个环境

    • 创建 application-dev.properties,设置 server.port=8080

    • 创建 application-test.properties,设置 server.port=8081

    • 创建 application-prod.properties,设置 server.port=8082

    /**
     * 配置 docket 和 Swagger 的具体参数
     * @return
     */
    @Bean
    public Docket docket(Environment environment) {
    
        // 设置要显示swagger的环境
        Profiles of = Profiles.of("dev", "test");
        // 判断当前是否处于该环境
        boolean flag = environment.acceptsProfiles(of);
    
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                // 通过 enable() 接收决定是否要显示
                .enable(flag)
                .select()
                .apis(RequestHandlerSelectors.basePackage("cn.ideal.controller"))
                .paths(PathSelectors.ant("/test/**"))
                .build();
    }
    

    在主配置 appplication.properties 中指定当前为开发版本 spring.profiles.active=dev

    这样就可以访问对应设置好的 8080 了,同理设置为别的,就只能访问别的

    (五) 配置分组

    在 ui 界面其实大家可以注意到,右上角有一个 default,以及一个下拉选项(暂时还没内容)

    这里就是一个分组的概念,也就是我们要实现 API 接口的分类

    方法非常简单,只需要通过在 Docket 中,调用 groupName() 即可

    @Bean
    public Docket docketA(){
        return new Docket(DocumentationType.SWAGGER_2).groupName("groupA");
    }
    @Bean
    public Docket docketB(){
        return new Docket(DocumentationType.SWAGGER_2).groupName("groupB");
    }
    @Bean
    public Docket docketC(){
        return new Docket(DocumentationType.SWAGGER_2).groupName("groupC");
    }
    

    重启后,就可以看到分组的效果了,不同的组别里进行不同的配置,能达到不同的访问效果

    (六) 常用注解

    (一) 作用在类

    (1) @Api()

    写了一些常用的注解和其参数,不一定全,不过应该还是够用的

    Swagger 配合注解更香喔 ~

    @Api():表示这个类是 swagger 资源

    • tags:表示说明内容,只写 tags 就可以省略属性名
    • value:同样是说明,不过会被 tags 替代
    @Api(value = "测试接口value", tags = "测试接口Tags")
    @RestController
    @RequestMapping("test/")
    public class TestController {
    	......
    }
    

    上面同时定义了 tags 和 value,最后显示的只有 tags ,两者都可以单独使用

    (二) 作用在方法

    (1) @ApiOperation()

    @ApiOperation() :对方法的说明,注解位于方法上

    • value:方法的说明
    • notes:额外注释说明
    • response:返回的对象
    • tags:这个方法会被单独摘出来,重新分组
    @ApiOperation(
            value = "用户信息查询方法Value",
            notes = "这是用户信息查询方法的注释说明",
            response = User.class,
            tags = {"用户信息查询方法Tags"})
    @GetMapping("/queryUser")
    public User queryUser() {
    	......
    }
    

    说明:注释需要点开每一个方法才能看到

    补充:如果在方法上使用 @RequestMapping() 注解,那么文档中就会有各种类型的说明,例如 GET、POST 等等等等,所以一般我们会指定其请求类型,例如 @GetMapping("/queryUser")

    (2) @ApiParam()

    @ApiParam() :对方法参数的具体说明

    • name:参数名
    • value:参数说明
    • required:是否必填
    @ApiOperation(value = "用户登录方法", notes = "用户登录注释说明")
    @PostMapping("/login")
    public String login(@ApiParam(name = "username", value = "用户名", required = true) String username,
                        @ApiParam(name = "password", value = "密码", required = true) String password) {
        if ("123".equals(username) && "123".equals(password)) {
            return "登录成功";
        } else {
            return "登录失败";
        }
    }
    

    点开这个方法,可以看到,这些注解都体现在文档中了

    (三) 作用在模型类

    (1) @ApiModel()

    @ApiModel() 作用在模型类上,如 VO、BO ,表示对类进行说明

    • value:代表模型的名称
    • description:一段描述

    (2) @ApiModelProperty()

    @ApiModelProperty() 作用在类方法和属性上,表示对属性的说明

    • value:字段说明
    • name:重写属性名字
    • dataType:重写属性类型
    • required:是否必填
    • example:举例说明
    • hidden:隐藏

    (两者一起演示)可在文档首页,以及具体方法涉及到时,会显示此 model 说明信息

    三 Springboot 集成 Swagger3

    大部分内容上面都有提过,所以这里只说一些不同的地方

    一个就是依赖可以用 3.0 的starter

    其次就是配置中修改 return new Docket(DocumentationType.OAS_30)

    同时不需要引入 @EnableSwagger2 注解了

    都说启动类需要加 @EnableOpenApi , 试了一下,好像是不需要的

    ※ 启动路径变成了

    http://localhost:8080/swagger-ui/index.html

    (一) 引入依赖

    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-boot-starter</artifactId>
        <version>3.0.0</version>
    </dependency>
    

    (二) 配置类

    @Configuration
    public class Swagger3Config {
        @Bean
        public Docket createRestApi(Environment environment) {
            // 设置要显示swagger的环境
            Profiles of = Profiles.of("dev", "test");
            // 判断当前是否处于该环境
            boolean flag = environment.acceptsProfiles(of);
    
            return new Docket(DocumentationType.OAS_30)
                    .apiInfo(apiInfo())
                    // 通过 enable() 接收决定是否要显示
                    .enable(flag)
                    // 通过.select()方法,去配置扫描接口
                    .select()
                    // RequestHandlerSelectors 配置扫描接口的具体方式
                    .apis(RequestHandlerSelectors.basePackage("cn.ideal.controller"))
                    .paths(PathSelectors.ant("/test/**"))
                    .build();
        }
    
        private ApiInfo apiInfo() {
            Contact contact = new Contact("BWH_Steven", "https://www.ideal-20.cn", "ideal_bwh@163.com");
            return new ApiInfo(
                    "Swagger3学习——理想二旬不止", // 标题
                    "学习演示如何配置Swagger2", // 描述
                    "v1.0", // 版本
                    "https://www.ideal-20.cn", // 组织链接
                    contact, // 联系人信息
                    "Apache 2.0 许可", // 许可
                    "许可链接", // 许可连接
                    new ArrayList<>()// 扩展
            );
        }
    }
    

    看看效果

    四 Swagger 测试

    Swagger 不仅仅是一个普通的文档,它还可以直接在生成的文档上进行测试,伴随着文档的说明,测试起来也非常方便

    例如点击 Try it out 后,因为测试一个查询方法,是没有参数的,所以直接点击 Excute,就能看到响应结果了

    响应结果

    五 结尾

    如果文章中有什么不足,欢迎大家留言交流,感谢朋友们的支持!

    如果能帮到你的话,那就来关注我吧!如果您更喜欢微信文章的阅读方式,可以关注我的公众号

    在这里的我们素不相识,却都在为了自己的梦而努力 ❤

    一个坚持推送原创开发技术文章的公众号:理想二旬不止

  • 相关阅读:
    hdu 2200 Eddy's AC难题(简单数学。。)
    hdu 2201 熊猫阿波的故事(简单概率。。)
    hdu 2571 命运(水DP)
    hdu 2955 Robberies(背包DP)
    GDI+图形图像技术1
    C#笔记2__Char类、String类、StringBuilder类 / 正则表达式 /
    C#笔记1__命名空间 / 常量 / object / is、as、...?... :...
    VS2013快捷键及技巧 / 智能插件
    JAVA笔记15__TCP服务端、客户端程序 / ECHO程序 /
    JAVA笔记14__多线程共享数据(同步)/ 线程死锁 / 生产者与消费者应用案例 / 线程池
  • 原文地址:https://www.cnblogs.com/ideal-20/p/13796314.html
Copyright © 2011-2022 走看看