From acebf7089df3a1d7f11db657b3cf94de2db8b846 Mon Sep 17 00:00:00 2001 From: kdelay Date: Fri, 7 Aug 2026 15:57:17 +0900 Subject: [PATCH] SpringDocWebMvcConfiguration relies on unspecified autoconfiguration ordering. Fixes #3313 --- .../SpringDocDataRestConfiguration.java | 2 + ...SpringDocFunctionCatalogConfiguration.java | 2 + .../SpringDocGroovyConfiguration.java | 2 + .../SpringDocHateoasConfiguration.java | 2 + ...ngDocJacksonKotlinModuleConfiguration.java | 2 + .../SpringDocJavadocConfiguration.java | 2 + .../SpringDocKotlinConfiguration.kt | 2 + .../SpringDocKotlinxConfiguration.java | 2 + .../SpringDocPageableConfiguration.java | 2 + .../SpringDocSecurityConfiguration.java | 2 + .../SpringDocSortConfiguration.java | 2 + .../SpringDocSpecPropertiesConfiguration.java | 2 + .../properties/SpringDocConfigProperties.java | 2 + .../properties/SwaggerUiConfigProperties.java | 2 + .../properties/SwaggerUiOAuthProperties.java | 2 + .../MultipleOpenApiSupportConfiguration.java | 2 + .../SpringDocWebFluxConfiguration.java | 2 + .../webflux/scalar/ScalarConfiguration.java | 2 + .../springdoc/webflux/ui/SwaggerConfig.java | 2 + .../SpringDocWebMvcConfiguration.java | 2 + .../SpringDocAutoConfigurationOrderTest.java | 146 ++++++++++++++++++ .../webmvc/scalar/ScalarConfiguration.java | 2 + .../springdoc/webmvc/ui/SwaggerConfig.java | 2 + 23 files changed, 190 insertions(+) create mode 100644 springdoc-openapi-starter-webmvc-api/src/test/java/org/springdoc/autoconfigure/SpringDocAutoConfigurationOrderTest.java diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocDataRestConfiguration.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocDataRestConfiguration.java index a1240af1d..61e0b84e4 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocDataRestConfiguration.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocDataRestConfiguration.java @@ -47,6 +47,7 @@ import org.springdoc.core.utils.SpringDocDataRestUtils; import tools.jackson.databind.ObjectMapper; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; @@ -77,6 +78,7 @@ */ @Lazy(false) @Configuration(proxyBeanMethods = false) +@AutoConfigureAfter(SpringDocConfiguration.class) @ConditionalOnExpression("${springdoc.api-docs.enabled:true} and ${springdoc.enable-data-rest:true}") @ConditionalOnClass(RepositoryRestConfiguration.class) @ConditionalOnWebApplication diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocFunctionCatalogConfiguration.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocFunctionCatalogConfiguration.java index cbcca5843..c2c1e13db 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocFunctionCatalogConfiguration.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocFunctionCatalogConfiguration.java @@ -32,6 +32,7 @@ import org.springdoc.core.providers.CloudFunctionProvider; import org.springdoc.core.providers.SpringCloudFunctionProvider; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; @@ -50,6 +51,7 @@ */ @Lazy(false) @Configuration(proxyBeanMethods = false) +@AutoConfigureAfter(SpringDocConfiguration.class) @ConditionalOnExpression("${springdoc.api-docs.enabled:true} and ${springdoc.show-spring-cloud-functions:true}") @ConditionalOnClass(FunctionEndpointInitializer.class) @ConditionalOnWebApplication diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocGroovyConfiguration.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocGroovyConfiguration.java index 397c96e22..abcc9d7df 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocGroovyConfiguration.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocGroovyConfiguration.java @@ -32,6 +32,7 @@ import org.springdoc.core.providers.ObjectMapperProvider; import org.springdoc.core.utils.SpringDocUtils; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; @@ -47,6 +48,7 @@ */ @Lazy(false) @Configuration(proxyBeanMethods = false) +@AutoConfigureAfter(SpringDocConfiguration.class) @ConditionalOnExpression("${springdoc.api-docs.enabled:true} and ${springdoc.enable-groovy:true}") @ConditionalOnClass(MetaClass.class) @ConditionalOnWebApplication diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocHateoasConfiguration.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocHateoasConfiguration.java index 313c118d7..7663e6328 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocHateoasConfiguration.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocHateoasConfiguration.java @@ -37,6 +37,7 @@ import org.springdoc.core.providers.ObjectMapperProvider; import org.springdoc.core.utils.Constants; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; @@ -55,6 +56,7 @@ */ @Lazy(false) @Configuration(proxyBeanMethods = false) +@AutoConfigureAfter(SpringDocConfiguration.class) @ConditionalOnExpression("${springdoc.api-docs.enabled:true} and ${springdoc.enable-hateoas:true}") @ConditionalOnClass({LinkRelationProvider.class, HateoasProperties.class}) @ConditionalOnWebApplication diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocJacksonKotlinModuleConfiguration.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocJacksonKotlinModuleConfiguration.java index 23255b269..cedd8bb06 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocJacksonKotlinModuleConfiguration.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocJacksonKotlinModuleConfiguration.java @@ -30,6 +30,7 @@ import org.springdoc.core.properties.SpringDocConfigProperties; import org.springdoc.core.providers.ObjectMapperProvider; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; @@ -46,6 +47,7 @@ */ @Lazy(false) @Configuration(proxyBeanMethods = false) +@AutoConfigureAfter(SpringDocConfiguration.class) @ConditionalOnClass(KotlinModule.class) @ConditionalOnExpression("${springdoc.api-docs.enabled:true} and ${springdoc.enable-kotlin:true}") @ConditionalOnWebApplication diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocJavadocConfiguration.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocJavadocConfiguration.java index 09650eb42..8a3300b59 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocJavadocConfiguration.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocJavadocConfiguration.java @@ -32,6 +32,7 @@ import org.springdoc.core.providers.ObjectMapperProvider; import org.springdoc.core.providers.SpringDocJavadocProvider; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; @@ -50,6 +51,7 @@ */ @Lazy(false) @Configuration(proxyBeanMethods = false) +@AutoConfigureAfter(SpringDocConfiguration.class) @ConditionalOnExpression("${springdoc.api-docs.enabled:true} and ${springdoc.enable-javadoc:true}") @ConditionalOnClass(CommentFormatter.class) @ConditionalOnWebApplication diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocKotlinConfiguration.kt b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocKotlinConfiguration.kt index 1a33e2a20..dc15678f2 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocKotlinConfiguration.kt +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocKotlinConfiguration.kt @@ -33,6 +33,7 @@ import org.springdoc.core.providers.ObjectMapperProvider import org.springdoc.core.utils.Constants import org.springdoc.core.utils.SpringDocKotlinUtils import org.springdoc.core.utils.SpringDocUtils +import org.springframework.boot.autoconfigure.AutoConfigureAfter import org.springframework.boot.autoconfigure.condition.ConditionalOnBean import org.springframework.boot.autoconfigure.condition.ConditionalOnClass import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression @@ -50,6 +51,7 @@ import kotlin.coroutines.Continuation */ @Lazy(false) @Configuration(proxyBeanMethods = false) +@AutoConfigureAfter(SpringDocConfiguration::class) @ConditionalOnProperty(name = [Constants.SPRINGDOC_ENABLED], matchIfMissing = true) @ConditionalOnExpression("\${springdoc.api-docs.enabled:true} and \${springdoc.enable-kotlin:true}") @ConditionalOnClass(Continuation::class) diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocKotlinxConfiguration.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocKotlinxConfiguration.java index b2385fc25..8bd6ee108 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocKotlinxConfiguration.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocKotlinxConfiguration.java @@ -28,6 +28,7 @@ import kotlinx.coroutines.flow.Flow; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; @@ -44,6 +45,7 @@ */ @Lazy(false) @Configuration(proxyBeanMethods = false) +@AutoConfigureAfter(SpringDocConfiguration.class) @ConditionalOnExpression("${springdoc.api-docs.enabled:true} and ${springdoc.enable-kotlin:true}") @ConditionalOnClass(Flow.class) @ConditionalOnWebApplication diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocPageableConfiguration.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocPageableConfiguration.java index 0872b66f7..d6c7b15fd 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocPageableConfiguration.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocPageableConfiguration.java @@ -35,6 +35,7 @@ import org.springdoc.core.providers.RepositoryRestConfigurationProvider; import org.springdoc.core.providers.SpringDataWebPropertiesProvider; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -59,6 +60,7 @@ */ @Lazy(false) @Configuration(proxyBeanMethods = false) +@AutoConfigureAfter(SpringDocConfiguration.class) @ConditionalOnProperty(name = SPRINGDOC_ENABLED, matchIfMissing = true) @ConditionalOnClass(Pageable.class) @ConditionalOnWebApplication diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocSecurityConfiguration.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocSecurityConfiguration.java index 37cde1ebb..25ee43900 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocSecurityConfiguration.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocSecurityConfiguration.java @@ -46,6 +46,7 @@ import org.springdoc.core.customizers.GlobalOpenApiCustomizer; import org.springdoc.core.customizers.OpenApiCustomizer; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; @@ -81,6 +82,7 @@ */ @Lazy(false) @Configuration(proxyBeanMethods = false) +@AutoConfigureAfter(SpringDocConfiguration.class) @ConditionalOnExpression("${springdoc.api-docs.enabled:true} and ${springdoc.enable-spring-security:true}") @ConditionalOnClass(SecurityFilterChain.class) @ConditionalOnWebApplication diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocSortConfiguration.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocSortConfiguration.java index 1d0dfd76b..3e3d8cb55 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocSortConfiguration.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocSortConfiguration.java @@ -34,6 +34,7 @@ import org.springdoc.core.providers.RepositoryRestConfigurationProvider; import org.springdoc.core.providers.SpringDataWebPropertiesProvider; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -55,6 +56,7 @@ */ @Lazy(false) @Configuration(proxyBeanMethods = false) +@AutoConfigureAfter(SpringDocConfiguration.class) @ConditionalOnProperty(name = SPRINGDOC_ENABLED, matchIfMissing = true) @ConditionalOnClass(Sort.class) @ConditionalOnWebApplication diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocSpecPropertiesConfiguration.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocSpecPropertiesConfiguration.java index c14a2e18d..c7a39fc93 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocSpecPropertiesConfiguration.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocSpecPropertiesConfiguration.java @@ -36,6 +36,7 @@ import org.springdoc.core.properties.SpringDocConfigProperties.GroupConfig; import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; @@ -51,6 +52,7 @@ */ @Lazy(false) @Configuration(proxyBeanMethods = false) +@AutoConfigureAfter(SpringDocConfiguration.class) @ConditionalOnBean(SpringDocConfiguration.class) @Conditional(SpecPropertiesCondition.class) public class SpringDocSpecPropertiesConfiguration { diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/properties/SpringDocConfigProperties.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/properties/SpringDocConfigProperties.java index 1bec1ad18..549c1e772 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/properties/SpringDocConfigProperties.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/properties/SpringDocConfigProperties.java @@ -38,6 +38,7 @@ import org.springdoc.core.utils.Constants; import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -55,6 +56,7 @@ */ @Lazy(false) @Configuration(proxyBeanMethods = false) +@AutoConfigureAfter(SpringDocConfiguration.class) @ConfigurationProperties(prefix = Constants.SPRINGDOC_PREFIX) @ConditionalOnProperty(name = SPRINGDOC_ENABLED, matchIfMissing = true) @ConditionalOnBean(SpringDocConfiguration.class) diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/properties/SwaggerUiConfigProperties.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/properties/SwaggerUiConfigProperties.java index 8a1a31fe7..bb3ffaf90 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/properties/SwaggerUiConfigProperties.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/properties/SwaggerUiConfigProperties.java @@ -37,6 +37,7 @@ import org.springdoc.core.utils.Constants; import org.springframework.beans.factory.InitializingBean; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -58,6 +59,7 @@ */ @Lazy(false) @Configuration(proxyBeanMethods = false) +@AutoConfigureAfter(SpringDocConfiguration.class) @ConfigurationProperties(prefix = SPRINGDOC_SWAGGER_PREFIX) @ConditionalOnProperty(name = SPRINGDOC_SWAGGER_UI_ENABLED, matchIfMissing = true) @ConditionalOnBean(SpringDocConfiguration.class) diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/properties/SwaggerUiOAuthProperties.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/properties/SwaggerUiOAuthProperties.java index 579ed85ec..fb86d311f 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/properties/SwaggerUiOAuthProperties.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/properties/SwaggerUiOAuthProperties.java @@ -34,6 +34,7 @@ import org.springdoc.core.configuration.SpringDocConfiguration; import org.springdoc.core.utils.SpringDocPropertiesUtils; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -52,6 +53,7 @@ */ @Lazy(false) @Configuration(proxyBeanMethods = false) +@AutoConfigureAfter(SpringDocConfiguration.class) @ConfigurationProperties(prefix = "springdoc.swagger-ui.oauth") @ConditionalOnProperty(name = SPRINGDOC_SWAGGER_UI_ENABLED, matchIfMissing = true) @ConditionalOnBean(SpringDocConfiguration.class) diff --git a/springdoc-openapi-starter-webflux-api/src/main/java/org/springdoc/webflux/core/configuration/MultipleOpenApiSupportConfiguration.java b/springdoc-openapi-starter-webflux-api/src/main/java/org/springdoc/webflux/core/configuration/MultipleOpenApiSupportConfiguration.java index c6209b0f6..5cc44aba7 100644 --- a/springdoc-openapi-starter-webflux-api/src/main/java/org/springdoc/webflux/core/configuration/MultipleOpenApiSupportConfiguration.java +++ b/springdoc-openapi-starter-webflux-api/src/main/java/org/springdoc/webflux/core/configuration/MultipleOpenApiSupportConfiguration.java @@ -44,6 +44,7 @@ import org.springframework.beans.factory.ObjectFactory; import org.springframework.boot.actuate.autoconfigure.web.server.ConditionalOnManagementPort; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementPortType; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -66,6 +67,7 @@ */ @Lazy(false) @Configuration(proxyBeanMethods = false) +@AutoConfigureAfter(SpringDocConfiguration.class) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) @ConditionalOnProperty(name = SPRINGDOC_ENABLED, matchIfMissing = true) @Conditional(MultipleOpenApiSupportCondition.class) diff --git a/springdoc-openapi-starter-webflux-api/src/main/java/org/springdoc/webflux/core/configuration/SpringDocWebFluxConfiguration.java b/springdoc-openapi-starter-webflux-api/src/main/java/org/springdoc/webflux/core/configuration/SpringDocWebFluxConfiguration.java index 5fe043f16..9d10d791e 100644 --- a/springdoc-openapi-starter-webflux-api/src/main/java/org/springdoc/webflux/core/configuration/SpringDocWebFluxConfiguration.java +++ b/springdoc-openapi-starter-webflux-api/src/main/java/org/springdoc/webflux/core/configuration/SpringDocWebFluxConfiguration.java @@ -61,6 +61,7 @@ import org.springframework.boot.actuate.autoconfigure.web.server.ConditionalOnManagementPort; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementPortType; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerProperties; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; @@ -83,6 +84,7 @@ */ @Lazy(false) @Configuration(proxyBeanMethods = false) +@AutoConfigureAfter(SpringDocConfiguration.class) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) @ConditionalOnProperty(name = SPRINGDOC_ENABLED, matchIfMissing = true) @ConditionalOnBean(SpringDocConfiguration.class) diff --git a/springdoc-openapi-starter-webflux-scalar/src/main/java/org/springdoc/webflux/scalar/ScalarConfiguration.java b/springdoc-openapi-starter-webflux-scalar/src/main/java/org/springdoc/webflux/scalar/ScalarConfiguration.java index 53e5465c2..8a9cc7b3d 100644 --- a/springdoc-openapi-starter-webflux-scalar/src/main/java/org/springdoc/webflux/scalar/ScalarConfiguration.java +++ b/springdoc-openapi-starter-webflux-scalar/src/main/java/org/springdoc/webflux/scalar/ScalarConfiguration.java @@ -34,6 +34,7 @@ import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties; import org.springframework.boot.actuate.autoconfigure.web.server.ConditionalOnManagementPort; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementPortType; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -58,6 +59,7 @@ */ @Lazy(false) @Configuration(proxyBeanMethods = false) +@AutoConfigureAfter(SpringDocConfiguration.class) @ConditionalOnProperty(name = SCALAR_ENABLED, matchIfMissing = true) @ConditionalOnWebApplication(type = Type.REACTIVE) @ConditionalOnBean(SpringDocConfiguration.class) diff --git a/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerConfig.java b/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerConfig.java index 143f3335a..1fdabcb7d 100644 --- a/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerConfig.java +++ b/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerConfig.java @@ -41,6 +41,7 @@ import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties; import org.springframework.boot.actuate.autoconfigure.web.server.ConditionalOnManagementPort; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementPortType; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -69,6 +70,7 @@ */ @Lazy(false) @Configuration(proxyBeanMethods = false) +@AutoConfigureAfter(SpringDocConfiguration.class) @ConditionalOnProperty(name = SPRINGDOC_SWAGGER_UI_ENABLED, matchIfMissing = true) @ConditionalOnWebApplication(type = Type.REACTIVE) @ConditionalOnBean(SpringDocConfiguration.class) diff --git a/springdoc-openapi-starter-webmvc-api/src/main/java/org/springdoc/webmvc/core/configuration/SpringDocWebMvcConfiguration.java b/springdoc-openapi-starter-webmvc-api/src/main/java/org/springdoc/webmvc/core/configuration/SpringDocWebMvcConfiguration.java index d311113ed..68ebb87b9 100644 --- a/springdoc-openapi-starter-webmvc-api/src/main/java/org/springdoc/webmvc/core/configuration/SpringDocWebMvcConfiguration.java +++ b/springdoc-openapi-starter-webmvc-api/src/main/java/org/springdoc/webmvc/core/configuration/SpringDocWebMvcConfiguration.java @@ -62,6 +62,7 @@ import org.springframework.boot.actuate.autoconfigure.web.server.ConditionalOnManagementPort; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementPortType; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerProperties; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; @@ -88,6 +89,7 @@ */ @Lazy(false) @Configuration(proxyBeanMethods = false) +@AutoConfigureAfter(SpringDocConfiguration.class) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) @ConditionalOnProperty(name = SPRINGDOC_ENABLED, matchIfMissing = true) @ConditionalOnBean(SpringDocConfiguration.class) diff --git a/springdoc-openapi-starter-webmvc-api/src/test/java/org/springdoc/autoconfigure/SpringDocAutoConfigurationOrderTest.java b/springdoc-openapi-starter-webmvc-api/src/test/java/org/springdoc/autoconfigure/SpringDocAutoConfigurationOrderTest.java new file mode 100644 index 000000000..0d23ff8b4 --- /dev/null +++ b/springdoc-openapi-starter-webmvc-api/src/test/java/org/springdoc/autoconfigure/SpringDocAutoConfigurationOrderTest.java @@ -0,0 +1,146 @@ +/* + * + * * + * * * + * * * * + * * * * * Copyright 2019-2026 the original author or authors. + * * * * * + * * * * * Licensed under the Apache License, Version 2.0 (the "License"); + * * * * * you may not use this file except in compliance with the License. + * * * * * You may obtain a copy of the License at + * * * * * + * * * * * https://www.apache.org/licenses/LICENSE-2.0 + * * * * * + * * * * * Unless required by applicable law or agreed to in writing, software + * * * * * distributed under the License is distributed on an "AS IS" BASIS, + * * * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * * * * See the License for the specific language governing permissions and + * * * * * limitations under the License. + * * * * + * * * + * * + * + */ + +package org.springdoc.autoconfigure; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Enumeration; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.junit.jupiter.api.Test; +import org.springdoc.core.configuration.SpringDocConfiguration; + +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.core.type.classreading.MetadataReaderFactory; +import org.springframework.core.type.classreading.SimpleMetadataReaderFactory; +import org.springframework.util.StringUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * An auto-configuration that is only registered when {@link SpringDocConfiguration} already + * contributed its beans has to declare that order, otherwise the {@code @ConditionalOnBean} match + * depends on where the class name happens to sort in Spring Boot's auto-configuration sort. A + * third-party auto-configuration that sorts earlier and orders itself around a springdoc + * auto-configuration is then enough to move that class ahead of {@link SpringDocConfiguration}, + * which silently drops it (gh-3313). + * + * @author kdelay + */ +class SpringDocAutoConfigurationOrderTest { + + private static final String IMPORTS_RESOURCE = "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports"; + + private final MetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory(); + + @Test + void auto_configurations_conditional_on_spring_doc_configuration_declare_their_order() throws IOException { + List gatedWithoutDeclaredOrder = new ArrayList<>(); + for (String candidate : autoConfigurationImports()) { + AnnotationMetadata metadata = annotationMetadata(candidate); + if (isConditionalOnSpringDocConfiguration(metadata) && !declaresOrderAfterSpringDocConfiguration(metadata)) { + gatedWithoutDeclaredOrder.add(candidate); + } + } + assertThat(gatedWithoutDeclaredOrder).isEmpty(); + } + + private Set autoConfigurationImports() throws IOException { + Set candidates = new LinkedHashSet<>(); + Enumeration resources = getClass().getClassLoader().getResources(IMPORTS_RESOURCE); + while (resources.hasMoreElements()) { + try (InputStream inputStream = resources.nextElement().openStream()) { + String content = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); + for (String line : content.split("\n")) { + String candidate = line.trim(); + if (StringUtils.hasText(candidate) && candidate.startsWith("org.springdoc.")) { + candidates.add(candidate); + } + } + } + } + assertThat(candidates).isNotEmpty(); + return candidates; + } + + /** + * Reads the annotations without loading the class, because several of these + * auto-configurations reference optional types that are absent from this module's classpath. + * @param candidate the auto-configuration class name as written in the imports file + * @return the annotation metadata + */ + private AnnotationMetadata annotationMetadata(String candidate) throws IOException { + try { + return this.metadataReaderFactory.getMetadataReader(candidate).getAnnotationMetadata(); + } + catch (IOException ex) { + // nested classes are written with a dot separator in the imports file + int lastDot = candidate.lastIndexOf('.'); + String nested = candidate.substring(0, lastDot) + '$' + candidate.substring(lastDot + 1); + return this.metadataReaderFactory.getMetadataReader(nested).getAnnotationMetadata(); + } + } + + private boolean isConditionalOnSpringDocConfiguration(AnnotationMetadata metadata) { + return referencesSpringDocConfiguration(metadata.getAnnotationAttributes(ConditionalOnBean.class.getName(), true), + "value", "name"); + } + + private boolean declaresOrderAfterSpringDocConfiguration(AnnotationMetadata metadata) { + return referencesSpringDocConfiguration(metadata.getAnnotationAttributes(AutoConfigureAfter.class.getName(), true), + "value", "name") + || referencesSpringDocConfiguration(metadata.getAnnotationAttributes(AutoConfiguration.class.getName(), true), + "after", "afterName"); + } + + private boolean referencesSpringDocConfiguration(Map attributes, String... attributeNames) { + if (attributes == null) { + return false; + } + for (String attributeName : attributeNames) { + Object value = attributes.get(attributeName); + if (value instanceof String[] values + && Arrays.asList(values).contains(SpringDocConfiguration.class.getName())) { + return true; + } + if (value instanceof Collection values && values.contains(SpringDocConfiguration.class.getName())) { + return true; + } + } + return false; + } + +} diff --git a/springdoc-openapi-starter-webmvc-scalar/src/main/java/org/springdoc/webmvc/scalar/ScalarConfiguration.java b/springdoc-openapi-starter-webmvc-scalar/src/main/java/org/springdoc/webmvc/scalar/ScalarConfiguration.java index c44a3279b..7139e3d56 100644 --- a/springdoc-openapi-starter-webmvc-scalar/src/main/java/org/springdoc/webmvc/scalar/ScalarConfiguration.java +++ b/springdoc-openapi-starter-webmvc-scalar/src/main/java/org/springdoc/webmvc/scalar/ScalarConfiguration.java @@ -34,6 +34,7 @@ import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties; import org.springframework.boot.actuate.autoconfigure.web.server.ConditionalOnManagementPort; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementPortType; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -59,6 +60,7 @@ */ @Lazy(false) @Configuration(proxyBeanMethods = false) +@AutoConfigureAfter(SpringDocConfiguration.class) @ConditionalOnProperty(name = SCALAR_ENABLED, matchIfMissing = true) @ConditionalOnWebApplication(type = Type.SERVLET) @ConditionalOnBean(SpringDocConfiguration.class) diff --git a/springdoc-openapi-starter-webmvc-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerConfig.java b/springdoc-openapi-starter-webmvc-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerConfig.java index 06cbe9d57..a2dbc8814 100644 --- a/springdoc-openapi-starter-webmvc-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerConfig.java +++ b/springdoc-openapi-starter-webmvc-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerConfig.java @@ -41,6 +41,7 @@ import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties; import org.springframework.boot.actuate.autoconfigure.web.server.ConditionalOnManagementPort; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementPortType; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -68,6 +69,7 @@ */ @Lazy(false) @Configuration(proxyBeanMethods = false) +@AutoConfigureAfter(SpringDocConfiguration.class) @ConditionalOnProperty(name = SPRINGDOC_SWAGGER_UI_ENABLED, matchIfMissing = true) @ConditionalOnWebApplication(type = Type.SERVLET) @ConditionalOnBean(SpringDocConfiguration.class)