之前在SpringBoot項(xiàng)目中一直使用的是SpringFox提供的Swagger庫(kù),上了下官網(wǎng)發(fā)現(xiàn)已經(jīng)有接近兩年沒(méi)出新版本了!前幾天升級(jí)了SpringBoot 2.6.x 版本,發(fā)現(xiàn)這個(gè)庫(kù)的兼容性也越來(lái)越不好了,有的常用注解屬性被廢棄了居然都沒(méi)提供替代!無(wú)意中發(fā)現(xiàn)了另一款Swagger庫(kù)SpringDoc,試用了一下非常不錯(cuò),推薦給大家!
SpringDoc簡(jiǎn)介
SpringDoc是一款可以結(jié)合SpringBoot使用的API文檔生成工具,基于OpenAPI 3,目前在Github上已有1.7K+Star,更新發(fā)版還是挺勤快的,是一款更好用的Swagger庫(kù)!值得一提的是SpringDoc不僅支持Spring WebMvc項(xiàng)目,還可以支持Spring WebFlux項(xiàng)目,甚至Spring Rest和Spring Native項(xiàng)目,總之非常強(qiáng)大,下面是一張SpringDoc的架構(gòu)圖。

使用
接下來(lái)我們介紹下SpringDoc的使用,使用的是之前集成SpringFox的mall-tiny-swagger項(xiàng)目,我將把它改造成使用SpringDoc。
集成
首先我們得集成SpringDoc,在pom.xml中添加它的依賴即可,開(kāi)箱即用,無(wú)需任何配置。
<!--springdoc 官方Starter-->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.6.6</version>
</dependency>
從SpringFox遷移
- 我們先來(lái)看下經(jīng)常使用的Swagger注解,看看SpringFox的和SpringDoc的有啥區(qū)別,畢竟對(duì)比已學(xué)過(guò)的技術(shù)能該快掌握新技術(shù);
| SpringFox | SpringDoc |
|---|
| @Api | @Tag |
| @ApiIgnore | @Parameter(hidden = true)or@Operation(hidden = true)or@Hidden |
| @ApiImplicitParam | @Parameter |
| @ApiImplicitParams | @Parameters |
| @ApiModel | @Schema |
| @ApiModelProperty | @Schema |
| @ApiOperation(value = "foo", notes = "bar") | @Operation(summary = "foo", description = "bar") |
| @ApiParam | @Parameter |
| @ApiResponse(code = 404, message = "foo") | ApiResponse(responseCode = "404", description = "foo") |
- 接下來(lái)我們對(duì)之前Controller中使用的注解進(jìn)行改造,對(duì)照上表即可,之前在
@Api注解中被廢棄了好久又沒(méi)有替代的description屬性終于被支持了!
/**
* 品牌管理Controller
* Created by macro on 2019/4/19.
*/
@Tag(name = "PmsBrandController", description = "商品品牌管理")
@Controller
@RequestMapping("/brand")
public class PmsBrandController {
@Autowired
private PmsBrandService brandService;
private static final Logger LOGGER = LoggerFactory.getLogger(PmsBrandController.class);
@Operation(summary = "獲取所有品牌列表",description = "需要登錄后訪問(wèn)")
@RequestMapping(value = "listAll", method = RequestMethod.GET)
@ResponseBody
public CommonResult<List<PmsBrand>> getBrandList() {
return CommonResult.success(brandService.listAllBrand());
}
@Operation(summary = "添加品牌")
@RequestMapping(value = "/create", method = RequestMethod.POST)
@ResponseBody
@PreAuthorize("hasRole('ADMIN')")
public CommonResult createBrand(@RequestBody PmsBrand pmsBrand) {
CommonResult commonResult;
int count = brandService.createBrand(pmsBrand);
if (count == 1) {
commonResult = CommonResult.success(pmsBrand);
LOGGER.debug("createBrand success:{}", pmsBrand);
} else {
commonResult = CommonResult.failed("操作失敗");
LOGGER.debug("createBrand failed:{}", pmsBrand);
}
return commonResult;
}
@Operation(summary = "更新指定id品牌信息")
@RequestMapping(value = "/update/{id}", method = RequestMethod.POST)
@ResponseBody
@PreAuthorize("hasRole('ADMIN')")
public CommonResult updateBrand(@PathVariable("id") Long id, @RequestBody PmsBrand pmsBrandDto, BindingResult result) {
CommonResult commonResult;
int count = brandService.updateBrand(id, pmsBrandDto);
if (count == 1) {
commonResult = CommonResult.success(pmsBrandDto);
LOGGER.debug("updateBrand success:{}", pmsBrandDto);
} else {
commonResult = CommonResult.failed("操作失敗");
LOGGER.debug("updateBrand failed:{}", pmsBrandDto);
}
return commonResult;
}
@Operation(summary = "刪除指定id的品牌")
@RequestMapping(value = "/delete/{id}", method = RequestMethod.GET)
@ResponseBody
@PreAuthorize("hasRole('ADMIN')")
public CommonResult deleteBrand(@PathVariable("id") Long id) {
int count = brandService.deleteBrand(id);
if (count == 1) {
LOGGER.debug("deleteBrand success :id={}", id);
return CommonResult.success(null);
} else {
LOGGER.debug("deleteBrand failed :id={}", id);
return CommonResult.failed("操作失敗");
}
}
@Operation(summary = "分頁(yè)查詢品牌列表")
@RequestMapping(value = "/list", method = RequestMethod.GET)
@ResponseBody
@PreAuthorize("hasRole('ADMIN')")
public CommonResult<CommonPage<PmsBrand>> listBrand(@RequestParam(value = "pageNum", defaultValue = "1")
@Parameter(description = "頁(yè)碼") Integer pageNum,
@RequestParam(value = "pageSize", defaultValue = "3")
@Parameter(description = "每頁(yè)數(shù)量") Integer pageSize) {
List<PmsBrand> brandList = brandService.listBrand(pageNum, pageSize);
return CommonResult.success(CommonPage.restPage(brandList));
}
@Operation(summary = "獲取指定id的品牌詳情")
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
@ResponseBody
@PreAuthorize("hasRole('ADMIN')")
public CommonResult<PmsBrand> brand(@PathVariable("id") Long id) {
return CommonResult.success(brandService.getBrand(id));
}
}
- 接下來(lái)進(jìn)行SpringDoc的配置,使用
OpenAPI來(lái)配置基礎(chǔ)的文檔信息,通過(guò)GroupedOpenApi配置分組的API文檔,SpringDoc支持直接使用接口路徑進(jìn)行配置。
/**
* SpringDoc API文檔相關(guān)配置
* Created by macro on 2022/3/4.
*/
@Configuration
public class SpringDocConfig {
@Bean
public OpenAPI mallTinyOpenAPI() {
return new OpenAPI()
.info(new Info().title("Mall-Tiny API")
.description("SpringDoc API 演示")
.version("v1.0.0")
.license(new License().name("Apache 2.0").url("https://github.com/macrozheng/mall-learning")))
.externalDocs(new ExternalDocumentation()
.description("SpringBoot實(shí)戰(zhàn)電商項(xiàng)目mall(50K+Star)全套文檔")
.url("http://www."));
}
@Bean
public GroupedOpenApi publicApi() {
return GroupedOpenApi.builder()
.group("brand")
.pathsToMatch("/brand/**")
.build();
}
@Bean
public GroupedOpenApi adminApi() {
return GroupedOpenApi.builder()
.group("admin")
.pathsToMatch("/admin/**")
.build();
}
}
結(jié)合SpringSecurity使用
- 由于我們的項(xiàng)目集成了SpringSecurity,需要通過(guò)JWT認(rèn)證頭進(jìn)行訪問(wèn),我們還需配置好SpringDoc的白名單路徑,主要是Swagger的資源路徑;
/**
* SpringSecurity的配置
* Created by macro on 2018/4/26.
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf()// 由于使用的是JWT,我們這里不需要csrf
.disable()
.sessionManagement()// 基于token,所以不需要session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers(HttpMethod.GET, // Swagger的資源路徑需要允許訪問(wèn)
"/",
"/swagger-ui.html",
"/swagger-ui/",
"/*.html",
"/favicon.ico",
"/**/*.html",
"/**/*.css",
"/**/*.js",
"/swagger-resources/**",
"/v3/api-docs/**"
)
.permitAll()
.antMatchers("/admin/login")// 對(duì)登錄注冊(cè)要允許匿名訪問(wèn)
.permitAll()
.antMatchers(HttpMethod.OPTIONS)//跨域請(qǐng)求會(huì)先進(jìn)行一次options請(qǐng)求
.permitAll()
.anyRequest()// 除上面外的所有請(qǐng)求全部需要鑒權(quán)認(rèn)證
.authenticated();
}
}
- 然后在
OpenAPI對(duì)象中通過(guò)addSecurityItem方法和SecurityScheme對(duì)象,啟用基于JWT的認(rèn)證功能。
/**
* SpringDoc API文檔相關(guān)配置
* Created by macro on 2022/3/4.
*/
@Configuration
public class SpringDocConfig {
private static final String SECURITY_SCHEME_NAME = "BearerAuth";
@Bean
public OpenAPI mallTinyOpenAPI() {
return new OpenAPI()
.info(new Info().title("Mall-Tiny API")
.description("SpringDoc API 演示")
.version("v1.0.0")
.license(new License().name("Apache 2.0").url("https://github.com/macrozheng/mall-learning")))
.externalDocs(new ExternalDocumentation()
.description("SpringBoot實(shí)戰(zhàn)電商項(xiàng)目mall(50K+Star)全套文檔")
.url("http://www."))
.addSecurityItem(new SecurityRequirement().addList(SECURITY_SCHEME_NAME))
.components(new Components()
.addSecuritySchemes(SECURITY_SCHEME_NAME,
new SecurityScheme()
.name(SECURITY_SCHEME_NAME)
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")));
}
}
測(cè)試
- 接下來(lái)啟動(dòng)項(xiàng)目就可以訪問(wèn)Swagger界面了,訪問(wèn)地址:http://localhost:8088/swagger-ui.html

- 我們先通過(guò)登錄接口進(jìn)行登錄,可以發(fā)現(xiàn)這個(gè)版本的Swagger返回結(jié)果是支持高亮顯示的,版本明顯比SpringFox來(lái)的新;

- 然后通過(guò)認(rèn)證按鈕輸入獲取到的認(rèn)證頭信息,注意這里不用加
bearer前綴;

- 之后我們就可以愉快地訪問(wèn)需要登錄認(rèn)證的接口了;

- 看一眼請(qǐng)求參數(shù)的文檔說(shuō)明,還是熟悉的Swagger樣式!

常用配置
SpringDoc還有一些常用的配置可以了解下,更多配置可以參考官方文檔。
springdoc:
swagger-ui:
# 修改Swagger UI路徑
path: /swagger-ui.html
# 開(kāi)啟Swagger UI界面
enabled: true
api-docs:
# 修改api-docs路徑
path: /v3/api-docs
# 開(kāi)啟api-docs
enabled: true
# 配置需要生成接口文檔的掃描包
packages-to-scan: com.macro.mall.tiny.controller
# 配置需要生成接口文檔的接口路徑
paths-to-match: /brand/**,/admin/**
總結(jié)
在SpringFox的Swagger庫(kù)好久不出新版的情況下,遷移到SpringDoc確實(shí)是一個(gè)更好的選擇。今天體驗(yàn)了一把SpringDoc,確實(shí)很好用,和之前熟悉的用法差不多,學(xué)習(xí)成本極低。而且SpringDoc能支持WebFlux之類的項(xiàng)目,功能也更加強(qiáng)大,使用SpringFox有點(diǎn)卡手的朋友可以遷移到它試試!
參考資料
- 項(xiàng)目地址:https://github.com/springdoc/springdoc-openapi
項(xiàng)目源碼地址
https://github.com/macrozheng/mall-learning/tree/master/mall-tiny-springdoc