大家好,我是小富~
前言
说个挺奇葩的事,有个老铁给我发私信吐槽了一下它的面试经验,他去了个国企单位面试,而后面试官跟他就Swagger的问题聊了半个多小时。额~ 面试嘛这些都不稀奇,总能遇到是千奇百怪的人,千奇百怪的问题。不过,我剖析这个面试官是不太好意思间接让他走,哈哈哈!
什么是Swagger?
Swagger目前是比拟支流的RESTful
格调的API文档工具,做过开发的人应该都用过它吧!
它提供了一套工具和标准,让开发人员可能更轻松地创立和保护可读性强、易于应用和交互的API文档(官网口气)。
title: Swaggerdesc: Swagger 官方网站logo: https://static1.smartbear.co/swagger/media/assets/images/swagger_logo.svglink: https://swagger.io/
为什么用Swagger?
以往在没有这样的API文档工具,开发人员须要手动编写和保护性能API的文档。而且,因为API变更往往难以及时更新到文档中,这可能会给依赖文档的开发者带来困惑。
说几个Swagger
的特点:
- 最重要的一点能够依据代码注解主动生成API文档,
能生成的相对不手写
,而且API文档与API定义会同步更新。 - 它提供了一个可执行的Web界面,反对API在线测试,能够间接在界面上间接设置参数测试,不必额定的测试工具或插件。
- 反对多种编程语言,
Java
、PHP
、Python
等语言都反对,喜爱什么语言构建API都行。
总的来说,Swagger能够让咱们更多工夫在专一于编写代码(摸鱼),而不是破费额定精力来保护文档,实际出真知先跑个demo试试。
Swagger搭建
maven 依赖
目前应用的版本是Swagger3.0、Springboot 2.7.6,Swagger2.0与3.0依赖包名称的变动有些大,须要特地留神一下。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.7.6</version></dependency><dependency> <groupId>io.springfox</groupId> <artifactId>springfox-boot-starter</artifactId> <version>3.0.0</version></dependency>
配置类
首先咱们创立一个控制器TestController
类,里边只有一个最简略的申请 /test
。
@RestControllerpublic class TestController { @RequestMapping("/test") public String test(String name) { return name; }}
接下来创立配置类SwaggerConfig
,类标注@EnableSwagger2
注解是要害,到这最简略的Swagger文档环境就搭建好了。
import org.springframework.context.annotation.Configuration;import springfox.documentation.swagger2.annotations.EnableSwagger2;@Configuration@EnableSwagger2public class SwaggerConfig {}
启动报错
启动时可能会报如下的谬误,这是因为高版本的Springboot
与Swagger
版本应用的门路匹配策略抵触导致的。
Springfox
应用的门路匹配规定为AntPathMatcher
的,而SpringBoot2.7.6
应用的是PathPatternMatcher
,两者抵触了。
org.springframework.context.ApplicationContextException: Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:181) ~[spring-context-5.3.24.jar:5.3.24] at org.springframework.context.support.DefaultLifecycleProcessor.access$200(DefaultLifecycleProcessor.java:54) ~[spring-context-5.3.24.jar:5.3.24] at org.springframework.context.support.DefaultLifecycleProcessor$LifecycleGroup.start(DefaultLifecycleProcessor.java:356) ~[spring-context-5.3.24.jar:5.3.24] at java.lang.Iterable.forEach(Iterable.java:75) ~[na:1.8.0_341] at org.springframework.context.support.DefaultLifecycleProcessor.startBeans(DefaultLifecycleProcessor.java:155) ~[spring-context-5.3.24.jar:5.3.24] at org.springframework.context.support.DefaultLifecycleProcessor.onRefresh(DefaultLifecycleProcessor.java:123) ~[spring-context-5.3.24.jar:5.3.24] at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:935) ~[spring-context-5.3.24.jar:5.3.24] at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:586) ~[spring-context-5.3.24.jar:5.3.24] at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:147) ~[spring-boot-2.7.6.jar:2.7.6] at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:731) [spring-boot-2.7.6.jar:2.7.6] at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:408) [spring-boot-2.7.6.jar:2.7.6] at org.springframework.boot.SpringApplication.run(SpringApplication.java:307) [spring-boot-2.7.6.jar:2.7.6] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1303) [spring-boot-2.7.6.jar:2.7.6] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1292) [spring-boot-2.7.6.jar:2.7.6] at com.springboot101.SwaggerApplication.main(SwaggerApplication.java:10) [classes/:na]
解决方案
这个谬误的解决办法比拟多,我整顿了四种解决此问题的计划,你看哪个更适合你。
1、升高版本
SpringBoot版本升高到2.5.X 、springfox降到3.X 以下能够解决问题,不过不举荐这么做,毕竟降配置做兼容显得有点傻。
2、对立门路匹配策略
将SpringMVC
的匹配URL门路的策略改为ant_path_matcher
,application.yml
文件减少如下的配置:
spring: mvc: pathmatch: matching-strategy: ant_path_matcher
3、@EnableWebMvc注解
在配置类SwaggerConfig
上标注@EnableWebMvc
注解也能够解决。
Swagger
框架须要通过解析和扫描带有注解的Controller
类和办法来生成API文档。@EnableWebMvc
注解会注册一个RequestMappingHandlerMapping
的Bean,并将其作为默认的申请映射处理器,以确保这些Controller类和办法可能被正确处理,能够与Swagger的门路配置规定相匹配,从而使得Swagger可能胜利生成API文档。
@EnableWebMvc@Configuration@EnableSwagger2public class SwaggerConfig {}
4、注册 BeanPostProcessor
也能够自行实现兼容逻辑来解决这个问题,咱们能够在Spring
容器中注册一个BeanPostProcessor
,在该处理器中对 HandlerMappings 进行定制。
通过过滤掉已存在PatternParser的映射,意味着咱们能够将Swagger特定的HandlerMappings增加到HandlerMappings
列表中,从而应用自定义的设置来代替原有的HandlerMappings。
这样修复了可能导致Swagger无奈失常应用的问题。
@Beanpublic static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() { return new BeanPostProcessor() { @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) { customizeSpringfoxHandlerMappings(getHandlerMappings(bean)); } return bean; } private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings) { List<T> copy = mappings.stream() .filter(mapping -> mapping.getPatternParser() == null) .collect(Collectors.toList()); mappings.clear(); mappings.addAll(copy); } @SuppressWarnings("unchecked") private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) { try { Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings"); field.setAccessible(true); return (List<RequestMappingInfoHandlerMapping>) field.get(bean); } catch (IllegalArgumentException | IllegalAccessException e) { log.warn("批改WebMvcRequestHandlerProvider的属性:handlerMappings出错,可能导致swagger不可用", e); throw new IllegalStateException(e); } } };}
拜访 swagger-ui
到这,问题解决!咱们拜访Swagger文档门路 http://127.0.0.1:9002/swagger-ui/index.html
,可能看到咱们写的 API 信息以及一些Swagger 文档的默认配置信息。
留神到咱们只写了一个 /test
接口,但这里确把这个办法的所有申请形式都列了进去,因为咱们在 controller
办法中应用了@RequestMapping
注解,并没有具体的指定接口的申请形式,所以防止文档冗余,尽量指定申请形式或者应用指定申请形式的 @XXXMapping
注解。
指定申请形式后:
API文档配置
上边咱们拜访的文档中展现的数据都是默认的配置,当初咱们来定制化一下文档。
Springfox
提供了一个Docket
对象,供咱们灵便的配置Swagger
的各项属性。Docket
对象内提供了很多的办法来配置文档,下边介绍下罕用的配置项。
select
select()
返回一个ApiSelectorBuilder
对象,是应用apis()
、paths()
两个办法的前提,用于指定Swagger要扫描的接口和门路。
apis
默认状况下,Swagger会扫描整个我的项目中的接口,通过 apis()
办法,你能够传入一个RequestHandlerSelector
对象实例来指定要蕴含的接口所在的包门路。
@Beanpublic Docket docket(Environment environment) { return new Docket(DocumentationType.SWAGGER_2) .select() .apis(RequestHandlerSelectors.basePackage("com.springboot101.controller")) .build();}
paths
仅将某些特定申请门路的API
展现在Swagger文档中,例如门路中蕴含/test
。能够应用 apis()
和 paths()
办法一起来过滤接口。
@Beanpublic Docket docket(Environment environment) { return new Docket(DocumentationType.SWAGGER_2) .select() .paths(PathSelectors.ant("/test/**")) .build();}
groupName
为生成的Swagger
文档指定分组的名称,用来辨别不同的文档组。
@Beanpublic Docket docket(Environment environment) { return new Docket(DocumentationType.SWAGGER_2) .groupName("用户分组") .build();}
实现文档的多个分组,则需创立多个 Docket
实例,设置不同的组名,和组内过滤 API 的条件。
@Beanpublic Docket docket1(Environment environment) { return new Docket(DocumentationType.SWAGGER_2) .groupName("商家分组") .select() .paths(PathSelectors.ant("/test1/**")) .build();}
apiInfo
设置API文档的根本信息,例如题目、形容、版本等。你能够应用ApiInfo
对象自定义信息。
@Beanpublic Docket docket(Environment environment) { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()); // 文档根底配置}private ApiInfo apiInfo() { Contact contact = new Contact("小富", "http://fire100.top", "email@example.com"); return new ApiInfoBuilder() .title("Swagger学习") .description("程序员小富-带你一起学习 Swagger") .version("v1.0.1") .termsOfServiceUrl("http://fire100.top") .contact(contact) .license("许可证") .licenseUrl("许可链接") .extensions(Arrays.asList( new StringVendorExtension("我是", "小富"), new StringVendorExtension("你是", "谁") )) .build();}
对应的Swagger文档页面上展现的地位
enable
启用或禁用Swagger文档的生成,有时测试环境会凋谢API文档,但在生产环境则要禁用,能够依据环境变量管制是否显示。
@Beanpublic Docket docket(Environment environment) { // 可显示 swagger 文档的环境 Profiles of = Profiles.of("dev", "test","pre"); boolean enable = environment.acceptsProfiles(of); return new Docket(DocumentationType.SWAGGER_2) .enable(enable) .apiInfo(apiInfo()); // 文档根底配置}
host
API文档显示的主机名称或IP地址,即在测试执行接口时应用的IP或域名。
@Beanpublic Docket docket(Environment environment) { return new Docket(DocumentationType.SWAGGER_2) .host("http://test.com") // 申请地址 .apiInfo(apiInfo()); // 文档根底配置}
securitySchemes
配置API平安认证形式,比方常见的在header
中设置如Bearer
、Authorization
、Basic
等鉴权字段,ApiKey
对象中字段含意别离是别名、鉴权字段key、鉴权字段增加的地位。
@Beanpublic Docket docket(Environment environment) { return new Docket(DocumentationType.SWAGGER_2) .securitySchemes( Arrays.asList( new ApiKey("Bearer鉴权", "Bearer", "header"), new ApiKey("Authorization鉴权", "Authorization", "header"), new ApiKey("Basic鉴权", "Basic", "header") ) );}
这样配置后,Swagger文档将会蕴含一个Authorize
按钮,供用户输出咱们设定的Bearer
、Authorization
、Basic
进行身份验证。
securityContexts
securitySchemes
办法中尽管设置了鉴权字段,但此时在测试接口的时候不会主动在 header
中加上鉴权字段和值,还要配置API的平安上下文,指定哪些接口须要进行平安认证。
@Beanpublic Docket docket(Environment environment) { return new Docket(DocumentationType.SWAGGER_2) .securitySchemes( Arrays.asList( new ApiKey("Bearer鉴权", "Bearer", "header"), new ApiKey("Authorization鉴权", "Authorization", "header"), new ApiKey("Basic鉴权", "Basic", "header") ) ) .securityContexts(Collections.singletonList(securityContext()));}private SecurityContext securityContext() { return SecurityContext.builder() .securityReferences( Arrays.asList( new SecurityReference("Authorization", new AuthorizationScope[0]), new SecurityReference("Bearer", new AuthorizationScope[0]), new SecurityReference("Basic", new AuthorizationScope[0]))) .build();}
当初在测试调用API接口时,header
中能够失常加上鉴权字段和值了。
tags
为API文档中的接口增加标签,标签能够用来对API进行分类或分组,并提供更好的组织和导航性能。
@Beanpublic Docket docket(Environment environment) { return new Docket(DocumentationType.SWAGGER_2) .tags(new Tag("小富接口", "小富相干的测试接口"))}
受权登录
出于对系统安全性的思考,通常咱们还会为API文档减少登录性能。
引入maven依赖
swagger的平安登录是基于security
实现的,引入相干的maven
依赖。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId></dependency>
登录配置
application.yml
文件中配置登录swagger的用户名和明码。
spring: security: user: name: admin password: 123456
再次拜访文档就会呈现如下的登录页
文档注解
当咱们心愿在Swagger文档中提供具体和残缺的内容时,还能够应用其余许多Swagger内置注解来进一步丰盛和定制API文档。
@ApiIgnore
上边咱们提到能够依据指定门路或者包门路来提供API,也能够应用粒度更细的@ApiIgnore
注解,来实现某个API在文档中疏忽。
@ApiIgnore@GetMapping("/user2/{id}")public User test2(@PathVariable Integer id, @RequestBody User user) { return user;}
@ApiModel
在咱们的接口中,只有应用实体作为参数或响应体,Swagger就会主动扫描到它们,但你会发现目前这些实体不足具体的形容信息。为了让使用者通俗易懂,须要应用swagger提供的注解为这些实体增加具体的形容。
@ApiModel
注解的应用在实体类上,提供对Swagger Model额定信息的形容。
@ApiModelProperty
@ApiModelProperty
注解为实体类中的属性增加形容,提供了字段名称、是否必填、字段示例等形容信息。
@ApiModel(value = "用户实体类", description = "用于寄存用户登录信息")@Datapublic class User { @ApiModelProperty(value = "用户名字段", required = true, example = "#公众号:程序员小富") private String name; @ApiModelProperty(value = "年龄", required = true, example = "19") private Integer age; @ApiModelProperty(value = "邮箱", required = true, example = "#公众号:程序员小富") private String email;}
@Api
@Api
注解用于标记一个控制器(controller
)类,并提供接口的详细信息和配置项。
value
:API 接口的形容信息,因为版本swagger版本起因,value
可能会不失效能够应用description
hidden
:该 API 是否在 Swagger 文档中暗藏tags
:API 的标签,如果此处与new Docket().tags
中设置的标签统一,则会将该 API 放入到这个标签组内authorizations
:鉴权配置,配合@AuthorizationScope
注解管制权限范畴或者特定密钥能力拜访该API。produces
:API的响应内容类型,例如 application/json。consumes
:API的申请内容类型,例如 application/json。protocols
:API反对的通信协议。
@Api(value = "用户治理接口形容", description = "用户治理接口形容", hidden = false, produces = "application/json", consumes = "application/json", protocols = "https", tags = {"用户治理"}, authorizations = { @Authorization(value = "apiKey", scopes = { @AuthorizationScope(scope = "read:user", description = "读权限"), @AuthorizationScope(scope = "write:user", description = "写权限") }), @Authorization(value = "basicAuth") })@RestControllerpublic class TestController {}
@ApiOperation
@ApiOperation
该注解作用在接口办法上,用来对一个操作或HTTP办法进行形容。
value
:对接口办法的简略阐明notes
:对操作的具体阐明。httpMethod
:申请形式code
:状态码,默认为 200。能够传入符合标准的HTTP Status Code Definitions。hidden
:在文档中暗藏该接口response
:返回的对象tags
:应用该注解后,该接口办法会独自进行分组produces
:API的响应内容类型,例如 application/json。consumes
:API的申请内容类型,例如 application/json。protocols
:API反对的通信协议。authorizations
:鉴权配置,配合@AuthorizationScope
注解管制权限范畴或者特定密钥能力拜访该API。responseHeaders
:响应的header内容
@ApiOperation( value = "获取用户信息", notes = "通过用户ID获取用户的详细信息", hidden = false, response = UserDto.class, tags = {"用户治理"}, produces = "application/json", consumes = "application/json", protocols = "https", authorizations = { @Authorization(value = "apiKey", scopes = {@AuthorizationScope(scope = "read:user", description = "读权限")}), @Authorization(value = "Basic") }, responseHeaders = {@ResponseHeader(name = "X-Custom-Header", description = "Custom header", response = String.class)}, code = 200, httpMethod = "GET")@GetMapping("/user1")public UserDto user1(@RequestBody User user) { return new UserDto();}
@ApiImplicitParams
@ApiImplicitParams
注解用在办法上,以数组形式存储,配合@ApiImplicitParam
注解应用。
@ApiImplicitParam
@ApiImplicitParam
注解对API办法中的繁多参数进行注解。
留神这个注解@ApiImplicitParam
必须被蕴含在注解@ApiImplicitParams
之内。
name
:参数名称value
:参数的简短形容required
:是否为必传参数dataType
:参数类型,能够为类名,也能够为根本类型(String,int、boolean等)paramType
:参数的传入(申请)类型,可选的值有 path、query、body、header、form。
@ApiImplicitParams({ @ApiImplicitParam(name = "用户名", value = "用户名称信息", required = true, dataType = "String", paramType = "query")})@GetMapping("/user")public String user(String name) { return name;}
@ApiParam()
@ApiParam()
也是对API办法中的繁多参数进行注解,其外部属性和@ApiImplicitParam
注解类似。
@GetMapping("/user4")public String user4(@ApiParam(name = "主键ID", value = "@ApiParam注解测试", required = true) String id) { return id;}
@ApiResponses
@ApiResponses
注解可用于形容申请的状态码,作用在办法上,以数组形式存储,配合 @ApiResponse
注解应用。
@ApiResponse
@ApiResponse
注解形容一种申请的状态信息。
code
:HTTP申请响应码。message
:响应的文本音讯response
:返回类型信息。responseContainer
:如果返回类型为容器类型,能够设置相应的值。有效值为 "List"、 "Set"、"Map"其余任何有效的值都会被疏忽。
@ApiResponses(value = { @ApiResponse(code = 200, message = "@ApiResponse注解测试通过", response = String.class), @ApiResponse(code = 401, message = "可能参数填的有问题", response = String.class), @ApiResponse(code = 404, message = "可能申请门路写的有问题", response = String.class)})@GetMapping("/user4")public String user4(@ApiParam(name = "主键ID", value = "@ApiParam注解测试", required = true) String id) { return id;}
总结
只管在面试中不会过多考查Swagger这类工具,但作为开发者,养成良好的文档标准习惯是十分重要的,无论应用Swagger还是其余文档工具,编写清晰、详尽的API文档都应该是咱们的素养之一。
代码示例
https://github.com/chengxy-nds/Springboot-Notebook/tree/master/springboot101/%E9%80%9A%E7%94%A8%E5%8A%9F%E8%83%BD/springboot-swagger