(九) SpringBoot起飛之路-整合/整合Swagger 2 And 3

BWH_Steven發表於2020-10-11

興趣的朋友可以去了解一下其他幾篇,你的贊就是對我最大的支援,感謝大家!

(一) 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,就能看到響應結果了

響應結果

五 結尾

如果文章中有什麼不足,歡迎大家留言交流,感謝朋友們的支援!

如果能幫到你的話,那就來關注我吧!如果您更喜歡微信文章的閱讀方式,可以關注我的公眾號

在這裡的我們素不相識,卻都在為了自己的夢而努力 ❤

一個堅持推送原創開發技術文章的公眾號:理想二旬不止

相關文章