记我的Springboot2.6.4从集成swagger到springdoc的坎坷路~

embedded/2024/12/26 8:50:08/

项目背景

主要依赖及jdk信息:

Springboot:2.6.4

Jdk: 1.8

最近新搭建了一套管理系统,前端部分没有公司的前端团队,自己在github上找了一个star较多使用相对也简单的框架。在这个管理系统搭建好上线之后,给组内的小伙伴们分享时,前端同事第一次提出,他们可以参与进来,前端部分由他们改造,我这边只需要提供接口就行。

这时,接口是现成的,但缺文档,于是,第一想到的,就是集成swagger,想着添加依赖、在一些controller上添加对应注解,就能满足需求,之后调整代码也不用再单独去维护文档。所以,就开始我这为期2天的集成路~~~

集成过程:

集成方式一:直接添加swagger依赖

java">        <dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.9.2</version></dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><version>2.9.2</version></dependency>

增加swagger配置

java">import org.springframework.context.annotation.Configuration;
import springfox.documentation.swagger2.annotations.EnableSwagger2;@Configuration
@EnableSwagger2
public class SwaggerConfig {}

启动项目,正常情况下可访问:http://host:port/context-path/swagger-ui.html查看swagger界面

遇到的问题

然而,项目启动失败,报错信息如下:

java">2024-12-25 12:19:59.619 [main]  WARN AbstractApplicationContext.java:591 - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.context.ApplicationContextException: Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException

WARN AbstractApplicationContext.java:591 - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.context.ApplicationContextException: Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException

详细信息:

java">2024-12-25 13:29:04.634 [main]  ERROR SpringApplication.java:830 - Application run failed
org.springframework.context.ApplicationContextException: Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerExceptionat org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:181)at org.springframework.context.support.DefaultLifecycleProcessor.access$200(DefaultLifecycleProcessor.java:54)at org.springframework.context.support.DefaultLifecycleProcessor$LifecycleGroup.start(DefaultLifecycleProcessor.java:356)at java.lang.Iterable.forEach(Iterable.java:75)at org.springframework.context.support.DefaultLifecycleProcessor.startBeans(DefaultLifecycleProcessor.java:155)at org.springframework.context.support.DefaultLifecycleProcessor.onRefresh(DefaultLifecycleProcessor.java:123)at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:935)at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:586)at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:145)at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:740)at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:415)at org.springframework.boot.SpringApplication.run(SpringApplication.java:303)at org.springframework.boot.SpringApplication.run(SpringApplication.java:1312)at org.springframework.boot.SpringApplication.run(SpringApplication.java:1301)at com.ifeng.hm.novel.admin.NovelAdminApplication.main(NovelAdminApplication.java:19)
Caused by: java.lang.NullPointerException: nullat springfox.documentation.spi.service.contexts.Orderings$8.compare(Orderings.java:112)at springfox.documentation.spi.service.contexts.Orderings$8.compare(Orderings.java:109)at com.google.common.collect.ComparatorOrdering.compare(ComparatorOrdering.java:40)at java.util.TimSort.countRunAndMakeAscending(TimSort.java:355)at java.util.TimSort.sort(TimSort.java:220)at java.util.Arrays.sort(Arrays.java:1438)at com.google.common.collect.Ordering.sortedCopy(Ordering.java:854)at springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider.requestHandlers(WebMvcRequestHandlerProvider.java:57)at springfox.documentation.spring.web.plugins.DocumentationPluginsBootstrapper$2.apply(DocumentationPluginsBootstrapper.java:138)at springfox.documentation.spring.web.plugins.DocumentationPluginsBootstrapper$2.apply(DocumentationPluginsBootstrapper.java:135)at com.google.common.collect.Iterators$6.transform(Iterators.java:829)at com.google.common.collect.TransformedIterator.next(TransformedIterator.java:52)at com.google.common.collect.TransformedIterator.next(TransformedIterator.java:52)at com.google.common.collect.Iterators$ConcatenatedIterator.hasNext(Iterators.java:1400)at com.google.common.collect.ImmutableList.copyOf(ImmutableList.java:275)at com.google.common.collect.ImmutableList.copyOf(ImmutableList.java:239)at com.google.common.collect.FluentIterable.toList(FluentIterable.java:631)at springfox.documentation.spring.web.plugins.DocumentationPluginsBootstrapper.defaultContextBuilder(DocumentationPluginsBootstrapper.java:111)at springfox.documentation.spring.web.plugins.DocumentationPluginsBootstrapper.buildContext(DocumentationPluginsBootstrapper.java:96)at springfox.documentation.spring.web.plugins.DocumentationPluginsBootstrapper.start(DocumentationPluginsBootstrapper.java:167)at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:178)... 14 common frames omitted

