SPR-8464 Fix bug with detecting annotations on handler method arguments and consolidate anotation detection tests.

This commit is contained in:
Rossen Stoyanchev 2011-06-20 19:32:27 +00:00
parent 82d09d432e
commit 40fb1b21e1
4 changed files with 419 additions and 431 deletions

View File

@ -30,6 +30,7 @@ import javax.servlet.http.HttpServletRequest;
import org.springframework.context.ApplicationContextException;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ReflectionUtils.MethodFilter;
@ -149,17 +150,19 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
* @param handler the bean name of a handler or a handler instance
*/
protected void detectHandlerMethods(final Object handler) {
final Class<?> handlerType = (handler instanceof String) ?
Class<?> handlerType = (handler instanceof String) ?
getApplicationContext().getType((String) handler) : handler.getClass();
Set<Method> methods = HandlerMethodSelector.selectMethods(handlerType, new MethodFilter() {
final Class<?> userType = ClassUtils.getUserClass(handlerType);
Set<Method> methods = HandlerMethodSelector.selectMethods(userType, new MethodFilter() {
public boolean matches(Method method) {
return getMappingForMethod(method, handlerType) != null;
return getMappingForMethod(method, userType) != null;
}
});
for (Method method : methods) {
T mapping = getMappingForMethod(method, handlerType);
T mapping = getMappingForMethod(method, userType);
registerHandlerMethod(handler, method, mapping);
}
}

View File

@ -1,248 +0,0 @@
/*
* Copyright 2002-2011 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.servlet.mvc.method.annotation;
import static org.junit.Assert.assertEquals;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.Set;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.aop.interceptor.SimpleTraceInterceptor;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.TestBean;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils.MethodFilter;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.support.DefaultDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.support.GenericWebApplicationContext;
import org.springframework.web.method.HandlerMethodSelector;
import org.springframework.web.method.annotation.support.ModelAttributeMethodProcessor;
import org.springframework.web.method.support.HandlerMethodArgumentResolverComposite;
import org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.mvc.method.annotation.support.DefaultMethodReturnValueHandler;
/**
* Test various scenarios for detecting method-level and method parameter annotations depending
* on where they are located -- on interfaces, parent classes, in parameterized methods, or in
* combination with proxies.
*
* Note the following:
* <ul>
* <li>Parameterized methods cannot be used in combination with JDK dynamic proxies since the
* proxy interface does not contain the bridged methods that need to be invoked.
* <li>When using JDK dynamic proxies, the proxied interface must contain all required method
* and method parameter annotations.
* <li>Method-level annotations can be placed on super types (interface or parent class) while
* method parameter annotations must be present on the method being invoked.
* </ul>
*
* @author Rossen Stoyanchev
*/
@RunWith(Parameterized.class)
public class HandlerMethodAdapterAnnotationDetectionTests {
@Parameters
public static Collection<Object[]> handlerTypes() {
return Arrays.asList(new Object[][] {
{ new MappingIfcController(), false },
{ new MappingAbstractClassController(), false },
{ new ParameterizedIfcController(), false },
{ new MappingParameterizedIfcController(), false },
{ new MappingIfcProxyController(), true },
{ new PlainController(), true },
{ new MappingAbstractClassController(), true }
});
}
private Object handler;
private boolean useAutoProxy;
public HandlerMethodAdapterAnnotationDetectionTests(Object handler, boolean useAutoProxy) {
this.handler = handler;
this.useAutoProxy = useAutoProxy;
}
@Test
public void invokeModelAttributeMethod() throws Exception {
ServletInvocableHandlerMethod requestMappingMethod = createRequestMappingMethod(handler, useAutoProxy);
ModelAttribute annot = requestMappingMethod.getMethodAnnotation(ModelAttribute.class);
assertEquals("Failed to detect method annotation", "attrName", annot.value());
MockHttpServletRequest servletRequest = new MockHttpServletRequest();
NativeWebRequest webRequest = new ServletWebRequest(servletRequest, new MockHttpServletResponse());
servletRequest.setParameter("name", "Chad");
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
requestMappingMethod.invokeAndHandle(webRequest, mavContainer);
Object modelAttr = mavContainer.getAttribute("attrName");
assertEquals(TestBean.class, modelAttr.getClass());
assertEquals("Chad", ((TestBean) modelAttr).getName());
}
private ServletInvocableHandlerMethod createRequestMappingMethod(Object handler, boolean useAutoProxy) {
if (useAutoProxy) {
handler = getProxyBean(handler);
}
HandlerMethodArgumentResolverComposite argResolvers = new HandlerMethodArgumentResolverComposite();
argResolvers.addResolver(new ModelAttributeMethodProcessor(false));
HandlerMethodReturnValueHandlerComposite handlers = new HandlerMethodReturnValueHandlerComposite();
handlers.addHandler(new ModelAttributeMethodProcessor(false));
handlers.addHandler(new DefaultMethodReturnValueHandler(null));
Class<?> handlerType = ClassUtils.getUserClass(handler.getClass());
Set<Method> methods = HandlerMethodSelector.selectMethods(handlerType, REQUEST_MAPPING_METHODS);
Method method = methods.iterator().next();
ServletInvocableHandlerMethod attrMethod = new ServletInvocableHandlerMethod(handler, method);
attrMethod.setHandlerMethodArgumentResolvers(argResolvers);
attrMethod.setHandlerMethodReturnValueHandlers(handlers);
attrMethod.setDataBinderFactory(new DefaultDataBinderFactory(null));
return attrMethod;
}
private Object getProxyBean(Object handler) {
GenericWebApplicationContext wac = new GenericWebApplicationContext();
wac.registerBeanDefinition("controller", new RootBeanDefinition(handler.getClass()));
DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator();
autoProxyCreator.setBeanFactory(wac.getBeanFactory());
wac.getBeanFactory().addBeanPostProcessor(autoProxyCreator);
wac.getBeanFactory().registerSingleton("advsr", new DefaultPointcutAdvisor(new SimpleTraceInterceptor()));
wac.refresh();
return wac.getBean("controller");
}
public static MethodFilter REQUEST_MAPPING_METHODS = new MethodFilter() {
public boolean matches(Method method) {
return AnnotationUtils.findAnnotation(method, RequestMapping.class) != null;
}
};
private interface MappingIfc {
@RequestMapping
@ModelAttribute("attrName")
TestBean model(TestBean input);
}
private static class MappingIfcController implements MappingIfc {
public TestBean model(@ModelAttribute TestBean input) {
return new TestBean(input.getName());
}
}
private interface MappingProxyIfc {
@RequestMapping
@ModelAttribute("attrName")
TestBean model(@ModelAttribute TestBean input);
}
private static class MappingIfcProxyController implements MappingProxyIfc {
@ModelAttribute("attrName")
public TestBean model(@ModelAttribute TestBean input) {
return new TestBean(input.getName());
}
}
public static abstract class MappingAbstractClass {
@RequestMapping
@ModelAttribute("attrName")
TestBean model(TestBean input) {
return new TestBean(input.getName());
}
}
public static class MappingAbstractClassController extends MappingAbstractClass {
public TestBean model(@ModelAttribute TestBean input) {
TestBean testBean = super.model(input);
testBean.setAge(14);
return testBean;
}
}
public interface ParameterizedIfc<TB, S> {
TB model(S input);
}
public static class ParameterizedIfcController implements ParameterizedIfc<TestBean, TestBean> {
@RequestMapping
@ModelAttribute("attrName")
public TestBean model(@ModelAttribute TestBean input) {
return new TestBean(input.getName());
}
}
public interface MappingParameterizedIfc<TB, S> {
@RequestMapping
@ModelAttribute("attrName")
TB model(S input);
}
public static class MappingParameterizedIfcController implements MappingParameterizedIfc<TestBean, TestBean> {
public TestBean model(@ModelAttribute TestBean input) {
return new TestBean(input.getName());
}
}
public interface MappingParameterizedProxyIfc<TB, S> {
@RequestMapping
@ModelAttribute("attrName")
TB model(@ModelAttribute("inputName") S input);
}
public static class MappingParameterizedProxyIfcController implements MappingParameterizedProxyIfc<TestBean, TestBean> {
@RequestMapping
@ModelAttribute("attrName")
public TestBean model(@ModelAttribute TestBean input) {
return new TestBean(input.getName());
}
}
public static class PlainController {
public PlainController() {
}
@RequestMapping
@ModelAttribute("attrName")
public TestBean model(@ModelAttribute TestBean input) {
return new TestBean(input.getName());
}
}
}

View File

@ -0,0 +1,412 @@
/*
* Copyright 2002-2011 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.servlet.mvc.method.annotation;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.aop.interceptor.SimpleTraceInterceptor;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.support.GenericWebApplicationContext;
import org.springframework.web.servlet.HandlerExecutionChain;
/**
* Test various scenarios for detecting method-level and method parameter annotations depending
* on where they are located -- on interfaces, parent classes, in parameterized methods, or in
* combination with proxies.
*
* @author Rossen Stoyanchev
*/
@RunWith(Parameterized.class)
public class HandlerMethodAnnotationDetectionTests {
@Parameters
public static Collection<Object[]> handlerTypes() {
Object[][] array = new Object[12][2];
array[0] = new Object[] { SimpleController.class, true}; // CGLib proxy
array[1] = new Object[] { SimpleController.class, false};
array[2] = new Object[] { AbstractClassController.class, true }; // CGLib proxy
array[3] = new Object[] { AbstractClassController.class, false };
array[4] = new Object[] { ParameterizedAbstractClassController.class, false}; // CGLib proxy
array[5] = new Object[] { ParameterizedAbstractClassController.class, false};
array[6] = new Object[] { InterfaceController.class, true }; // JDK dynamic proxy
array[7] = new Object[] { InterfaceController.class, false };
array[8] = new Object[] { ParameterizedInterfaceController.class, false}; // no AOP
array[9] = new Object[] { ParameterizedInterfaceController.class, false};
array[10] = new Object[] { SupportClassController.class, true}; // CGLib proxy
array[11] = new Object[] { SupportClassController.class, false};
return Arrays.asList(array);
}
private RequestMappingHandlerMapping handlerMapping = new RequestMappingHandlerMapping();
// private DefaultAnnotationHandlerMapping handlerMapping = new DefaultAnnotationHandlerMapping();
private RequestMappingHandlerAdapter handlerAdapter = new RequestMappingHandlerAdapter();
// AnnotationMethodHandlerAdapter handlerAdapter = new AnnotationMethodHandlerAdapter();
private ExceptionHandlerExceptionResolver exceptionResolver = new ExceptionHandlerExceptionResolver();
// private AnnotationMethodHandlerExceptionResolver exceptionResolver = new AnnotationMethodHandlerExceptionResolver();
public HandlerMethodAnnotationDetectionTests(Class<?> controllerType, boolean useAutoProxy) {
GenericWebApplicationContext context = new GenericWebApplicationContext();
context.registerBeanDefinition("controller", new RootBeanDefinition(controllerType));
if (useAutoProxy) {
DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator();
autoProxyCreator.setBeanFactory(context.getBeanFactory());
context.getBeanFactory().addBeanPostProcessor(autoProxyCreator);
context.getBeanFactory().registerSingleton("advsr", new DefaultPointcutAdvisor(new SimpleTraceInterceptor()));
}
context.refresh();
handlerMapping.setApplicationContext(context);
List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
messageConverters.add(new MappingJacksonHttpMessageConverter());
handlerAdapter.setMessageConverters(messageConverters);
handlerAdapter.afterPropertiesSet();
// handlerAdapter.setMessageConverters(messageConverters.toArray(new HttpMessageConverter<?>[messageConverters.size()]));
// handlerAdapter.setApplicationContext(context);
exceptionResolver.setMessageConverters(messageConverters);
exceptionResolver.afterPropertiesSet();
// exceptionResolver.setMessageConverters(messageConverters.toArray(new HttpMessageConverter<?>[messageConverters.size()]));
}
@Test
public void testRequestMappingMethod() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/path1/path2");
request.setParameter("datePattern", "MM:dd:yyyy");
request.addHeader("dateA", "11:01:2011");
request.addHeader("dateB", "11:02:2011");
request.addHeader("Accept", "application/json");
HandlerExecutionChain chain = handlerMapping.getHandler(request);
assertNotNull(chain);
MockHttpServletResponse response = new MockHttpServletResponse();
handlerAdapter.handle(request, response, chain.getHandler());
assertEquals("application/json", response.getHeader("Content-Type"));
assertEquals("{\"dateA\":1320105600000,\"dateB\":1320192000000}", response.getContentAsString());
response = new MockHttpServletResponse();
exceptionResolver.resolveException(request, response, chain.getHandler(), new Exception("failure"));
assertEquals("application/json", response.getHeader("Content-Type"));
assertEquals("\"failure\"", response.getContentAsString());
}
/**
* SIMPLE CASE
*/
@Controller
static class SimpleController {
@InitBinder
public void initBinder(WebDataBinder dataBinder, @RequestParam("datePattern") String thePattern) {
CustomDateEditor dateEditor = new CustomDateEditor(new SimpleDateFormat(thePattern), false);
dataBinder.registerCustomEditor(Date.class, dateEditor);
}
@ModelAttribute
public void initModel(@RequestHeader("dateA") Date date, Model model) {
model.addAttribute("dateA", date);
}
@RequestMapping(value="/path1/path2", method=RequestMethod.POST, produces="application/json")
@ResponseBody
public Map<String, Object> handle(@RequestHeader("dateB") Date date, Model model) throws Exception {
model.addAttribute("dateB", date);
return model.asMap();
}
@ExceptionHandler(Exception.class)
@ResponseBody
public String handleException(Exception exception) {
return exception.getMessage();
}
}
@Controller
static abstract class MappingAbstractClass {
@InitBinder
public abstract void initBinder(WebDataBinder dataBinder, String thePattern);
@ModelAttribute
public abstract void initModel(Date date, Model model);
@RequestMapping(value="/path1/path2", method=RequestMethod.POST, produces="application/json")
@ResponseBody
public abstract Map<String, Object> handle(Date date, Model model) throws Exception;
@ExceptionHandler(Exception.class)
@ResponseBody
public abstract String handleException(Exception exception);
}
/**
* CONTROLLER WITH ABSTRACT CLASS
*
* <p>All annotations can be on methods in the abstract class except parameter annotations.
*/
static class AbstractClassController extends MappingAbstractClass {
public void initBinder(WebDataBinder dataBinder, @RequestParam("datePattern") String thePattern) {
CustomDateEditor dateEditor = new CustomDateEditor(new SimpleDateFormat(thePattern), false);
dataBinder.registerCustomEditor(Date.class, dateEditor);
}
public void initModel(@RequestHeader("dateA") Date date, Model model) {
model.addAttribute("dateA", date);
}
public Map<String, Object> handle(@RequestHeader("dateB") Date date, Model model) throws Exception {
model.addAttribute("dateB", date);
return model.asMap();
}
public String handleException(Exception exception) {
return exception.getMessage();
}
}
@Controller
static interface MappingInterface {
@InitBinder
void initBinder(WebDataBinder dataBinder, @RequestParam("datePattern") String thePattern);
@ModelAttribute
void initModel(@RequestHeader("dateA") Date date, Model model);
@RequestMapping(value="/path1/path2", method=RequestMethod.POST, produces="application/json")
@ResponseBody
Map<String, Object> handle(@RequestHeader("dateB") Date date, Model model) throws Exception;
@ExceptionHandler(Exception.class)
@ResponseBody
String handleException(Exception exception);
}
/**
* CONTROLLER WITH INTERFACE
*
* No AOP:
* All annotations can be on interface methods except parameter annotations.
*
* JDK Dynamic proxy:
* All annotations must be on the interface.
*/
static class InterfaceController implements MappingInterface {
public void initBinder(WebDataBinder dataBinder, @RequestParam("datePattern") String thePattern) {
CustomDateEditor dateEditor = new CustomDateEditor(new SimpleDateFormat(thePattern), false);
dataBinder.registerCustomEditor(Date.class, dateEditor);
}
public void initModel(@RequestHeader("dateA") Date date, Model model) {
model.addAttribute("dateA", date);
}
public Map<String, Object> handle(@RequestHeader("dateB") Date date, Model model) throws Exception {
model.addAttribute("dateB", date);
return model.asMap();
}
public String handleException(Exception exception) {
return exception.getMessage();
}
}
@Controller
static abstract class MappingParameterizedAbstractClass<A, B, C> {
@InitBinder
public abstract void initBinder(WebDataBinder dataBinder, A thePattern);
@ModelAttribute
public abstract void initModel(B date, Model model);
@RequestMapping(value="/path1/path2", method=RequestMethod.POST, produces="application/json")
@ResponseBody
public abstract Map<String, Object> handle(C date, Model model) throws Exception;
@ExceptionHandler(Exception.class)
@ResponseBody
public abstract String handleException(Exception exception);
}
/**
* CONTROLLER WITH PARAMETERIZED BASE CLASS
*
* <p>All annotations can be on methods in the abstract class except parameter annotations.
*/
static class ParameterizedAbstractClassController extends MappingParameterizedAbstractClass<String, Date, Date> {
public void initBinder(WebDataBinder dataBinder, @RequestParam("datePattern") String thePattern) {
CustomDateEditor dateEditor = new CustomDateEditor(new SimpleDateFormat(thePattern), false);
dataBinder.registerCustomEditor(Date.class, dateEditor);
}
public void initModel(@RequestHeader("dateA") Date date, Model model) {
model.addAttribute("dateA", date);
}
public Map<String, Object> handle(@RequestHeader("dateB") Date date, Model model) throws Exception {
model.addAttribute("dateB", date);
return model.asMap();
}
public String handleException(Exception exception) {
return exception.getMessage();
}
}
@Controller
static interface MappingParameterizedInterface<A, B, C> {
@InitBinder
void initBinder(WebDataBinder dataBinder, A thePattern);
@ModelAttribute
void initModel(B date, Model model);
@RequestMapping(value="/path1/path2", method=RequestMethod.POST, produces="application/json")
@ResponseBody
Map<String, Object> handle(C date, Model model) throws Exception;
@ExceptionHandler(Exception.class)
@ResponseBody
String handleException(Exception exception);
}
/**
* CONTROLLER WITH PARAMETERIZED INTERFACE
*
* <p>All annotations can be on interface except parameter annotations.
*
* <p>Cannot be used as JDK dynamic proxy since parameterized interface does not contain type information.
*/
static class ParameterizedInterfaceController implements MappingParameterizedInterface<String, Date, Date> {
@InitBinder
public void initBinder(WebDataBinder dataBinder, @RequestParam("datePattern") String thePattern) {
CustomDateEditor dateEditor = new CustomDateEditor(new SimpleDateFormat(thePattern), false);
dataBinder.registerCustomEditor(Date.class, dateEditor);
}
@ModelAttribute
public void initModel(@RequestHeader("dateA") Date date, Model model) {
model.addAttribute("dateA", date);
}
@RequestMapping(value="/path1/path2", method=RequestMethod.POST, produces="application/json")
@ResponseBody
public Map<String, Object> handle(@RequestHeader("dateB") Date date, Model model) throws Exception {
model.addAttribute("dateB", date);
return model.asMap();
}
@ExceptionHandler(Exception.class)
@ResponseBody
public String handleException(Exception exception) {
return exception.getMessage();
}
}
/**
* SPR-8248
*
* <p>Support class contains all annotations. Subclass has type-level @{@link RequestMapping}.
*/
@Controller
static class MappingSupportClass {
@InitBinder
public void initBinder(WebDataBinder dataBinder, @RequestParam("datePattern") String thePattern) {
CustomDateEditor dateEditor = new CustomDateEditor(new SimpleDateFormat(thePattern), false);
dataBinder.registerCustomEditor(Date.class, dateEditor);
}
@ModelAttribute
public void initModel(@RequestHeader("dateA") Date date, Model model) {
model.addAttribute("dateA", date);
}
@ResponseBody
@RequestMapping(value="/path2", method=RequestMethod.POST, produces="application/json")
public Map<String, Object> handle(@RequestHeader("dateB") Date date, Model model) throws Exception {
model.addAttribute("dateB", date);
return model.asMap();
}
@ExceptionHandler(Exception.class)
@ResponseBody
public String handleException(Exception exception) {
return exception.getMessage();
}
}
@Controller
@RequestMapping("/path1")
static class SupportClassController extends MappingSupportClass {
}
}

View File

@ -1,179 +0,0 @@
/*
* Copyright 2002-2011 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.servlet.mvc.method.annotation;
import static org.junit.Assert.assertNotNull;
import java.util.Arrays;
import java.util.Collection;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.aop.interceptor.SimpleTraceInterceptor;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.TestBean;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.support.GenericWebApplicationContext;
import org.springframework.web.method.HandlerMethod;
/**
* Test various scenarios for detecting handler methods depending on where @RequestMapping annotations
* are located -- super types, parameterized methods, or in combination with proxies.
*
* Note the following:
* <ul>
* <li>Parameterized methods cannot be used in combination with JDK dynamic proxies since the
* proxy interface does not contain the bridged methods that need to be invoked.
* <li>When using JDK dynamic proxies, the proxied interface must contain all required annotations.
* <li>Method-level annotations can be placed on parent classes or interfaces.
* </ul>
*
* @author Rossen Stoyanchev
*/
@RunWith(Parameterized.class)
public class HandlerMethodMappingAnnotationDetectionTests {
@Parameters
public static Collection<Object[]> handlerTypes() {
return Arrays.asList(new Object[][] {
{ new MappingInterfaceController(), false},
{ new MappingAbstractClassController(), false},
{ new ParameterizedInterfaceController(), false },
{ new MappingParameterizedInterfaceController(), false },
{ new MappingClassController(), false },
{ new MappingAbstractClassController(), true},
{ new PlainController(), true}
});
}
private Object handler;
private boolean useAutoProxy;
public HandlerMethodMappingAnnotationDetectionTests(Object handler, boolean useAutoProxy) {
this.handler = handler;
this.useAutoProxy = useAutoProxy;
}
@Test
public void detectAndMapHandlerMethod() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/type/handle");
RequestMappingHandlerMapping mapping = createHandlerMapping(handler.getClass(), useAutoProxy);
HandlerMethod handlerMethod = (HandlerMethod) mapping.getHandler(request).getHandler();
assertNotNull("Failed to detect and map @RequestMapping handler method", handlerMethod);
}
private RequestMappingHandlerMapping createHandlerMapping(Class<?> controllerType, boolean useAutoProxy) {
GenericWebApplicationContext wac = new GenericWebApplicationContext();
wac.registerBeanDefinition("controller", new RootBeanDefinition(controllerType));
if (useAutoProxy) {
DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator();
autoProxyCreator.setBeanFactory(wac.getBeanFactory());
wac.getBeanFactory().addBeanPostProcessor(autoProxyCreator);
wac.getBeanFactory().registerSingleton("advsr", new DefaultPointcutAdvisor(new SimpleTraceInterceptor()));
}
RequestMappingHandlerMapping mapping = new RequestMappingHandlerMapping();
mapping.setApplicationContext(wac);
return mapping;
}
/* Annotation on interface method */
@Controller
public interface MappingInterface {
@RequestMapping(value="/handle", method = RequestMethod.GET)
void handle();
}
@RequestMapping(value="/type")
public static class MappingInterfaceController implements MappingInterface {
public void handle() {
}
}
/* Annotation on abstract class method */
@Controller
public static abstract class MappingAbstractClass {
@RequestMapping(value = "/handle", method = RequestMethod.GET)
public abstract void handle();
}
@RequestMapping(value="/type")
public static class MappingAbstractClassController extends MappingAbstractClass {
public void handle() {
}
}
/* Annotation on parameterized controller method */
@Controller
public interface ParameterizedInterface<T> {
void handle(T object);
}
@RequestMapping(value="/type")
public static class ParameterizedInterfaceController implements ParameterizedInterface<TestBean> {
@RequestMapping(value = "/handle", method = RequestMethod.GET)
public void handle(TestBean object) {
}
}
/* Annotation on parameterized interface method */
@Controller
public interface MappingParameterizedInterface<T> {
@RequestMapping(value = "/handle", method = RequestMethod.GET)
void handle(T object);
}
@RequestMapping(value="/type")
public static class MappingParameterizedInterfaceController implements MappingParameterizedInterface<TestBean> {
public void handle(TestBean object) {
}
}
/* Type + method annotations, method in parent class only (SPR-8248) */
@Controller
public static class MappingClass {
@RequestMapping(value = "/handle", method = RequestMethod.GET)
public void handle(TestBean object) {
}
}
@RequestMapping(value="/type")
public static class MappingClassController extends MappingClass {
// Method in parent class only
}
/* Annotations on controller class */
@Controller
@RequestMapping(value="/type")
public static class PlainController {
@RequestMapping(value = "/handle", method = RequestMethod.GET)
public void handle() {
}
}
}