興趣的朋友可以去了解一下其他幾篇,你的贊就是對我最大的支援,感謝大家!
(一) 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,就能看到響應結果了
響應結果
五 結尾
如果文章中有什麼不足,歡迎大家留言交流,感謝朋友們的支援!
如果能幫到你的話,那就來關注我吧!如果您更喜歡微信文章的閱讀方式,可以關注我的公眾號
在這裡的我們素不相識,卻都在為了自己的夢而努力 ❤
一個堅持推送原創開發技術文章的公眾號:理想二旬不止