出现这个异常的原因:

在SpringBoot2.6之后,Spring MVC 处理程序映射匹配请求路径的默认策略已从 AntPathMatcher 更改为PathPatternParser。

解决方案

那既然定位到原因就好办了,针对问题解决问题,于是,借助网络资源,开始尝试~~~

网上解决方案一:

降低springboot版本,降到2.6以下,但我项目中还依赖了spring-cloud-alibaba,nacos,setinel等等,降版本比较麻烦,这个方案pass掉了,继续探索

网上解决方案二

springboot的配置文件(yaml或properties)中添加如下配置(示例为yaml):

java">spring:mvc:pathmatch:matching-strategy: ant_path_matcher

这里的matching-strategy可选值有:

原因也很简单,既然springboot2.6之后,映射配置请求路径的默认策略从AntPathMatcher更改为了PathPatternParser,那通过配置手动改回应该就可以。

漫长探索之路

然页,添加配置后,项目启动,又失败了!!!这!不!科!学!为什么大家都留意说亲测有效,我这么配置就不行呢?

没有办法,从自身找问题吧,遇事不决先debug看看~

先找到setMatchingStrategy方法看看配置有没有生效,是没读取到配置,还是就使用了默认配置?

(org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties)

(org.springframework.boot.context.properties.bind.JavaBeanBinder.BeanProperty)

通过这里看,正确读取了配置,并调用set方法成功设置属性值。

近一步验证,配置没有问题!

继续debug...

在执行springfox.documentation.spring.web.plugins.DocumentationPluginsBootstrapper#start之前的日志:

从这里的日志信息来看,Swagger2Controller注入也没有问题

Ok!那接下来重点看springfox.documentation.spring.web.plugins.DocumentationPluginsBootstrapper#start方法

找到一个plugin,for循环中调用:

scanDocumentation(buildContext(each));

 继续debug,到这里抛出了异常:

对这个array做sort操作时,出现了NPE!那么,继续进到这个array看为什么会是这样的(重点0-2这三个元素,actuator)

在执行Arrays.sort(array)时用到的comparator是springfox.documentation.spi.service.contexts.Orderings

所以,出现了NullPointerException~

初现柳暗花明

那么,先不进一步看为什么,先来做个尝试,把/actuator排除或者直接移除依赖可不可行?试试看~

java">            <exclusions><exclusion><artifactId>spring-boot-actuator</artifactId><groupId>org.springframework.boot</groupId></exclusion><exclusion><artifactId>spring-boot-actuator-autoconfigure</artifactId><groupId>org.springframework.boot</groupId></exclusion></exclusions>

项目成功启动,打开http://localhost:8080/swagger-ui.html

正常!

终于正常了!

但是,如果actuator强依赖,不能exclude掉呢?对的,加回来,继续尝试~~~

尝试一:通过swagger配置,只匹配某些路径

java">@Configuration
@EnableSwagger2
public class SwaggerConfig {@Beanpublic Docket api() {return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select().apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)).paths(PathSelectors.ant("/admin/**")).build();}/*** 添加摘要信息*/private ApiInfo apiInfo(){// 用ApiInfoBuilder进行定制return new ApiInfoBuilder()// 设置标题.title("标题:管理系统_接口文档")// 描述.description("描述:用于管理xxx信息,具体包括XXX,XXX模块...")// 作者信息.contact(new Contact("xx@abc.com", null, null))// 版本.version("版本号:1.0.0").build();}
}

