Browse Source
git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@4595 50f2f4bb-b051-0410-bef5-90022cba6387pull/1/merge
4 changed files with 419 additions and 431 deletions
@ -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()); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -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 { |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -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() { |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
Loading…
Reference in new issue