Order auto-configured ProblemDetailsExceptionHandler beans

Add `@Order(0)` to the WebMVC and Webflux
`ProblemDetailsExceptionHandler` beans. This makes it easier to create
custom `@ControllerAdvice` beans that must be ordered before or after
the `ProblemDetailsExceptionHandler`.

See gh-36288
This commit is contained in:
Maurice Zeijen 2023-07-10 10:59:06 +02:00 committed by Stephane Nicoll
parent e677eb7759
commit 9955ee7e8a
4 changed files with 81 additions and 0 deletions

View File

@ -357,6 +357,7 @@ public class WebFluxAutoConfiguration {
@Bean
@ConditionalOnMissingBean(ResponseEntityExceptionHandler.class)
@Order(0)
ProblemDetailsExceptionHandler problemDetailsExceptionHandler() {
return new ProblemDetailsExceptionHandler();
}

View File

@ -662,6 +662,7 @@ public class WebMvcAutoConfiguration {
@Bean
@ConditionalOnMissingBean(ResponseEntityExceptionHandler.class)
@Order(0)
ProblemDetailsExceptionHandler problemDetailsExceptionHandler() {
return new ProblemDetailsExceptionHandler();
}

View File

@ -31,6 +31,7 @@ import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import jakarta.validation.ValidatorFactory;
import org.aspectj.lang.JoinPoint;
@ -80,6 +81,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 +673,24 @@ class WebFluxAutoConfigurationTests {
.hasSingleBean(CustomExceptionHandler.class));
}
@Test
void problemDetailsIsOrderedBetweenLowestAndHighestOrderedControllerHandlers() {
this.contextRunner.withPropertyValues("spring.webflux.problemdetails.enabled:true")
.withUserConfiguration(OrderedControllerAdviceBeansConfiguration.class)
.run((context) -> {
List<Class<?>> controllerAdviceClasses = ControllerAdviceBean.findAnnotatedBeans(context)
.stream()
.map(ControllerAdviceBean::getBeanType)
.collect(Collectors.toList());
assertThat(controllerAdviceClasses).containsExactly(
OrderedControllerAdviceBeansConfiguration.HighestOrderedControllerAdvice.class,
ProblemDetailsExceptionHandler.class,
OrderedControllerAdviceBeansConfiguration.LowestOrderedControllerAdvice.class);
});
}
@Test
void asyncTaskExecutorWithApplicationTaskExecutor() {
this.contextRunner.withConfiguration(AutoConfigurations.of(TaskExecutionAutoConfiguration.class))
@ -1016,6 +1036,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 {

View File

@ -32,6 +32,7 @@ import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@ -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,22 @@ class WebMvcAutoConfigurationTests {
.hasSingleBean(CustomExceptionHandler.class));
}
@Test
void problemDetailsIsOrderedBetweenLowestAndHighestOrderedControllerHandlers() {
this.contextRunner.withPropertyValues("spring.mvc.problemdetails.enabled:true")
.withUserConfiguration(OrderedControllerAdviceBeansConfiguration.class)
.run((context) -> {
List<Class<?>> controllerAdviceClasses = ControllerAdviceBean.findAnnotatedBeans(context)
.stream()
.map(ControllerAdviceBean::getBeanType)
.collect(Collectors.toList());
assertThat(controllerAdviceClasses).containsExactly(HighestOrderedControllerAdvice.class,
ProblemDetailsExceptionHandler.class, LowestOrderedControllerAdvice.class);
});
}
private void assertResourceHttpRequestHandler(AssertableWebApplicationContext context,
Consumer<ResourceHttpRequestHandler> handlerConsumer) {
Map<String, Object> handlerMap = getHandlerMap(context.getBean("resourceHandlerMapping", HandlerMapping.class));
@ -1496,6 +1518,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 {