Skip to content

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に以下の設定を追加します:

properties
spring.mvc.pathmatch.matching-strategy=ant_path_matcher

この設定により、Spring MVCが従来のAntPathMatcherを使用するようになり、Springfoxとの互換性が確保されます。

方法2: Springfoxの代わりにSpringdoc OpenAPIを使用

Springfoxはメンテナンスが活発ではなくなっているため、代替としてSpringdoc OpenAPIの使用を検討することも有用です。

build.gradleへの追加:

gradle
implementation "org.springdoc:springdoc-openapi-ui:1.6.4"

メインアプリケーションクラス:

java
@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定義を追加する必要があります:

java
@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のハンドラーマッピングをフィルタリングします:

java
@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);
            }
        }
    };
}

推奨される解決策

  1. 新しいプロジェクト: Springdoc OpenAPIへの移行を検討してください
  2. 既存プロジェクト: spring.mvc.pathmatch.matching-strategy=ant_path_matcherの設定を追加
  3. Actuator使用時: 上記のカスタムBean定義を追加

回避策(非推奨)

Spring Bootのバージョンを2.5.xにダウングレードすることで一時的に問題を回避できますが、これは長期的な解決策ではありません。

gradle
// 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を参照してください。