多應用下 Swagger 的使用,這可能是最好的方式!
問題
微服務化的時代,我們整個專案工程下面都會有很多的子系統,對於每個應用都有暴露 Api 介面文件需要,這個時候我們就會想到 Swagger 這個優秀 jar 包。但是我們會遇到這樣的問題,假如說我們有5個應用,難道說我們每個模組下面都要去引入這個 jar 包嗎?我作為一個比較懶的程式感覺這樣好麻煩,於是乎我思考了一種我認為比較好的方式,如果大家覺得有什麼不太好的地方希望指正,謝謝!
基礎
開始之前大家首先要了解一些基礎,主要有以下幾個方面:
- 單應用下 Swagger 的整合與使用
- 條件裝配 @Conditional 介紹
- 配置檔案引數獲取 @ConfigurationProperties
單體應用下 Swagger 整合與使用
關於這部分從3方面講起分別是:什麼是、為什麼、如何用
什麼是 Swagger ?
Swagger 是一個規範且完整的框架,用於生成、描述、呼叫和視覺化 RESTful 風格的 Web 服務。
為什麼使用 Swagger ?
主要的優點:
- 支援 API 自動生成同步的線上文件:使用 Swagger 後可以直接通過程式碼生成文件,不再需要自己手動編寫介面文件了,對程式設計師來說非常方便,可以節約寫文件的時間去學習新技術。
- 提供 Web 頁面線上測試 API:光有文件還不夠,Swagger 生成的文件還支援線上測試。引數和格式都定好了,直接在介面上輸入引數對應的值即可線上測試介面。
缺點的話就是但凡引入一個 jar 需要去了解下原理和使用,對於這個缺點我感覺相比於優點就是大巫見小巫,我簡單看了一下原始碼,其實不算太難。
如何使用 Swagger
關於 Swagger 的使用其實也就是3板斧,大家一定很熟悉的;
第一板斧就是引入 jar 包,這裡我使用的是2.9.2版本
<!-- swagger 相關 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${swagger2.version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${swagger2.version}</version>
</dependency>
第二板斧就是SpringBoot自動掃描配置類
/**
* SwaggerConfig
*
* @author wangtongzhou
* @since 2020-06-09 09:41
*/
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
//生產環境的時候關閉 Swagger 比較安全
.apiInfo(apiInfo())
.select()
//Api掃描目錄
.apis(RequestHandlerSelectors.basePackage("com.springboot2.learning"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("learn")
.description("learn")
.version("1.0")
.build();
}
}
第三板斧使用 Swagger 註解
/**
* 使用者相關介面
*
* @author wangtongzhou
* @since 2020-06-12 07:35
*/
@RestController
@RequestMapping("/user")
@Api(value = "使用者相關介面")
public class UserController {
@PostMapping("/")
@ApiOperation("新增使用者的介面")
@ApiImplicitParams({
@ApiImplicitParam(name = "userName", value = "使用者名稱", defaultValue =
"wtz"),
@ApiImplicitParam(name = "age", value = "年齡", defaultValue = "20")
})
public User addUser(String userName, Integer age) {
User user = new User();
user.setAge(age);
user.setUserName(userName);
return user;
}
@GetMapping("/{userId}")
@ApiOperation("根據使用者id查詢使用者資訊")
@ApiImplicitParam(name = "userId", value = "使用者id", defaultValue = "20")
public User queryUserByUserId(@PathVariable Long userId) {
User user = new User();
user.setUserId(userId);
return user;
}
}
/**
* 使用者實體
*
* @author wangtongzhou
* @since 2020-06-12 07:45
*/
@ApiModel
public class User {
@ApiModelProperty(value = "使用者名稱稱")
private String userName;
@ApiModelProperty(value = "年齡")
private Integer age;
@ApiModelProperty(value = "使用者id")
private Long userId;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
}
效果如下:
實體註解介面描述
介面引數
執行介面
返回地址
條件裝配 @Conditional 介紹
@Conditional 是Spring4.0提供的註解,位於 org.springframework.context.annotation 包內,它可以根據程式碼中設定的條件裝載不同的bean。比如說當一個介面有兩個實現類時,我們要把這個介面交給Spring管理時通常會只選擇實現其中一個實現類,這個時候我們總不能使用if-else吧,所以這個@Conditional的註解就出現了。
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Conditional {
Class<? extends Condition>[] value();
}
使用方法
這裡介紹一個MySQL和Oracle選擇方式,開始之前首先在properties檔案中增加sql.name=mysql的配置,接下來步驟如下
- 實現Conditional介面, 實現matches方法
/**
* mysql條件裝配
*
* @author wangtongzhou
* @since 2020-06-13 08:01
*/
public class MysqlConditional implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String sqlName = context.getEnvironment().getProperty("sql.name");
if ("mysql".equals(sqlName)){
return true;
}
return false;
}
}
/**
* oracle條件裝配
*
* @author wangtongzhou
* @since 2020-06-13 08:02
*/
public class OracleConditional implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String sqlName=context.getEnvironment().getProperty("sql.name");
if ("oracle".equals(sqlName)){
return true;
}
return false;
}
}
- 在需要判斷條件的bean上,加上@Conditional(***.class)即可在滿足條件的時候載入對應的類
/**
* conditional
*
* @author wangtongzhou
* @since 2020-06-13 08:01
*/
@Configuration
public class ConditionalConfig {
@Bean
@Conditional(MysqlConditional.class)
public Mysql mysql() {
return new Mysql();
}
@Bean
@Conditional(OracleConditional.class)
public Oracle oracle() {
return new Oracle();
}
}
- 呼叫測試
@RunWith(SpringRunner.class)
@SpringBootTest
public class ConditionalTests {
@Autowired
private ApplicationContext applicationContext;
@Test
public void test_conditional() {
Mysql mysql = (Mysql) applicationContext.getBean("mysql");
Assert.assertNotNull(mysql);
Assert.assertTrue("mysql".equals(mysql.getSqlName()));
}
}
其他擴充套件註解
- @@ConditionalOnClass({Docket.class, ApiInfoBuilder.class})
當存在Docket和ApiInfoBuilder類的時候才載入Bean; - @ConditionalOnMissingClass不存在某個類的時候才會例項化Bean;
- @ConditionalOnProperty(prefix = "swagger", value = "enable", matchIfMissing = true)當存在swagger為字首的屬性,才會例項化Bean;
- @ConditionalOnMissingBean當不存在某個Bean的時候才會例項化;
這裡就介紹這幾個常用,org.springframework.boot.autoconfigure.condition這個下面包含全部的關於@Conditional相關的所有註解
@Conditional擴充套件註解介紹
配置檔案引數獲取 @ConfigurationProperties
@ConfigurationProperties是SpringBoot加入的註解,主要用於配置檔案中的指定鍵值對對映到一個Java實體類上。關於這個的使用就在下面的方式引出。
比較好的方式
關於開篇中引入的問題,解題流程主要是以下3步:
- 抽象一個公共 Swagger jar;
- 如何定製化 Swagger jar;
- 使用定製化完成以後的 Swagger jar;
抽象一個公共 Swagger jar
主要是就是將 Swagger jar 和一些其他需要的 jar 進行引入,使其成為一個公共的模組,軟體工程中把這個叫做單一原則;
Maven包的引入
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
</dependencies>
如何定製化 Swagger jar;
定製化就是將 Swagger 相關的屬性進行配置化的處理,這裡也可以分為兩步;
- 將公共的屬性抽象成配置化的類,這裡就是關於@ConfigurationProperties的使用,將配置檔案中的 swagger 開頭的屬性對映到配置類的屬性當中;
/**
* Swagger基本屬性
*
* @author wangtongzhou
* @since 2020-05-24 16:58
*/
@ConfigurationProperties("swagger")
public class SwaggerProperties {
/**
* 子系統
*/
private String title;
/**
* 描述
*/
private String description;
/**
* 版本號
*/
private String version;
/**
* api包路徑
*/
private String basePackage;
public String getTitle() {
return title;
}
public SwaggerProperties setTitle(String title) {
this.title = title;
return this;
}
public String getDescription() {
return description;
}
public SwaggerProperties setDescription(String description) {
this.description = description;
return this;
}
public String getVersion() {
return version;
}
public SwaggerProperties setVersion(String version) {
this.version = version;
return this;
}
public String getBasePackage() {
return basePackage;
}
public SwaggerProperties setBasePackage(String basePackage) {
this.basePackage = basePackage;
return this;
}
}
- 公共屬性賦值配置到 Swagger 的配置類中,該配置類中進行一些類條件的判斷和外掛Bean是否已經注入過,然後就是將配置類中的屬性,賦值到 Swagger 的初始化工程中;
/**
* Swagger自動配置類
*
* @author wangtongzhou
* @since 2020-05-24 16:35
*/
@Configuration
@EnableSwagger2
@ConditionalOnClass({Docket.class, ApiInfoBuilder.class})
@ConditionalOnProperty(prefix = "swagger", value = "enable", matchIfMissing = true)
@EnableConfigurationProperties(SwaggerProperties.class)
public class SwaggerConfig {
@Bean
@ConditionalOnMissingBean
public SwaggerProperties swaggerProperties() {
return new SwaggerProperties();
}
@Bean
public Docket createRestApi() {
SwaggerProperties properties = swaggerProperties();
return new Docket(DocumentationType.SWAGGER_2)
//生產環境的時候關閉 Swagger 比較安全
.apiInfo(apiInfo(properties))
.select()
//Api掃描目錄
.apis(RequestHandlerSelectors.basePackage(properties.getBasePackage()))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo(SwaggerProperties properties) {
return new ApiInfoBuilder()
.title(properties.getTitle())
.description(properties.getDescription())
.version(properties.getVersion())
.build();
}
}
完成以上兩步,就完成了 Swagger 模組的定製化開發,接下來還要做一件事情,作為一個公共的模組,我們要讓他自己進行自動化裝配,解放我們的雙手,我們在 resources 目錄下增加一個 spring.factories 配置檔案,內容如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.springcloud.study.swagger.config.SwaggerConfig
到此我們完成所有的開發;
使用定製化完成以後的 Swagger jar;
關於使用也分為兩步,
- 引入 jar;
<dependency>
<groupId>com.springcloud.study</groupId>
<artifactId>common-swagger</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
- 定製化的屬性配置;
# Swagger 配置項
swagger:
title: 使用者模組
description: 使用者子系統
version: 1.0.0
base-package: com.springcloud.study.user.controller
完成這兩步就可以開啟正常的使用了;
後續的規劃
- 註解整理
後續會將 Spring 註解進行一個統一的整理,包含一些使用說明或者原理等等,希望到時候能幫助到大家吧,目前計劃兩週一個吧; - 開源專案
Spring Cloud 的學習過於碎片化,希望通過自己搞一個開源專案,提升對各個元件的掌握能力,同時也能產出一套通用化許可權管理系統,具備很高的靈活性、擴充套件性和高可用性,並且簡單易用,這塊是和未來做企業數字化轉型相關的事是重合的,慢慢的會做一些企業級通用化的的功能開發;前端部分的話希望是採用Vue,但是這塊有一個學習成本,還沒有進行研究,目前還沒排上日程。整體的里程碑是希望在6.22離職之前完成整套後端的開發,7月中旬完成第一次Commit。
點點關注
這邊文章限於篇幅,過多的關注於使用了,後續會把上面幾個註解的原理分析講講,歡迎大家點點關注,點點贊,感謝!