Support Content Negotiation with @ExceptionHandler
Prior to this commit, `@ExceptionHandler` annotated controller methods
could be mapped using the exception type declaration as an annotation
attribute, or as a method parameter.
While such methods support a wide variety of method arguments and return
types, it was not possible to declare the same exception type on
different methods (in the same controller/controller advice).
This commit adds a new `produces` attribute on `@ExceptionHandler`; with
that, applications can vary the HTTP response depending on the exception
type and the requested content-type by the client:
```
@ExceptionHandler(produces = "application/json")
public ResponseEntity<ErrorMessage> handleJson(IllegalArgumentException exc) {
return ResponseEntity.badRequest().body(new ErrorMessage(exc.getMessage(), 42));
}
@ExceptionHandler(produces = "text/html")
public String handle(IllegalArgumentException exc, Model model) {
model.addAttribute("error", new ErrorMessage(exc.getMessage(), 42));
return "errorView";
}
```
This commit implements support in both Spring MVC and Spring WebFlux.
Closes gh-31936
This commit is contained in:
@@ -283,12 +283,14 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware {
|
||||
@Qualifier("webFluxAdapterRegistry") ReactiveAdapterRegistry reactiveAdapterRegistry,
|
||||
ServerCodecConfigurer serverCodecConfigurer,
|
||||
@Qualifier("webFluxConversionService") FormattingConversionService conversionService,
|
||||
@Qualifier("webFluxContentTypeResolver") RequestedContentTypeResolver contentTypeResolver,
|
||||
@Qualifier("webFluxValidator") Validator validator) {
|
||||
|
||||
RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
|
||||
adapter.setMessageReaders(serverCodecConfigurer.getReaders());
|
||||
adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer(conversionService, validator));
|
||||
adapter.setReactiveAdapterRegistry(reactiveAdapterRegistry);
|
||||
adapter.setContentTypeResolver(contentTypeResolver);
|
||||
|
||||
BlockingExecutionConfigurer executorConfigurer = getBlockingExecutionConfigurer();
|
||||
if (executorConfigurer.getExecutor() != null) {
|
||||
|
||||
@@ -38,6 +38,7 @@ import org.springframework.core.MethodIntrospector;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ReactiveAdapterRegistry;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.HttpMessageReader;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -50,12 +51,16 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.support.WebBindingInitializer;
|
||||
import org.springframework.web.method.ControllerAdviceBean;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.method.annotation.ExceptionHandlerMappingInfo;
|
||||
import org.springframework.web.method.annotation.ExceptionHandlerMethodResolver;
|
||||
import org.springframework.web.method.annotation.HandlerMethodValidator;
|
||||
import org.springframework.web.reactive.HandlerMapping;
|
||||
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
|
||||
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.reactive.result.method.InvocableHandlerMethod;
|
||||
import org.springframework.web.reactive.result.method.SyncHandlerMethodArgumentResolver;
|
||||
import org.springframework.web.reactive.result.method.SyncInvocableHandlerMethod;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
/**
|
||||
* Package-private class to assist {@link RequestMappingHandlerAdapter} with
|
||||
@@ -104,6 +109,8 @@ class ControllerMethodResolver {
|
||||
|
||||
private final ReactiveAdapterRegistry reactiveAdapterRegistry;
|
||||
|
||||
private final RequestedContentTypeResolver contentTypeResolver;
|
||||
|
||||
@Nullable
|
||||
private final Scheduler invocationScheduler;
|
||||
|
||||
@@ -129,16 +136,17 @@ class ControllerMethodResolver {
|
||||
private final Map<Class<?>, SessionAttributesHandler> sessionAttributesHandlerCache = new ConcurrentHashMap<>(64);
|
||||
|
||||
|
||||
|
||||
ControllerMethodResolver(
|
||||
ArgumentResolverConfigurer customResolvers, ReactiveAdapterRegistry adapterRegistry,
|
||||
ConfigurableApplicationContext context, List<HttpMessageReader<?>> readers,
|
||||
@Nullable WebBindingInitializer webBindingInitializer,
|
||||
@Nullable Scheduler invocationScheduler,
|
||||
@Nullable Predicate<? super HandlerMethod> blockingMethodPredicate) {
|
||||
ConfigurableApplicationContext context, RequestedContentTypeResolver contentTypeResolver,
|
||||
List<HttpMessageReader<?>> readers, @Nullable WebBindingInitializer webBindingInitializer,
|
||||
@Nullable Scheduler invocationScheduler, @Nullable Predicate<? super HandlerMethod> blockingMethodPredicate) {
|
||||
|
||||
Assert.notNull(customResolvers, "ArgumentResolverConfigurer is required");
|
||||
Assert.notNull(adapterRegistry, "ReactiveAdapterRegistry is required");
|
||||
Assert.notNull(context, "ApplicationContext is required");
|
||||
Assert.notNull(contentTypeResolver, "RequestedContentTypeResolver is required");
|
||||
Assert.notNull(readers, "HttpMessageReader List is required");
|
||||
|
||||
this.initBinderResolvers = initBinderResolvers(customResolvers, adapterRegistry, context);
|
||||
@@ -146,6 +154,7 @@ class ControllerMethodResolver {
|
||||
this.requestMappingResolvers = requestMappingResolvers(customResolvers, adapterRegistry, context, readers);
|
||||
this.exceptionHandlerResolvers = exceptionHandlerResolvers(customResolvers, adapterRegistry, context);
|
||||
this.reactiveAdapterRegistry = adapterRegistry;
|
||||
this.contentTypeResolver = contentTypeResolver;
|
||||
this.invocationScheduler = invocationScheduler;
|
||||
this.blockingMethodPredicate = blockingMethodPredicate;
|
||||
|
||||
@@ -398,44 +407,53 @@ class ControllerMethodResolver {
|
||||
* controller method, and also within {@code @ControllerAdvice} classes that
|
||||
* are applicable to the class of the given controller method.
|
||||
* @param ex the exception to find a handler for
|
||||
* @param handlerMethod the controller method that raised the exception, or
|
||||
* if {@code null}, check only {@code @ControllerAdvice} classes.
|
||||
* @param exchange the current HTTP exchange
|
||||
* @param handlerMethod the controller method that raised the exception,
|
||||
* or if {@code null}, check only {@code @ControllerAdvice} classes.
|
||||
*/
|
||||
@Nullable
|
||||
@SuppressWarnings("NullAway")
|
||||
public InvocableHandlerMethod getExceptionHandlerMethod(Throwable ex, @Nullable HandlerMethod handlerMethod) {
|
||||
public InvocableHandlerMethod getExceptionHandlerMethod(Throwable ex, ServerWebExchange exchange, @Nullable HandlerMethod handlerMethod) {
|
||||
|
||||
Class<?> handlerType = (handlerMethod != null ? handlerMethod.getBeanType() : null);
|
||||
Object exceptionHandlerObject = null;
|
||||
Method exceptionHandlerMethod = null;
|
||||
List<MediaType> requestedMediaTypes = this.contentTypeResolver.resolveMediaTypes(exchange);
|
||||
|
||||
// Controller-local first
|
||||
if (handlerType != null) {
|
||||
// Controller-local first...
|
||||
exceptionHandlerObject = handlerMethod.getBean();
|
||||
exceptionHandlerMethod = this.exceptionHandlerCache
|
||||
.computeIfAbsent(handlerType, ExceptionHandlerMethodResolver::new)
|
||||
.resolveMethodByThrowable(ex);
|
||||
for (MediaType mediaType : requestedMediaTypes) {
|
||||
ExceptionHandlerMappingInfo mappingInfo = this.exceptionHandlerCache
|
||||
.computeIfAbsent(handlerType, ExceptionHandlerMethodResolver::new)
|
||||
.resolveExceptionMapping(ex, mediaType);
|
||||
if (mappingInfo != null) {
|
||||
if (!mappingInfo.getProducibleTypes().isEmpty()) {
|
||||
exchange.getAttributes().put(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, mappingInfo.getProducibleTypes());
|
||||
}
|
||||
return createInvocableHandlerMethod(handlerMethod.getBean(), mappingInfo.getHandlerMethod());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (exceptionHandlerMethod == null) {
|
||||
// Global exception handlers...
|
||||
// Global exception handlers
|
||||
for (MediaType mediaType : requestedMediaTypes) {
|
||||
for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
|
||||
ControllerAdviceBean advice = entry.getKey();
|
||||
if (advice.isApplicableToBeanType(handlerType)) {
|
||||
exceptionHandlerMethod = entry.getValue().resolveMethodByThrowable(ex);
|
||||
if (exceptionHandlerMethod != null) {
|
||||
exceptionHandlerObject = advice.resolveBean();
|
||||
break;
|
||||
ExceptionHandlerMappingInfo mappingInfo = entry.getValue().resolveExceptionMapping(ex, mediaType);
|
||||
if (mappingInfo != null) {
|
||||
if (!mappingInfo.getProducibleTypes().isEmpty()) {
|
||||
exchange.getAttributes().put(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, mappingInfo.getProducibleTypes());
|
||||
}
|
||||
return createInvocableHandlerMethod(advice.resolveBean(), mappingInfo.getHandlerMethod());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (exceptionHandlerObject == null || exceptionHandlerMethod == null) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
InvocableHandlerMethod invocable = new InvocableHandlerMethod(exceptionHandlerObject, exceptionHandlerMethod);
|
||||
private InvocableHandlerMethod createInvocableHandlerMethod(Object bean, Method method) {
|
||||
InvocableHandlerMethod invocable = new InvocableHandlerMethod(bean, method);
|
||||
invocable.setArgumentResolvers(this.exceptionHandlerResolvers);
|
||||
return invocable;
|
||||
}
|
||||
|
||||
@@ -46,6 +46,8 @@ import org.springframework.web.reactive.DispatchExceptionHandler;
|
||||
import org.springframework.web.reactive.HandlerAdapter;
|
||||
import org.springframework.web.reactive.HandlerMapping;
|
||||
import org.springframework.web.reactive.HandlerResult;
|
||||
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
|
||||
import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder;
|
||||
import org.springframework.web.reactive.result.method.InvocableHandlerMethod;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.util.DisconnectedClientHelper;
|
||||
@@ -56,6 +58,7 @@ import org.springframework.web.util.DisconnectedClientHelper;
|
||||
* handler methods.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Brian Clozel
|
||||
* @since 5.0
|
||||
*/
|
||||
public class RequestMappingHandlerAdapter
|
||||
@@ -82,6 +85,8 @@ public class RequestMappingHandlerAdapter
|
||||
@Nullable
|
||||
private ArgumentResolverConfigurer argumentResolverConfigurer;
|
||||
|
||||
private RequestedContentTypeResolver contentTypeResolver = new RequestedContentTypeResolverBuilder().build();
|
||||
|
||||
@Nullable
|
||||
private Scheduler scheduler;
|
||||
|
||||
@@ -148,6 +153,24 @@ public class RequestMappingHandlerAdapter
|
||||
return this.argumentResolverConfigurer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link RequestedContentTypeResolver} to use to determine requested
|
||||
* media types. If not set, the default constructor is used.
|
||||
* @since 6.2.0
|
||||
*/
|
||||
public void setContentTypeResolver(RequestedContentTypeResolver contentTypeResolver) {
|
||||
Assert.notNull(contentTypeResolver, "'contentTypeResolver' must not be null");
|
||||
this.contentTypeResolver = contentTypeResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the configured {@link RequestedContentTypeResolver}.
|
||||
* @since 6.2.0
|
||||
*/
|
||||
public RequestedContentTypeResolver getContentTypeResolver() {
|
||||
return this.contentTypeResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure an executor to invoke blocking controller methods with.
|
||||
* <p>By default, this is not set in which case controller methods are
|
||||
@@ -225,7 +248,7 @@ public class RequestMappingHandlerAdapter
|
||||
|
||||
this.methodResolver = new ControllerMethodResolver(
|
||||
this.argumentResolverConfigurer, this.reactiveAdapterRegistry, this.applicationContext,
|
||||
this.messageReaders, this.webBindingInitializer,
|
||||
this.contentTypeResolver, this.messageReaders, this.webBindingInitializer,
|
||||
this.scheduler, this.blockingMethodPredicate);
|
||||
|
||||
this.modelInitializer = new ModelInitializer(this.methodResolver, this.reactiveAdapterRegistry);
|
||||
@@ -280,7 +303,7 @@ public class RequestMappingHandlerAdapter
|
||||
exchange.getResponse().getHeaders().clearContentHeaders();
|
||||
|
||||
InvocableHandlerMethod invocable =
|
||||
this.methodResolver.getExceptionHandlerMethod(exception, handlerMethod);
|
||||
this.methodResolver.getExceptionHandlerMethod(exception, exchange, handlerMethod);
|
||||
|
||||
if (invocable != null) {
|
||||
ArrayList<Throwable> exceptions = new ArrayList<>();
|
||||
|
||||
@@ -37,6 +37,7 @@ import org.springframework.validation.Validator;
|
||||
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
|
||||
import org.springframework.web.ErrorResponse;
|
||||
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
|
||||
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
|
||||
import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder;
|
||||
import org.springframework.web.reactive.result.method.annotation.ResponseBodyResultHandler;
|
||||
import org.springframework.web.reactive.socket.server.WebSocketService;
|
||||
@@ -98,11 +99,12 @@ public class DelegatingWebFluxConfigurationTests {
|
||||
ReactiveAdapterRegistry reactiveAdapterRegistry = delegatingConfig.webFluxAdapterRegistry();
|
||||
ServerCodecConfigurer serverCodecConfigurer = delegatingConfig.serverCodecConfigurer();
|
||||
FormattingConversionService formattingConversionService = delegatingConfig.webFluxConversionService();
|
||||
RequestedContentTypeResolver requestedContentTypeResolver = delegatingConfig.webFluxContentTypeResolver();
|
||||
Validator validator = delegatingConfig.webFluxValidator();
|
||||
|
||||
ConfigurableWebBindingInitializer initializer = (ConfigurableWebBindingInitializer)
|
||||
this.delegatingConfig.requestMappingHandlerAdapter(reactiveAdapterRegistry, serverCodecConfigurer,
|
||||
formattingConversionService, validator).getWebBindingInitializer();
|
||||
formattingConversionService, requestedContentTypeResolver, validator).getWebBindingInitializer();
|
||||
|
||||
verify(webFluxConfigurer).configureHttpMessageCodecs(codecsConfigurer.capture());
|
||||
verify(webFluxConfigurer).getValidator();
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.web.reactive.result.method.annotation;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -30,6 +31,7 @@ import org.springframework.core.ReactiveAdapterRegistry;
|
||||
import org.springframework.core.codec.ByteArrayDecoder;
|
||||
import org.springframework.core.codec.ByteBufferDecoder;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.ServerCodecConfigurer;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
@@ -39,13 +41,17 @@ import org.springframework.web.bind.annotation.InitBinder;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.reactive.BindingContext;
|
||||
import org.springframework.web.reactive.HandlerMapping;
|
||||
import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder;
|
||||
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.reactive.result.method.InvocableHandlerMethod;
|
||||
import org.springframework.web.reactive.result.method.SyncHandlerMethodArgumentResolver;
|
||||
import org.springframework.web.reactive.result.method.SyncInvocableHandlerMethod;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRequest;
|
||||
import org.springframework.web.testfixture.method.ResolvableMethod;
|
||||
import org.springframework.web.testfixture.server.MockServerWebExchange;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@@ -77,7 +83,8 @@ class ControllerMethodResolverTests {
|
||||
|
||||
this.methodResolver = new ControllerMethodResolver(
|
||||
resolvers, ReactiveAdapterRegistry.getSharedInstance(), applicationContext,
|
||||
codecs.getReaders(), null, null, null);
|
||||
new RequestedContentTypeResolverBuilder().build(), codecs.getReaders(),
|
||||
null, null, null);
|
||||
|
||||
Method method = ResolvableMethod.on(TestController.class).mockCall(TestController::handle).method();
|
||||
this.handlerMethod = new HandlerMethod(new TestController(), method);
|
||||
@@ -191,8 +198,9 @@ class ControllerMethodResolverTests {
|
||||
|
||||
@Test
|
||||
void exceptionHandlerArgumentResolvers() {
|
||||
MockServerWebExchange serverWebExchange = MockServerWebExchange.builder(MockServerHttpRequest.get("/test").build()).build();
|
||||
InvocableHandlerMethod invocable = this.methodResolver.getExceptionHandlerMethod(
|
||||
new ResponseStatusException(HttpStatus.BAD_REQUEST, "reason"), this.handlerMethod);
|
||||
new ResponseStatusException(HttpStatus.BAD_REQUEST, "reason"), serverWebExchange, this.handlerMethod);
|
||||
|
||||
assertThat(invocable).as("No match").isNotNull();
|
||||
assertThat(invocable.getBeanType()).isEqualTo(TestController.class);
|
||||
@@ -226,13 +234,30 @@ class ControllerMethodResolverTests {
|
||||
|
||||
@Test
|
||||
void exceptionHandlerFromControllerAdvice() {
|
||||
MockServerWebExchange serverWebExchange = MockServerWebExchange.builder(MockServerHttpRequest.get("/test").build()).build();
|
||||
InvocableHandlerMethod invocable = this.methodResolver.getExceptionHandlerMethod(
|
||||
new IllegalStateException("reason"), this.handlerMethod);
|
||||
new IllegalStateException("reason"), serverWebExchange, this.handlerMethod);
|
||||
|
||||
assertThat(invocable).isNotNull();
|
||||
assertThat(invocable.getBeanType()).isEqualTo(TestControllerAdvice.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void exceptionHandlerWithMediaType() {
|
||||
Method method = ResolvableMethod.on(ExceptionHandlerController.class).mockCall(ExceptionHandlerController::handle).method();
|
||||
this.handlerMethod = new HandlerMethod(new ExceptionHandlerController(), method);
|
||||
MockServerHttpRequest httpRequest = MockServerHttpRequest.get("/test").accept(MediaType.APPLICATION_JSON).build();
|
||||
MockServerWebExchange serverWebExchange = MockServerWebExchange.builder(httpRequest).build();
|
||||
InvocableHandlerMethod invocable = this.methodResolver.getExceptionHandlerMethod(
|
||||
new ResponseStatusException(HttpStatus.BAD_REQUEST, "reason"), serverWebExchange, this.handlerMethod);
|
||||
|
||||
assertThat(invocable).as("No match").isNotNull();
|
||||
assertThat(invocable.getBeanType()).isEqualTo(ExceptionHandlerController.class);
|
||||
assertThat(invocable.getMethod().getName()).isEqualTo("handleExceptionJson");
|
||||
Set<MediaType> producibleMediaTypes = serverWebExchange.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
|
||||
assertThat(producibleMediaTypes).isNotEmpty().contains(MediaType.APPLICATION_JSON);
|
||||
}
|
||||
|
||||
|
||||
private static HandlerMethodArgumentResolver next(
|
||||
List<? extends HandlerMethodArgumentResolver> resolvers, AtomicInteger index) {
|
||||
@@ -273,6 +298,20 @@ class ControllerMethodResolverTests {
|
||||
|
||||
}
|
||||
|
||||
@Controller
|
||||
static class ExceptionHandlerController {
|
||||
|
||||
@GetMapping
|
||||
void handle() {}
|
||||
|
||||
@ExceptionHandler(produces = "text/html")
|
||||
void handleExceptionHtml(ResponseStatusException ex) {}
|
||||
|
||||
@ExceptionHandler(produces = "application/json")
|
||||
void handleExceptionJson(ResponseStatusException ex) {}
|
||||
|
||||
}
|
||||
|
||||
|
||||
static class CustomArgumentResolver implements HandlerMethodArgumentResolver {
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
|
||||
import org.springframework.web.bind.support.WebBindingInitializer;
|
||||
import org.springframework.web.bind.support.WebExchangeDataBinder;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder;
|
||||
import org.springframework.web.reactive.result.method.SyncInvocableHandlerMethod;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebSession;
|
||||
@@ -80,7 +81,8 @@ class ModelInitializerTests {
|
||||
|
||||
ControllerMethodResolver methodResolver = new ControllerMethodResolver(
|
||||
resolverConfigurer, adapterRegistry, new StaticApplicationContext(),
|
||||
Collections.emptyList(), null, null, null);
|
||||
new RequestedContentTypeResolverBuilder().build(), Collections.emptyList(),
|
||||
null, null, null);
|
||||
|
||||
this.modelInitializer = new ModelInitializer(methodResolver, adapterRegistry);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.ModelAttribute
|
||||
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer
|
||||
import org.springframework.web.method.HandlerMethod
|
||||
import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder
|
||||
import org.springframework.web.server.ServerWebExchange
|
||||
import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRequest
|
||||
import org.springframework.web.testfixture.method.ResolvableMethod
|
||||
@@ -52,8 +53,10 @@ class ModelInitializerKotlinTests {
|
||||
val adapterRegistry = ReactiveAdapterRegistry.getSharedInstance()
|
||||
val resolverConfigurer = ArgumentResolverConfigurer()
|
||||
resolverConfigurer.addCustomResolver(ModelMethodArgumentResolver(adapterRegistry))
|
||||
val methodResolver = ControllerMethodResolver(resolverConfigurer, adapterRegistry, StaticApplicationContext(),
|
||||
emptyList(), null, null, null)
|
||||
val methodResolver = ControllerMethodResolver(
|
||||
resolverConfigurer, adapterRegistry, StaticApplicationContext(),
|
||||
RequestedContentTypeResolverBuilder().build(), emptyList(), null, null, null
|
||||
)
|
||||
modelInitializer = ModelInitializer(methodResolver, adapterRegistry)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user