diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration.java index c3d77d94fa..e9ebef3624 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration.java @@ -357,6 +357,7 @@ public class WebFluxAutoConfiguration { @Bean @ConditionalOnMissingBean(ResponseEntityExceptionHandler.class) + @Order(0) ProblemDetailsExceptionHandler problemDetailsExceptionHandler() { return new ProblemDetailsExceptionHandler(); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java index f3e8097248..a6df6386d7 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java @@ -662,6 +662,7 @@ public class WebMvcAutoConfiguration { @Bean @ConditionalOnMissingBean(ResponseEntityExceptionHandler.class) + @Order(0) ProblemDetailsExceptionHandler problemDetailsExceptionHandler() { return new ProblemDetailsExceptionHandler(); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationTests.java index 65be8c0f79..b1753c4142 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationTests.java @@ -37,6 +37,7 @@ import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; import org.assertj.core.api.Assertions; +import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; @@ -49,6 +50,8 @@ import org.springframework.boot.autoconfigure.validation.ValidationAutoConfigura import org.springframework.boot.autoconfigure.validation.ValidatorAdapter; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration.WebFluxConfig; +import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfigurationTests.OrderedControllerAdviceBeansConfiguration.HighestOrderedControllerAdvice; +import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfigurationTests.OrderedControllerAdviceBeansConfiguration.LowestOrderedControllerAdvice; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ContextConsumer; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; @@ -80,6 +83,7 @@ import org.springframework.validation.Validator; import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.filter.reactive.HiddenHttpMethodFilter; +import org.springframework.web.method.ControllerAdviceBean; import org.springframework.web.reactive.HandlerMapping; import org.springframework.web.reactive.accept.RequestedContentTypeResolver; import org.springframework.web.reactive.config.BlockingExecutionConfigurer; @@ -671,6 +675,17 @@ class WebFluxAutoConfigurationTests { .hasSingleBean(CustomExceptionHandler.class)); } + @Test + void problemDetailsExceptionHandlerIsOrderedAt0() { + this.contextRunner.withPropertyValues("spring.webflux.problemdetails.enabled:true") + .withUserConfiguration(OrderedControllerAdviceBeansConfiguration.class) + .run((context) -> assertThat( + ControllerAdviceBean.findAnnotatedBeans(context).stream().map(ControllerAdviceBean::getBeanType)) + .asInstanceOf(InstanceOfAssertFactories.list(Class.class)) + .containsExactly(HighestOrderedControllerAdvice.class, ProblemDetailsExceptionHandler.class, + LowestOrderedControllerAdvice.class)); + } + @Test void asyncTaskExecutorWithApplicationTaskExecutor() { this.contextRunner.withConfiguration(AutoConfigurations.of(TaskExecutionAutoConfiguration.class)) @@ -1016,6 +1031,25 @@ class WebFluxAutoConfigurationTests { } + @Configuration(proxyBeanMethods = false) + @Import({ OrderedControllerAdviceBeansConfiguration.LowestOrderedControllerAdvice.class, + OrderedControllerAdviceBeansConfiguration.HighestOrderedControllerAdvice.class }) + static class OrderedControllerAdviceBeansConfiguration { + + @ControllerAdvice + @Order + static class LowestOrderedControllerAdvice { + + } + + @ControllerAdvice + @Order(Ordered.HIGHEST_PRECEDENCE) + static class HighestOrderedControllerAdvice { + + } + + } + @Aspect static class ExceptionHandlerInterceptor { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java index c2ebbb6d15..1ecc2395be 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java @@ -39,6 +39,7 @@ import jakarta.validation.ValidatorFactory; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; +import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.Test; import org.springframework.aop.support.AopUtils; @@ -51,6 +52,8 @@ import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguratio import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration; import org.springframework.boot.autoconfigure.validation.ValidatorAdapter; import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter; +import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfigurationTests.OrderedControllerAdviceBeansConfiguration.HighestOrderedControllerAdvice; +import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfigurationTests.OrderedControllerAdviceBeansConfiguration.LowestOrderedControllerAdvice; import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext; import org.springframework.boot.test.context.runner.ContextConsumer; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; @@ -65,6 +68,8 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; import org.springframework.core.convert.ConversionService; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; @@ -89,6 +94,7 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.FormContentFilter; import org.springframework.web.filter.HiddenHttpMethodFilter; import org.springframework.web.filter.RequestContextFilter; +import org.springframework.web.method.ControllerAdviceBean; import org.springframework.web.servlet.DispatcherServlet; import org.springframework.web.servlet.FlashMap; import org.springframework.web.servlet.FlashMapManager; @@ -972,6 +978,17 @@ class WebMvcAutoConfigurationTests { .hasSingleBean(CustomExceptionHandler.class)); } + @Test + void problemDetailsExceptionHandlerIsOrderedAt0() { + this.contextRunner.withPropertyValues("spring.mvc.problemdetails.enabled:true") + .withUserConfiguration(OrderedControllerAdviceBeansConfiguration.class) + .run((context) -> assertThat( + ControllerAdviceBean.findAnnotatedBeans(context).stream().map(ControllerAdviceBean::getBeanType)) + .asInstanceOf(InstanceOfAssertFactories.list(Class.class)) + .containsExactly(HighestOrderedControllerAdvice.class, ProblemDetailsExceptionHandler.class, + OrderedControllerAdviceBeansConfiguration.LowestOrderedControllerAdvice.class)); + } + private void assertResourceHttpRequestHandler(AssertableWebApplicationContext context, Consumer handlerConsumer) { Map handlerMap = getHandlerMap(context.getBean("resourceHandlerMapping", HandlerMapping.class)); @@ -1496,6 +1513,24 @@ class WebMvcAutoConfigurationTests { } + @Configuration(proxyBeanMethods = false) + @Import({ LowestOrderedControllerAdvice.class, HighestOrderedControllerAdvice.class }) + static class OrderedControllerAdviceBeansConfiguration { + + @ControllerAdvice + @Order + static class LowestOrderedControllerAdvice { + + } + + @ControllerAdvice + @Order(Ordered.HIGHEST_PRECEDENCE) + static class HighestOrderedControllerAdvice { + + } + + } + @ControllerAdvice static class CustomExceptionHandler extends ResponseEntityExceptionHandler {