Springfox、Springdoc和Swagger
Springfox、Swagger 和 Springdoc
Springfox、Swagger 和 Springdoc 是用于在 Spring Boot 项目中生成API文档的工具,但它们之间有显著的区别和演进关系:
1.Swagger
简介
- Swagger 是一个开源项目,旨在为 RESTful APIs 提供交互式文档。
- 最早由 SmartBear 开发,后来演进为 OpenAPI 规范 的前身。
- Swagger 的核心组件包括:
- Swagger UI:提供交互式的 Web 界面,展示 API 端点并允许直接调用测试。
- Swagger Editor:编写和查看 OpenAPI 描述文件的工具。
- Swagger Codegen:基于 API 描述文件生成客户端和服务端代码。
与 Spring 的关系
Swagger 本身不依赖 Spring,但通过扩展工具(如 Springfox)使其在 Spring 框架中得到使用。
Swagger2是Swagger规范的一个实现
Swagger3是基于OpenAPI规范的新版本,它是Swagger规范的后续标准,
提供了更好的可扩展性和更丰富的功能。Spring Boot从2.6.0版本开始不再原生支持Swagger2,因为Spring官方的更新导致了与Swagger2的不兼容。
开发者需要使用Springdoc OpenAPI库来替代Springfox,以在Spring Boot 2.6.0及以上版本中集成OpenAPI文档。
Springdoc OpenAPI提供了对OpenAPI 3.0规范的支持,并且与Spring Boot的新版本兼容。
2.Springfox
简介
- Springfox 是一个专门为 Spring Boot 集成 Swagger 的库。
- 核心功能:扫描 Spring 项目中的注解和配置,生成基于 Swagger 的 API 文档。
- 特点:
- 支持 Spring MVC 和 Spring WebFlux。
- 使用
@ApiOperation
和@ApiModel
等注解来生成文档。 - 支持 Swagger 2 和部分 OpenAPI 3 特性。
现状
- 停止活跃维护:Springfox 项目在 2021 年后维护频率大幅降低,社区对它的支持逐渐减少。3.0.x版本以后没有再更新
- 兼容性问题:
- 与 Spring Boot 2.6.x 和更高版本存在兼容性问题,主要是因为 Springfox 使用的
RequestMappingHandlerMapping
被 Spring Framework 的 Web 模块改动影响。
- 与 Spring Boot 2.6.x 和更高版本存在兼容性问题,主要是因为 Springfox 使用的
何时使用
- 如果项目是基于 Spring Boot 2.5.x 或更早版本,并且已经使用了 Springfox,可以暂时保留。
- 对于新项目,不建议继续使用 Springfox。
3.Springdoc
简介
- Springdoc 是一个现代化工具,基于 OpenAPI 3 规范设计,替代 Springfox。
- 提供与 Spring Boot 的无缝集成:
- 自动生成 OpenAPI 3 文档。
- 提供嵌入式的 Swagger UI(无需单独配置)。
- 兼容 Spring MVC 和 Spring WebFlux。
优点
- 强大兼容性:
- 与 Spring Boot 2.x 和 3.x 完美兼容。
- 支持 Spring Framework 的最新功能,例如响应式流和新注解模型。
- 零配置:
- 大部分功能开箱即用,减少了复杂的注解和配置需求。
- 社区活跃:
- 相比 Springfox,Springdoc 项目更活跃,持续发布新版本,解决社区反馈。
使用方式
1.添加 Maven 依赖:
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>最新版本</version>
</dependency>
2.启用后,访问默认路径 http://localhost:8080/swagger-ui.html。
总结:它们的关系与选择
工具 | 关系 | 适用场景 | 当前建议 |
---|---|---|---|
Swagger | 基础规范和工具 | 原始工具,用于标准化 API 文档 | 用于 OpenAPI 标准支持 |
Springfox | Swagger 的 Spring 集成实现 | 传统项目(Spring Boot 2.5.x) | 不再推荐,已过时 |
Springdoc | 基于 OpenAPI 3 的现代化替代工具 | 新项目,支持最新的 Spring Boot | 强烈推荐 |
迁移建议:从 Springfox 到 Springdoc
如果你当前使用 Springfox,但需要升级 Spring Boot 或改进文档支持,可以迁移到 Springdoc:
- 替换依赖:
- 移除
springfox-swagger2
和springfox-swagger-ui
。 - 添加
springdoc-openapi-ui
。
- 移除
- 注解适配:
- Springdoc 支持 OpenAPI 3 的注解,通常是标准的 JSR-303 和 Spring 注解。
- 替换
@ApiOperation
为@Operation
,替换@ApiModel
为标准注解。
- 配置改动:
- Springdoc 几乎不需要额外配置,大部分文档生成会自动完成。
结论:对于新项目,推荐使用 Springdoc;对于维护中的老项目,可以逐步迁移到 Springdoc,以便享受最新功能和更好的兼容性。
使用springboot2.6+版本和swagger2不兼容的解决方案
以下是一个springboot2.7.10集成swagger2和knife4j的例子
1、引入knife4j
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
knife4j官方建议开发者不要使用
2、knife4j配置文件
import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@Configuration
@EnableSwagger2
@EnableKnife4j
public class Knife4jConfig {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.useDefaultResponseMessages(false)
.enable(true)
.apiInfo(apiInfo())
.select()
//.apis(RequestHandlerSelectors.basePackage("com.linear.visual.controller"))
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build()
.securitySchemes(Collections.singletonList(securityScheme()))
.securityContexts(securityContexts());
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.description("可视化接口")
.contact(new Contact("Linear", "linear", "linear"))
.version("v1.0.0")
.title("API测试文档")
.build();
}
@Bean
SecurityScheme securityScheme() {
return new ApiKey("Authorization", "Authorization", "header");
}
/**
* 安全上下文
*/
private List<SecurityContext> securityContexts() {
List<SecurityContext> securityContexts = new ArrayList<>();
securityContexts.add(
SecurityContext.builder()
.securityReferences(defaultAuth())
.build());
return securityContexts;
}
/**
* 默认的安全上引用
*/
private List<SecurityReference> defaultAuth() {
AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
authorizationScopes[0] = authorizationScope;
List<SecurityReference> securityReferences = new ArrayList<>();
securityReferences.add(new SecurityReference("Authorization", authorizationScopes));
return securityReferences;
}
}
如果使用低版本的springboot比如2.6以下的,基本上都可以成功的。
但是使用2.6以上版本,这个时候可能会报启动失败
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.26.jar:5.3.26]
at org.springframework.context.support.DefaultLifecycleProcessor.access$200(DefaultLifecycleProcessor.java:54) ~[spring-context-5.3.26.jar:5.3.26]
at org.springframework.context.support.DefaultLifecycleProcessor$LifecycleGroup.start(DefaultLifecycleProcessor.java:356) ~[spring-context-5.3.26.jar:5.3.26]
at java.base/java.lang.Iterable.forEach(Iterable.java:75) ~[na:na]
at org.springframework.context.support.DefaultLifecycleProcessor.startBeans(DefaultLifecycleProcessor.java:155) ~[spring-context-5.3.26.jar:5.3.26]
at org.springframework.context.support.DefaultLifecycleProcessor.onRefresh(DefaultLifecycleProcessor.java:123) ~[spring-context-5.3.26.jar:5.3.26]
at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:937) ~[spring-context-5.3.26.jar:5.3.26]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:586) ~[spring-context-5.3.26.jar:5.3.26]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:147) ~[spring-boot-2.7.10.jar:2.7.10]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:731) ~[spring-boot-2.7.10.jar:2.7.10]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:408) ~[spring-boot-2.7.10.jar:2.7.10]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:307) ~[spring-boot-2.7.10.jar:2.7.10]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1303) ~[spring-boot-2.7.10.jar:2.7.10]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1292) ~[spring-boot-2.7.10.jar:2.7.10]
at com.asr.hedu.fsp.service.oms.course.OmsCourseBootstrap.main(OmsCourseBootstrap.java:47) ~[classes/:na]
Caused by: java.lang.NullPointerException: null
at springfox.documentation.spring.web.WebMvcPatternsRequestConditionWrapper.getPatterns(WebMvcPatternsRequestConditionWrapper.java:56) ~[springfox-spring-webmvc-3.0.0.jar:3.0.0]
at springfox.documentation.RequestHandler.sortedPaths(RequestHandler.java:113) ~[springfox-core-3.0.0.jar:3.0.0]
at springfox.documentation.spi.service.contexts.Orderings.lambda$byPatternsCondition$3(Orderings.java:89) ~[springfox-spi-3.0.0.jar:3.0.0]
at java.base/java.util.Comparator.lambda$comparing$77a9974f$1(Comparator.java:469) ~[na:na]
at java.base/java.util.TimSort.countRunAndMakeAscending(TimSort.java:355) ~[na:na]
at java.base/java.util.TimSort.sort(TimSort.java:234) ~[na:na]
at java.base/java.util.Arrays.sort(Arrays.java:1515) ~[na:na]
at java.base/java.util.ArrayList.sort(ArrayList.java:1750) ~[na:na]
at java.base/java.util.stream.SortedOps$RefSortingSink.end(SortedOps.java:392) ~[na:na]
at java.base/java.util.stream.Sink$ChainedReference.end(Sink.java:258) ~[na:na]
at java.base/java.util.stream.Sink$ChainedReference.end(Sink.java:258) ~[na:na]
at java.base/java.util.stream.Sink$ChainedReference.end(Sink.java:258) ~[na:na]
at java.base/java.util.stream.Sink$ChainedReference.end(Sink.java:258) ~[na:na]
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:485) ~[na:na]
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474) ~[na:na]
at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913) ~[na:na]
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:na]
at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578) ~[na:na]
at springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider.requestHandlers(WebMvcRequestHandlerProvider.java:81) ~[springfox-spring-webmvc-3.0.0.jar:3.0.0]
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195) ~[na:na]
at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1655) ~[na:na]
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484) ~[na:na]
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474) ~[na:na]
at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913) ~[na:na]
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:na]
at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578) ~[na:na]
at springfox.documentation.spring.web.plugins.AbstractDocumentationPluginsBootstrapper.withDefaults(AbstractDocumentationPluginsBootstrapper.java:107) ~[springfox-spring-web-3.0.0.jar:3.0.0]
at springfox.documentation.spring.web.plugins.AbstractDocumentationPluginsBootstrapper.buildContext(AbstractDocumentationPluginsBootstrapper.java:91) ~[springfox-spring-web-3.0.0.jar:3.0.0]
at springfox.documentation.spring.web.plugins.AbstractDocumentationPluginsBootstrapper.bootstrapDocumentationPlugins(AbstractDocumentationPluginsBootstrapper.java:82) ~[springfox-spring-web-3.0.0.jar:3.0.0]
at springfox.documentation.spring.web.plugins.DocumentationPluginsBootstrapper.start(DocumentationPluginsBootstrapper.java:100) ~[springfox-spring-web-3.0.0.jar:3.0.0]
at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:178) ~[spring-context-5.3.26.jar:5.3.26]
... 14 common frames omitted
这个问题经查询跟springboot的bean初始化有关系,后续的版本更改了相关的东西,具体是什么没有细究,反正参照stackoverflow中的修改方法,加入一下配置类就可以避免错误
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.NonNull;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
import springfox.documentation.spring.web.plugins.WebFluxRequestHandlerProvider;
import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider;
import java.lang.reflect.Field;
import java.util.List;
import java.util.stream.Collectors;
/**
* swagger config
*
* @author zxl
* @date 2023-05-16
*/
@Configuration
public class SwaggerConfig {
@Bean
public BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {
return new BeanPostProcessor() {
@Override
public Object postProcessAfterInitialization(@NonNull Object bean, @NonNull 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");
assert field != null;
field.setAccessible(true);
return (List<RequestMappingInfoHandlerMapping>) field.get(bean);
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new IllegalStateException(e);
}
}
};
}
}
加入后启动正常,但是访问swagger的/v2/api-docs接口,发现没有获得数据,接口内容是空的。
这是通过搜索得到有几种解决方案
1、ShortVideoSwagger2的配置类需要集成WebMvcConfigurationSupport
2、ShortVideoSwagger2的配置类增加@EnableWebMvc注解
3、springboot的配置增加增加一下配置
spring:
mvc:
pathmatch:
matching-strategy: ANT_PATH_MATCHE
通过试验得知,这三种接口都可以解决,但是前两种是有副总用的
@EnableWebMvc建议慎用,最后在非springboot项目中使用
前两种解决方案会破坏springboot对springwebmvc的自动装配,导致自定义的一些convertor或者ObjectMapper失效。
目前我的项目中是自定义的ObjectMapper失效。所以最后使用第三种方式,后期的springboot版本的matching-strategy默认的改为了PATH_PATTERN_PARSER,把它改为ANT_PATH_MATCHER就可以了。