Browse Source
ControllerMethodResolver now also encapsulates initialization, storage, and use of HandlerMethodArgumentResolver's by annotated method type.pull/1369/head
3 changed files with 567 additions and 241 deletions
@ -0,0 +1,295 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2017 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 |
||||||
|
* |
||||||
|
* http://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.springframework.web.reactive.result.method.annotation; |
||||||
|
|
||||||
|
|
||||||
|
import java.lang.reflect.Method; |
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Optional; |
||||||
|
import java.util.concurrent.atomic.AtomicInteger; |
||||||
|
|
||||||
|
import org.junit.Before; |
||||||
|
import org.junit.Test; |
||||||
|
import reactor.core.publisher.Mono; |
||||||
|
|
||||||
|
import org.springframework.context.annotation.AnnotationConfigApplicationContext; |
||||||
|
import org.springframework.core.MethodParameter; |
||||||
|
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.codec.DecoderHttpMessageReader; |
||||||
|
import org.springframework.http.codec.HttpMessageReader; |
||||||
|
import org.springframework.stereotype.Controller; |
||||||
|
import org.springframework.web.bind.annotation.ControllerAdvice; |
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler; |
||||||
|
import org.springframework.web.bind.annotation.GetMapping; |
||||||
|
import org.springframework.web.bind.annotation.InitBinder; |
||||||
|
import org.springframework.web.bind.annotation.ModelAttribute; |
||||||
|
import org.springframework.web.method.HandlerMethod; |
||||||
|
import org.springframework.web.method.ResolvableMethod; |
||||||
|
import org.springframework.web.reactive.BindingContext; |
||||||
|
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 static org.junit.Assert.assertEquals; |
||||||
|
import static org.junit.Assert.assertNotNull; |
||||||
|
|
||||||
|
/** |
||||||
|
* Unit tests for {@link ControllerMethodResolver}. |
||||||
|
* @author Rossen Stoyanchev |
||||||
|
*/ |
||||||
|
public class ControllerMethodResolverTests { |
||||||
|
|
||||||
|
private ControllerMethodResolver methodResolver; |
||||||
|
|
||||||
|
private HandlerMethod handlerMethod; |
||||||
|
|
||||||
|
|
||||||
|
@Before |
||||||
|
public void setUp() throws Exception { |
||||||
|
|
||||||
|
List<HandlerMethodArgumentResolver> customResolvers = |
||||||
|
Arrays.asList(new CustomArgumentResolver(), new CustomSyncArgumentResolver()); |
||||||
|
|
||||||
|
List<HttpMessageReader<?>> messageReaders = Arrays.asList( |
||||||
|
new DecoderHttpMessageReader<>(new ByteArrayDecoder()), |
||||||
|
new DecoderHttpMessageReader<>(new ByteBufferDecoder())); |
||||||
|
|
||||||
|
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); |
||||||
|
applicationContext.registerBean(TestControllerAdvice.class); |
||||||
|
applicationContext.refresh(); |
||||||
|
|
||||||
|
this.methodResolver = new ControllerMethodResolver( |
||||||
|
customResolvers, messageReaders, new ReactiveAdapterRegistry(), applicationContext); |
||||||
|
|
||||||
|
Method method = ResolvableMethod.on(TestController.class).mockCall(TestController::handle).method(); |
||||||
|
this.handlerMethod = new HandlerMethod(new TestController(), method); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@Test |
||||||
|
public void requestMappingArgumentResolvers() throws Exception { |
||||||
|
|
||||||
|
InvocableHandlerMethod invocable = this.methodResolver.getRequestMappingMethod(this.handlerMethod); |
||||||
|
List<HandlerMethodArgumentResolver> resolvers = invocable.getResolvers(); |
||||||
|
|
||||||
|
AtomicInteger index = new AtomicInteger(-1); |
||||||
|
assertEquals(RequestParamMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(RequestParamMapMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(PathVariableMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(PathVariableMapMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(RequestBodyArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(ModelAttributeMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(RequestHeaderMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(RequestHeaderMapMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(CookieValueMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(ExpressionValueMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(SessionAttributeMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(RequestAttributeMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
|
||||||
|
assertEquals(HttpEntityArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(ModelArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(ErrorsMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(ServerWebExchangeArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(PrincipalArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(WebSessionArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
|
||||||
|
assertEquals(CustomArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(CustomSyncArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
|
||||||
|
assertEquals(RequestParamMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(ModelAttributeMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void modelAttributeArgumentResolvers() throws Exception { |
||||||
|
|
||||||
|
List<InvocableHandlerMethod> methods = |
||||||
|
this.methodResolver.getModelAttributeMethods(this.handlerMethod); |
||||||
|
|
||||||
|
assertEquals("Expected one each from Controller + ControllerAdvice", 2, methods.size()); |
||||||
|
InvocableHandlerMethod invocable = methods.get(0); |
||||||
|
List<HandlerMethodArgumentResolver> resolvers = invocable.getResolvers(); |
||||||
|
|
||||||
|
AtomicInteger index = new AtomicInteger(-1); |
||||||
|
assertEquals(RequestParamMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(RequestParamMapMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(PathVariableMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(PathVariableMapMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(ModelAttributeMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(RequestHeaderMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(RequestHeaderMapMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(CookieValueMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(ExpressionValueMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(SessionAttributeMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(RequestAttributeMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
|
||||||
|
assertEquals(ModelArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(ErrorsMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(ServerWebExchangeArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(PrincipalArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(WebSessionArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
|
||||||
|
assertEquals(CustomArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(CustomSyncArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
|
||||||
|
assertEquals(RequestParamMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(ModelAttributeMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void initBinderArgumentResolvers() throws Exception { |
||||||
|
|
||||||
|
List<SyncInvocableHandlerMethod> methods = |
||||||
|
this.methodResolver.getInitBinderMethods(this.handlerMethod); |
||||||
|
|
||||||
|
assertEquals("Expected one each from Controller + ControllerAdvice", 2, methods.size()); |
||||||
|
SyncInvocableHandlerMethod invocable = methods.get(0); |
||||||
|
List<SyncHandlerMethodArgumentResolver> resolvers = invocable.getResolvers(); |
||||||
|
|
||||||
|
AtomicInteger index = new AtomicInteger(-1); |
||||||
|
assertEquals(RequestParamMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(RequestParamMapMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(PathVariableMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(PathVariableMapMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(RequestHeaderMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(RequestHeaderMapMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(CookieValueMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(ExpressionValueMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(RequestAttributeMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
|
||||||
|
assertEquals(ModelArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(ServerWebExchangeArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
|
||||||
|
assertEquals(CustomSyncArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
|
||||||
|
assertEquals(RequestParamMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void exceptionHandlerArgumentResolvers() throws Exception { |
||||||
|
|
||||||
|
Optional<InvocableHandlerMethod> optional = |
||||||
|
this.methodResolver.getExceptionHandlerMethod( |
||||||
|
new ResponseStatusException(HttpStatus.BAD_REQUEST, "reason"), this.handlerMethod); |
||||||
|
|
||||||
|
InvocableHandlerMethod invocable = optional.orElseThrow(() -> new AssertionError("No match")); |
||||||
|
assertEquals(TestController.class, invocable.getBeanType()); |
||||||
|
List<HandlerMethodArgumentResolver> resolvers = invocable.getResolvers(); |
||||||
|
|
||||||
|
AtomicInteger index = new AtomicInteger(-1); |
||||||
|
assertEquals(RequestParamMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(RequestParamMapMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(PathVariableMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(PathVariableMapMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(RequestHeaderMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(RequestHeaderMapMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(CookieValueMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(ExpressionValueMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(SessionAttributeMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(RequestAttributeMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
|
||||||
|
assertEquals(ModelArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(ServerWebExchangeArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(PrincipalArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(WebSessionArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
|
||||||
|
assertEquals(CustomArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
assertEquals(CustomSyncArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
|
||||||
|
assertEquals(RequestParamMethodArgumentResolver.class, next(resolvers, index).getClass()); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void exceptionHandlerFromControllerAdvice() throws Exception { |
||||||
|
|
||||||
|
Optional<InvocableHandlerMethod> optional = |
||||||
|
this.methodResolver.getExceptionHandlerMethod( |
||||||
|
new IllegalStateException("reason"), this.handlerMethod); |
||||||
|
|
||||||
|
InvocableHandlerMethod invocable = optional.orElseThrow(() -> new AssertionError("No match")); |
||||||
|
assertNotNull(invocable); |
||||||
|
assertEquals(TestControllerAdvice.class, invocable.getBeanType()); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
private static HandlerMethodArgumentResolver next( |
||||||
|
List<? extends HandlerMethodArgumentResolver> resolvers, AtomicInteger index) { |
||||||
|
|
||||||
|
return resolvers.get(index.incrementAndGet()); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@Controller |
||||||
|
private static class TestController { |
||||||
|
|
||||||
|
@InitBinder |
||||||
|
void initDataBinder() {} |
||||||
|
|
||||||
|
@ModelAttribute |
||||||
|
void initModel() {} |
||||||
|
|
||||||
|
@GetMapping |
||||||
|
void handle() {} |
||||||
|
|
||||||
|
@ExceptionHandler |
||||||
|
void handleException(ResponseStatusException ex) {} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
@ControllerAdvice |
||||||
|
private static class TestControllerAdvice { |
||||||
|
|
||||||
|
@InitBinder |
||||||
|
void initDataBinder() {} |
||||||
|
|
||||||
|
@ModelAttribute |
||||||
|
void initModel() {} |
||||||
|
|
||||||
|
@ExceptionHandler |
||||||
|
void handleException(IllegalStateException ex) {} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
private static class CustomArgumentResolver implements HandlerMethodArgumentResolver { |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean supportsParameter(MethodParameter p) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Mono<Object> resolveArgument(MethodParameter p, BindingContext c, ServerWebExchange e) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static class CustomSyncArgumentResolver extends CustomArgumentResolver |
||||||
|
implements SyncHandlerMethodArgumentResolver { |
||||||
|
|
||||||
|
@Override |
||||||
|
public Optional<Object> resolveArgumentValue(MethodParameter p, BindingContext c, ServerWebExchange e) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
Loading…
Reference in new issue