Spring Boot 2.6.0とSpringfox 3の統合問題
問題の概要
Spring Boot 2.6.0とSpringfox 3を組み合わせて使用する際に、アプリケーションの起動に失敗する問題が発生します。具体的には、documentationPluginsBootstrapper
Beanの起動に失敗し、以下のようなエラーが表示されます:
org.springframework.context.ApplicationContextException:
Failed to start bean 'documentationPluginsBootstrapper';
nested exception is java.lang.NullPointerException:
Cannot invoke "org.springframework.web.servlet.mvc.condition.PatternsRequestCondition.getPatterns()"
because "this.condition" is null
この問題は、Spring Boot 2.6.0で導入された新しいパスマッチング戦略と、Springfoxの互換性の問題によって引き起こされます。
根本原因
Spring Boot 2.6.0では、Spring MVCのデフォルトのパスマッチング戦略がAntPathMatcher
からPathPatternParser
に変更されました。Springfox 3はAntPathMatcher
を前提とした実装になっており、新しいパスマッチング戦略との互換性がないためにこの問題が発生します。
注意
Spring Boot Actuatorを使用している場合、Actuatorは常にPathPatternParser
を使用するため、設定の変更だけでは問題が解決しない場合があります。
解決方法
方法1: パスマッチング戦略の変更(推奨)
application.properties
またはapplication.yml
に以下の設定を追加します:
spring.mvc.pathmatch.matching-strategy=ant_path_matcher
この設定により、Spring MVCが従来のAntPathMatcher
を使用するようになり、Springfoxとの互換性が確保されます。
方法2: Springfoxの代わりにSpringdoc OpenAPIを使用
Springfoxはメンテナンスが活発ではなくなっているため、代替としてSpringdoc OpenAPIの使用を検討することも有用です。
build.gradleへの追加:
implementation "org.springdoc:springdoc-openapi-ui:1.6.4"
メインアプリケーションクラス:
@SpringBootApplication
@OpenAPIDefinition
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
Springdoc OpenAPIはSwagger UIを/swagger-ui/index.html
で提供します。
方法3: Beanのカスタム設定(Actuator使用時)
Spring Boot Actuatorを使用している場合、以下のBean定義を追加する必要があります:
@Bean
public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(
WebEndpointsSupplier webEndpointsSupplier,
ServletEndpointsSupplier servletEndpointsSupplier,
ControllerEndpointsSupplier controllerEndpointsSupplier,
EndpointMediaTypes endpointMediaTypes,
CorsEndpointProperties corsProperties,
WebEndpointProperties webEndpointProperties,
Environment environment) {
List<ExposableEndpoint<?>> allEndpoints = new ArrayList<>();
allEndpoints.addAll(webEndpointsSupplier.getEndpoints());
allEndpoints.addAll(servletEndpointsSupplier.getEndpoints());
allEndpoints.addAll(controllerEndpointsSupplier.getEndpoints());
String basePath = webEndpointProperties.getBasePath();
EndpointMapping endpointMapping = new EndpointMapping(basePath);
boolean shouldRegisterLinksMapping = shouldRegisterLinksMapping(
webEndpointProperties, environment, basePath);
return new WebMvcEndpointHandlerMapping(endpointMapping,
webEndpointsSupplier.getEndpoints(),
endpointMediaTypes,
corsProperties.toCorsConfiguration(),
new EndpointLinksResolver(allEndpoints, basePath),
shouldRegisterLinksMapping, null);
}
private boolean shouldRegisterLinksMapping(WebEndpointProperties webEndpointProperties,
Environment environment, String basePath) {
return webEndpointProperties.getDiscovery().isEnabled() &&
(StringUtils.hasText(basePath) ||
ManagementPortType.get(environment).equals(ManagementPortType.DIFFERENT));
}
方法4: Springfoxのハンドラーマッピングをカスタマイズ
以下のBeanプロセッサーを使用して、Springfoxのハンドラーマッピングをフィルタリングします:
@Bean
public BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {
return new BeanPostProcessor() {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
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");
if (field == null) {
return new ArrayList<>();
}
field.setAccessible(true);
return (List<RequestMappingInfoHandlerMapping>) field.get(bean);
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new IllegalStateException(e);
}
}
};
}
推奨される解決策
- 新しいプロジェクト: Springdoc OpenAPIへの移行を検討してください
- 既存プロジェクト:
spring.mvc.pathmatch.matching-strategy=ant_path_matcher
の設定を追加 - Actuator使用時: 上記のカスタムBean定義を追加
回避策(非推奨)
Spring Bootのバージョンを2.5.xにダウングレードすることで一時的に問題を回避できますが、これは長期的な解決策ではありません。
// build.gradle
plugins {
id 'org.springframework.boot' version '2.5.6' // 2.6.0ではなく2.5.6を使用
}
まとめ
Spring Boot 2.6.0とSpringfox 3の互換性問題は、パスマッチング戦略の変更に起因しています。最も簡単な解決策はパスマッチング戦略を明示的に設定することですが、長期的にはSpringdoc OpenAPIへの移行を検討することをお勧めします。
情報
この問題の詳細や最新の進捗については、SpringfoxのGitHubイシュー#3462を参照してください。