SPR-8464 Fix bug with detecting annotations on handler method arguments and consolidate anotation detection tests.
This commit is contained in:
parent
82d09d432e
commit
40fb1b21e1
|
|
@ -30,6 +30,7 @@ import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
import org.springframework.context.ApplicationContextException;
|
import org.springframework.context.ApplicationContextException;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.util.ClassUtils;
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
import org.springframework.util.ReflectionUtils.MethodFilter;
|
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
|
* @param handler the bean name of a handler or a handler instance
|
||||||
*/
|
*/
|
||||||
protected void detectHandlerMethods(final Object handler) {
|
protected void detectHandlerMethods(final Object handler) {
|
||||||
final Class<?> handlerType = (handler instanceof String) ?
|
Class<?> handlerType = (handler instanceof String) ?
|
||||||
getApplicationContext().getType((String) handler) : handler.getClass();
|
getApplicationContext().getType((String) handler) : handler.getClass();
|
||||||
|
|
||||||
|
final Class<?> userType = ClassUtils.getUserClass(handlerType);
|
||||||
|
|
||||||
Set<Method> methods = HandlerMethodSelector.selectMethods(handlerType, new MethodFilter() {
|
Set<Method> methods = HandlerMethodSelector.selectMethods(userType, new MethodFilter() {
|
||||||
public boolean matches(Method method) {
|
public boolean matches(Method method) {
|
||||||
return getMappingForMethod(method, handlerType) != null;
|
return getMappingForMethod(method, userType) != null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
for (Method method : methods) {
|
for (Method method : methods) {
|
||||||
T mapping = getMappingForMethod(method, handlerType);
|
T mapping = getMappingForMethod(method, userType);
|
||||||
registerHandlerMethod(handler, method, mapping);
|
registerHandlerMethod(handler, method, mapping);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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