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