From 40fb1b21e1ae3e802c2e9326afd81e6642c6ee59 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Mon, 20 Jun 2011 19:32:27 +0000 Subject: [PATCH] SPR-8464 Fix bug with detecting annotations on handler method arguments and consolidate anotation detection tests. --- .../handler/AbstractHandlerMethodMapping.java | 11 +- ...MethodAdapterAnnotationDetectionTests.java | 248 ----------- ...HandlerMethodAnnotationDetectionTests.java | 412 ++++++++++++++++++ ...MethodMappingAnnotationDetectionTests.java | 179 -------- 4 files changed, 419 insertions(+), 431 deletions(-) delete mode 100644 org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HandlerMethodAdapterAnnotationDetectionTests.java create mode 100644 org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HandlerMethodAnnotationDetectionTests.java delete mode 100644 org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HandlerMethodMappingAnnotationDetectionTests.java diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java index fc1efccc49a..d6347712879 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java @@ -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 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(); + + final Class userType = ClassUtils.getUserClass(handlerType); - Set methods = HandlerMethodSelector.selectMethods(handlerType, new MethodFilter() { + Set 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); } } diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HandlerMethodAdapterAnnotationDetectionTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HandlerMethodAdapterAnnotationDetectionTests.java deleted file mode 100644 index 595c30818af..00000000000 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HandlerMethodAdapterAnnotationDetectionTests.java +++ /dev/null @@ -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: - *
    - *
  • 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. - *
  • When using JDK dynamic proxies, the proxied interface must contain all required method - * and method parameter annotations. - *
  • 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. - *
- * - * @author Rossen Stoyanchev - */ -@RunWith(Parameterized.class) -public class HandlerMethodAdapterAnnotationDetectionTests { - - @Parameters - public static Collection 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 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 model(S input); - } - - public static class ParameterizedIfcController implements ParameterizedIfc { - @RequestMapping - @ModelAttribute("attrName") - public TestBean model(@ModelAttribute TestBean input) { - return new TestBean(input.getName()); - } - } - - public interface MappingParameterizedIfc { - @RequestMapping - @ModelAttribute("attrName") - TB model(S input); - } - - public static class MappingParameterizedIfcController implements MappingParameterizedIfc { - public TestBean model(@ModelAttribute TestBean input) { - return new TestBean(input.getName()); - } - } - - public interface MappingParameterizedProxyIfc { - @RequestMapping - @ModelAttribute("attrName") - TB model(@ModelAttribute("inputName") S input); - } - - public static class MappingParameterizedProxyIfcController implements MappingParameterizedProxyIfc { - @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()); - } - } -} diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HandlerMethodAnnotationDetectionTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HandlerMethodAnnotationDetectionTests.java new file mode 100644 index 00000000000..f13a2e4f5af --- /dev/null +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HandlerMethodAnnotationDetectionTests.java @@ -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 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> messageConverters = new ArrayList>(); + 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 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 handle(Date date, Model model) throws Exception; + + @ExceptionHandler(Exception.class) + @ResponseBody + public abstract String handleException(Exception exception); + } + + /** + * CONTROLLER WITH ABSTRACT CLASS + * + *

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 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 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 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 { + + @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 handle(C date, Model model) throws Exception; + + @ExceptionHandler(Exception.class) + @ResponseBody + public abstract String handleException(Exception exception); + } + + /** + * CONTROLLER WITH PARAMETERIZED BASE CLASS + * + *

All annotations can be on methods in the abstract class except parameter annotations. + */ + static class ParameterizedAbstractClassController extends MappingParameterizedAbstractClass { + + 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 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 { + + @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 handle(C date, Model model) throws Exception; + + @ExceptionHandler(Exception.class) + @ResponseBody + String handleException(Exception exception); + } + + /** + * CONTROLLER WITH PARAMETERIZED INTERFACE + * + *

All annotations can be on interface except parameter annotations. + * + *

Cannot be used as JDK dynamic proxy since parameterized interface does not contain type information. + */ + static class ParameterizedInterfaceController implements MappingParameterizedInterface { + + @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 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 + * + *

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 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 { + } + +} diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HandlerMethodMappingAnnotationDetectionTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HandlerMethodMappingAnnotationDetectionTests.java deleted file mode 100644 index fac97601973..00000000000 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HandlerMethodMappingAnnotationDetectionTests.java +++ /dev/null @@ -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: - *

    - *
  • 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. - *
  • When using JDK dynamic proxies, the proxied interface must contain all required annotations. - *
  • Method-level annotations can be placed on parent classes or interfaces. - *
- * - * @author Rossen Stoyanchev - */ -@RunWith(Parameterized.class) -public class HandlerMethodMappingAnnotationDetectionTests { - - @Parameters - public static Collection 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 { - void handle(T object); - } - @RequestMapping(value="/type") - public static class ParameterizedInterfaceController implements ParameterizedInterface { - @RequestMapping(value = "/handle", method = RequestMethod.GET) - public void handle(TestBean object) { - } - } - - /* Annotation on parameterized interface method */ - - @Controller - public interface MappingParameterizedInterface { - @RequestMapping(value = "/handle", method = RequestMethod.GET) - void handle(T object); - } - @RequestMapping(value="/type") - public static class MappingParameterizedInterfaceController implements MappingParameterizedInterface { - 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() { - } - } - -}