diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerAdapter.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerAdapter.java index a049b0dfc7..2cbc579437 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerAdapter.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerAdapter.java @@ -50,6 +50,8 @@ import org.springframework.web.server.ServerWebExchange; /** + * Supports the invocation of {@code @RequestMapping} methods. + * * @author Rossen Stoyanchev */ public class RequestMappingHandlerAdapter implements HandlerAdapter, BeanFactoryAware, InitializingBean { @@ -57,9 +59,11 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, BeanFactory private static Log logger = LogFactory.getLog(RequestMappingHandlerAdapter.class); - private final List argumentResolvers = new ArrayList<>(); + private List customArgumentResolvers; + + private List argumentResolvers; - private final List> messageConverters = new ArrayList<>(); + private final List> messageConverters = new ArrayList<>(10); private ConversionService conversionService = new DefaultFormattingConversionService(); @@ -75,13 +79,26 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, BeanFactory } + /** + * Provide custom argument resolvers without overriding the built-in ones. + */ + public void setCustomArgumentResolvers(List argumentResolvers) { + this.customArgumentResolvers = argumentResolvers; + } + + /** + * Return the custom argument resolvers. + */ + public List getCustomArgumentResolvers() { + return this.customArgumentResolvers; + } + /** * Configure the complete list of supported argument types thus overriding * the resolvers that would otherwise be configured by default. */ public void setArgumentResolvers(List resolvers) { - this.argumentResolvers.clear(); - this.argumentResolvers.addAll(resolvers); + this.argumentResolvers = new ArrayList<>(resolvers); } /** @@ -91,22 +108,35 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, BeanFactory return this.argumentResolvers; } + /** + * Configure message converters to read the request body with. + */ public void setMessageConverters(List> messageConverters) { this.messageConverters.clear(); this.messageConverters.addAll(messageConverters); } /** - * Provide the message converters to use for argument resolution. + * Return the configured message converters. */ public List> getMessageConverters() { return this.messageConverters; } + /** + * Configure a ConversionService for type conversion of controller method + * arguments as well as for converting from different async types to + * {@code Flux} and {@code Mono}. + * + * TODO: this may be replaced by DataBinder + */ public void setConversionService(ConversionService conversionService) { this.conversionService = conversionService; } + /** + * Return the configured ConversionService. + */ public ConversionService getConversionService() { return this.conversionService; } @@ -123,43 +153,47 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, BeanFactory } public ConfigurableBeanFactory getBeanFactory() { - return beanFactory; + return this.beanFactory; } @Override public void afterPropertiesSet() throws Exception { - if (ObjectUtils.isEmpty(this.argumentResolvers)) { - -// List> converters = Arrays.asList( -// new CodecHttpMessageConverter(new ByteBufferEncoder(), new ByteBufferDecoder()), -// new CodecHttpMessageConverter(new StringEncoder(), new StringDecoder()), -// new CodecHttpMessageConverter(new Jaxb2Encoder(), new Jaxb2Decoder()), -// new CodecHttpMessageConverter(new JacksonJsonEncoder(), -// new JacksonJsonDecoder(new JsonObjectDecoder()))); - - // Annotation-based argument resolution - ConversionService cs = getConversionService(); - this.argumentResolvers.add(new RequestParamMethodArgumentResolver(cs, getBeanFactory(), false)); - this.argumentResolvers.add(new RequestParamMapMethodArgumentResolver()); - this.argumentResolvers.add(new PathVariableMethodArgumentResolver(cs, getBeanFactory())); - this.argumentResolvers.add(new PathVariableMapMethodArgumentResolver()); - this.argumentResolvers.add(new RequestBodyArgumentResolver(getMessageConverters(), cs)); - this.argumentResolvers.add(new RequestHeaderMethodArgumentResolver(cs, getBeanFactory())); - this.argumentResolvers.add(new RequestHeaderMapMethodArgumentResolver()); - this.argumentResolvers.add(new CookieValueMethodArgumentResolver(cs, getBeanFactory())); - this.argumentResolvers.add(new ExpressionValueMethodArgumentResolver(cs, getBeanFactory())); - this.argumentResolvers.add(new SessionAttributeMethodArgumentResolver(cs, getBeanFactory())); - this.argumentResolvers.add(new RequestAttributeMethodArgumentResolver(cs , getBeanFactory())); - - // Type-based argument resolution - this.argumentResolvers.add(new ModelArgumentResolver()); - - // Catch-all - this.argumentResolvers.add(new RequestParamMethodArgumentResolver(cs, getBeanFactory(), true)); + if (this.argumentResolvers == null) { + this.argumentResolvers = initArgumentResolvers(); } } + protected List initArgumentResolvers() { + List resolvers = new ArrayList<>(); + + // Annotation-based argument resolution + ConversionService cs = getConversionService(); + resolvers.add(new RequestParamMethodArgumentResolver(cs, getBeanFactory(), false)); + resolvers.add(new RequestParamMapMethodArgumentResolver()); + resolvers.add(new PathVariableMethodArgumentResolver(cs, getBeanFactory())); + resolvers.add(new PathVariableMapMethodArgumentResolver()); + resolvers.add(new RequestBodyArgumentResolver(getMessageConverters(), cs)); + resolvers.add(new RequestHeaderMethodArgumentResolver(cs, getBeanFactory())); + resolvers.add(new RequestHeaderMapMethodArgumentResolver()); + resolvers.add(new CookieValueMethodArgumentResolver(cs, getBeanFactory())); + resolvers.add(new ExpressionValueMethodArgumentResolver(cs, getBeanFactory())); + resolvers.add(new SessionAttributeMethodArgumentResolver(cs, getBeanFactory())); + resolvers.add(new RequestAttributeMethodArgumentResolver(cs , getBeanFactory())); + + // Type-based argument resolution + resolvers.add(new ModelArgumentResolver()); + + // Custom resolvers + if (getCustomArgumentResolvers() != null) { + resolvers.addAll(getCustomArgumentResolvers()); + } + + // Catch-all + resolvers.add(new RequestParamMethodArgumentResolver(cs, getBeanFactory(), true)); + return resolvers; + } + @Override public boolean supports(Object handler) { return HandlerMethod.class.equals(handler.getClass()); @@ -169,7 +203,7 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, BeanFactory public Mono handle(ServerWebExchange exchange, Object handler) { HandlerMethod handlerMethod = (HandlerMethod) handler; InvocableHandlerMethod invocable = new InvocableHandlerMethod(handlerMethod); - invocable.setHandlerMethodArgumentResolvers(this.argumentResolvers); + invocable.setHandlerMethodArgumentResolvers(getArgumentResolvers()); ModelMap model = new ExtendedModelMap(); return invocable.invokeForRequest(exchange, model) .map(result -> result.setExceptionHandler(ex -> handleException(ex, handlerMethod, exchange))) diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandler.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandler.java index a2107aa77d..56f7886ffe 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandler.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandler.java @@ -78,6 +78,14 @@ public class ResponseBodyResultHandler extends ContentNegotiatingResultHandlerSu this(converters, conversionService, new HeaderContentTypeResolver()); } + + /** + * Return the configured message converters. + */ + public List> getMessageConverters() { + return this.messageConverters; + } + /** * Constructor with message converters, a {@code ConversionService}, and a * {@code RequestedContentTypeResolver}.