虽然,这里配置了只select带有ApiOperation注解的method以及路径/admin/**,但是,对前面那个异常无效,并不妨碍它失败!!!

究其原因:

这个AddtionalHealthEndpointPathsWebMvcHandlerMapping依然会把/actuator/**加入之前的arrays中,而在array的sort时会因为PatternsCondition为null从而出现NPE!

那么,接下来,对WebMvcRequestHandlerProvider进行改造。

方法一:

自定义BeanPostProcessor在postProcessAfterInitialization方法中对PatternParser进行过滤

java">@Beanpublic BeanPostProcessor springfoxHandlerProviderBeanPostProcessor(){return new BeanPostProcessor() {@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {if (bean instanceof WebMvcRequestHandlerProvider) {customizeSpringfoxHandlerMappings(getHandlerMappings(bean));}return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);}};}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);}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) {throw new IllegalStateException(e);}}

重点:customizeSpringfoxHandlerMappings方法

经过filter之后的array

NullPointerException不见了!

方法二:

hack一个WebMvcRequestHandlerProvider,处理handlerMapping属性

在src目录下创建package:

springfox.documentation.spring.web.plugins

复制WebMvcRequestHandlerProvider到这个自建的同名的包下

修改构造方法:

同样,启动成功~~~

到这里,springboot2集成swagger过程中遇到的这个问题算是解决了。但是这个过程太痛苦了,费时,又费力~~~~

集成方式二:使用springfox-boot-starter

添加依赖:

java">            <!-- Swagger3依赖 --><dependency><groupId>io.springfox</groupId><artifactId>springfox-boot-starter</artifactId><version>${swagger.version}</version><exclusions><exclusion><groupId>io.swagger</groupId><artifactId>swagger-models</artifactId></exclusion></exclusions></dependency>

与集成方式一相同,会遇到同样的问题,解决方式也相同,不再赘述!

但是它有个问题

2020年之后,不再维护了~,这让人很不安啊!!!

集成方式三:接入springdoc,放弃springfox

添加依赖

java">        <dependency><groupId>org.springdoc</groupId><artifactId>springdoc-openapi-ui</artifactId><version>1.8.0</version></dependency>

注:1.8.0是最新支持springboot2.x和1.x的版本

增加简单配置类:

java">@Configuration
public class OpenApiConfig {
}

启动项目:

正常启动,没有出现方式一、方式二中遇到的问题,喜大普奔~~~~

springdoc的继续探索
分组与接口过滤
java">    /*** 配置过滤规则* 若不配置该GroupedOpenApi, 默认扫描所有接口并生成文档* @return*/@Beanpublic GroupedOpenApi bookApi() {return GroupedOpenApi.builder()// 接口过滤,据此增加接口扫描规则(扫描@Operation注解标注的接口)。想皮一下的话,亦可自定义注解.addOpenApiMethodFilter(method -> method.isAnnotationPresent(Operation.class)).group("book") // 分组名,可建多个不同分组,分别扫描不同位置接口.pathsToMatch("/admin/book/**").build();}/*** 配置过滤规则* 若不配置该GroupedOpenApi, 默认扫描所有接口并生成文档* @return*/@Beanpublic GroupedOpenApi bookCategoryApi() {return GroupedOpenApi.builder()// 接口过滤,据此增加接口扫描规则(扫描@Operation注解标注的接口)。想皮一下的话,亦可自定义注解.addOpenApiMethodFilter(method -> method.isAnnotationPresent(Operation.class)).group("bookCategory") // 分组名,可建多个不同分组,分别扫描不同位置接口.pathsToMatch("/admin/category/**").build();}

效果:

常见配置

见官方文档:OpenAPI 3 Library for spring-boot

接口鉴权与认证

启动类增加@SecurityScheme注解

java">@SecurityScheme(name = "api_token", type = SecuritySchemeType.HTTP, scheme ="bearer", in = SecuritySchemeIn.HEADER)
public class BookAdminApplication {public static void main(String[] args) {SpringApplication.run(NovelAdminApplication.class, args);}
}

配置类中配置API安全策略:

java">    @Beanpublic OpenAPI demoOpenAPI() {return new OpenAPI().info(new Info().title("Novel/Book API").description("novel-admin book,book-category,book-list related api docs").version("v1.0.0")).security(List.of(new SecurityRequirement().addList("api_token")))//注意这里的api_token与启动类中的配置相同;}

如果某个接口不需要安全认证(如login,logout之类),则可以在接口方法中添加如下注解:

java">@RestController
@RequestMapping("/healthChk")
public class HealthChkController {//@SecurityRequirements() 里面需要一个String数组,里面列出需要使用的@SecurityScheme,例如我们这里的api_token。如果不写就说明不需要任何的安全模式,这里就是这种情况。@SecurityRequirements()@RequestMapping("/check")public String healthChk() {return "ok";}
}
@SecurityRequirements() 里面需要一个String数组,里面列出需要使用的@SecurityScheme,例如我们这里的api_token。如果不写就说明不需要任何的安全模式,这里就是这种情况。

其他更详细的使用,可以阅读springdoc官方文档学习~~~


http://www.ppmy.cn/embedded/148848.html

相关文章

在瑞芯微RK3588平台上使用RKNN部署YOLOv8Pose模型的C++实战指南

在人工智能和计算机视觉领域,人体姿态估计是一项极具挑战性的任务,它对于理解人类行为、增强人机交互等方面具有重要意义。YOLOv8Pose作为YOLO系列中的新成员,以其高效和准确性在人体姿态估计任务中脱颖而出。本文将详细介绍如何在瑞芯微RK3588平台上,使用RKNN(Rockchip N…

SAP HCM insufficient authorization, no.skipped personnel nos.可能涉及的场景

导读 授权不充分:HCM中有个权限对象P_ABAP,这个权限对象有个参数coars&#xff0c;如果设置成2&#xff0c;使用逻辑数据库就不会检查任何报表里面的结构化的权限&#xff0c;所以PA30找不到员工主数据&#xff0c;但是报表能查到对应的人&#xff0c;今天要分析的是工资核算结…

Docker、containerd、安全沙箱、社区Kata Containers运行对比

大家看了解决有意义、有帮助记得点赞加关注&#xff01;&#xff01;&#xff01; containerd、安全沙箱和Docker三种运行对比。 本文通过对比三种运行时的实现和使用限制、部署结构&#xff0c;帮助您根据需求场景了解并选择合适的容器运行。 一、容器运行时实现和使用限制…

微服务——不熟与运维

1、你是否有将 Java 微服务部署到容器&#xff08;如 Docker&#xff09;中的经验&#xff1f;请描述一下部署过程和相关注意事项。 部署过程&#xff1a; 编写 Dockerfile&#xff0c;定义基础镜像&#xff08;如 openjdk&#xff09;、应用 JAR 包路径和启动命令。构建镜像…

【JAVA高级篇教学】第五篇:OpenFeign 微服务调用注意事项

在微服务架构中&#xff0c;OpenFeign 是一种常用的 HTTP 客户端工具&#xff0c;用于实现服务之间的调用。它提供了声明式的接口调用方式&#xff0c;大幅简化了开发工作。然而&#xff0c;在实际使用中&#xff0c;需要注意一些细节&#xff0c;尤其是在处理 GET、POST 请求和…

SSH之Struts(一)

1&#xff0c;Struts2框架介绍 Struts2框架是MVC流程框架&#xff0c;适合分层开发。框架应用实现不依赖于Servlet&#xff0c;使用大量的拦截器来处理用户请求&#xff0c;属于无侵入式的设计。 2&#xff0c;Struts2框架的流程原理 1&#xff09;请求先到达Filter中央控制器…

要查询名为 `user` 的表有多少条数据(SELECT COUNT(*) FROM user;)

为什么要使用 * &#xff0c;而不是id&#xff0c;null&#xff0c;字段&#xff0c;1。这篇博客说明了原因&#xff0c;直接点击即可查看&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 要查询名为 user 的表有多少条数据&#xff0c;可以使用以下 SQL 查询语…

[数据结构] LRU Cache | ListMap 实现

目录 1. 什么是 LRU Cache 2. LRU Cache 的实现 3. 代码 概念理解 题目要求 删除一个节点&#xff08;抽出一本书&#xff09; 在链表头添加一个节点&#xff08;把一本书放在最上面&#xff09; 如何快速找到要抽出来的书&#xff1f; 答疑 代码实现 复杂度分析 4…