Discover controllers based on type @RequestMapping

This was supported in DefaultAnnotationHandlerMapping but not in the
RequestMappingHandlerMapping. The specific scenario where this matters
is a controller decorated with a JDK proxy. In this scenario the
HandlerMapping looks at interfaces only to decide if the bean is a
controller. The @Controller annotation is better left (and required)
on the class.

Issue: SPR-9374
This commit is contained in:
Rossen Stoyanchev 2012-05-10 16:19:14 -04:00
parent c499df2315
commit f61f4a960e
2 changed files with 56 additions and 55 deletions

View File

@ -45,7 +45,7 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
private boolean useSuffixPatternMatch = true; private boolean useSuffixPatternMatch = true;
private boolean useTrailingSlashMatch = true; private boolean useTrailingSlashMatch = true;
/** /**
* Whether to use suffix pattern match (".*") when matching patterns to * Whether to use suffix pattern match (".*") when matching patterns to
* requests. If enabled a method mapped to "/users" also matches to "/users.*". * requests. If enabled a method mapped to "/users" also matches to "/users.*".
@ -54,7 +54,7 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
public void setUseSuffixPatternMatch(boolean useSuffixPatternMatch) { public void setUseSuffixPatternMatch(boolean useSuffixPatternMatch) {
this.useSuffixPatternMatch = useSuffixPatternMatch; this.useSuffixPatternMatch = useSuffixPatternMatch;
} }
/** /**
* Whether to match to URLs irrespective of the presence of a trailing slash. * Whether to match to URLs irrespective of the presence of a trailing slash.
* If enabled a method mapped to "/users" also matches to "/users/". * If enabled a method mapped to "/users" also matches to "/users/".
@ -83,7 +83,8 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
*/ */
@Override @Override
protected boolean isHandler(Class<?> beanType) { protected boolean isHandler(Class<?> beanType) {
return AnnotationUtils.findAnnotation(beanType, Controller.class) != null; return ((AnnotationUtils.findAnnotation(beanType, Controller.class) != null) ||
(AnnotationUtils.findAnnotation(beanType, RequestMapping.class) != null));
} }
/** /**
@ -123,7 +124,7 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
protected RequestCondition<?> getCustomMethodCondition(Method method) { protected RequestCondition<?> getCustomMethodCondition(Method method) {
return null; return null;
} }
/** /**
* Provide a custom type-level request condition. * Provide a custom type-level request condition.
* The custom {@link RequestCondition} can be of any type so long as the * The custom {@link RequestCondition} can be of any type so long as the

View File

@ -51,37 +51,37 @@ import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.ModelAndView;
/** /**
* Test various scenarios for detecting method-level and method parameter annotations depending * 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 * on where they are located -- on interfaces, parent classes, in parameterized methods, or in
* combination with proxies. * combination with proxies.
* *
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
*/ */
@RunWith(Parameterized.class) @RunWith(Parameterized.class)
public class HandlerMethodAnnotationDetectionTests { public class HandlerMethodAnnotationDetectionTests {
@Parameters @Parameters
public static Collection<Object[]> handlerTypes() { public static Collection<Object[]> handlerTypes() {
Object[][] array = new Object[12][2]; Object[][] array = new Object[12][2];
array[0] = new Object[] { SimpleController.class, true}; // CGLib proxy array[0] = new Object[] { SimpleController.class, true}; // CGLib proxy
array[1] = new Object[] { SimpleController.class, false}; array[1] = new Object[] { SimpleController.class, false};
array[2] = new Object[] { AbstractClassController.class, true }; // CGLib proxy array[2] = new Object[] { AbstractClassController.class, true }; // CGLib proxy
array[3] = new Object[] { AbstractClassController.class, false }; array[3] = new Object[] { AbstractClassController.class, false };
array[4] = new Object[] { ParameterizedAbstractClassController.class, false}; // CGLib proxy array[4] = new Object[] { ParameterizedAbstractClassController.class, false}; // CGLib proxy
array[5] = new Object[] { ParameterizedAbstractClassController.class, false}; array[5] = new Object[] { ParameterizedAbstractClassController.class, false};
array[6] = new Object[] { InterfaceController.class, true }; // JDK dynamic proxy array[6] = new Object[] { InterfaceController.class, true }; // JDK dynamic proxy
array[7] = new Object[] { InterfaceController.class, false }; array[7] = new Object[] { InterfaceController.class, false };
array[8] = new Object[] { ParameterizedInterfaceController.class, false}; // no AOP array[8] = new Object[] { ParameterizedInterfaceController.class, false}; // no AOP
array[9] = new Object[] { ParameterizedInterfaceController.class, false}; array[9] = new Object[] { ParameterizedInterfaceController.class, false};
array[10] = new Object[] { SupportClassController.class, true}; // CGLib proxy array[10] = new Object[] { SupportClassController.class, true}; // CGLib proxy
array[11] = new Object[] { SupportClassController.class, false}; array[11] = new Object[] { SupportClassController.class, false};
return Arrays.asList(array); return Arrays.asList(array);
} }
@ -101,7 +101,7 @@ public class HandlerMethodAnnotationDetectionTests {
context.getBeanFactory().registerSingleton("advisor", new DefaultPointcutAdvisor(new SimpleTraceInterceptor())); context.getBeanFactory().registerSingleton("advisor", new DefaultPointcutAdvisor(new SimpleTraceInterceptor()));
} }
context.refresh(); context.refresh();
handlerMapping.setApplicationContext(context); handlerMapping.setApplicationContext(context);
handlerAdapter.afterPropertiesSet(); handlerAdapter.afterPropertiesSet();
exceptionResolver.afterPropertiesSet(); exceptionResolver.afterPropertiesSet();
@ -113,12 +113,12 @@ public class HandlerMethodAnnotationDetectionTests {
SimpleDateFormat dateFormat = new SimpleDateFormat(datePattern); SimpleDateFormat dateFormat = new SimpleDateFormat(datePattern);
String dateA = "11:01:2011"; String dateA = "11:01:2011";
String dateB = "11:02:2011"; String dateB = "11:02:2011";
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/path1/path2"); MockHttpServletRequest request = new MockHttpServletRequest("POST", "/path1/path2");
request.setParameter("datePattern", datePattern); request.setParameter("datePattern", datePattern);
request.addHeader("header1", dateA); request.addHeader("header1", dateA);
request.addHeader("header2", dateB); request.addHeader("header2", dateB);
HandlerExecutionChain chain = handlerMapping.getHandler(request); HandlerExecutionChain chain = handlerMapping.getHandler(request);
assertNotNull(chain); assertNotNull(chain);
@ -133,7 +133,7 @@ public class HandlerMethodAnnotationDetectionTests {
assertEquals("failure", response.getContentAsString()); assertEquals("failure", response.getContentAsString());
} }
/** /**
* SIMPLE CASE * SIMPLE CASE
*/ */
@ -156,15 +156,15 @@ public class HandlerMethodAnnotationDetectionTests {
public Date handle(@RequestHeader("header2") Date date) throws Exception { public Date handle(@RequestHeader("header2") Date date) throws Exception {
return date; return date;
} }
@ExceptionHandler(Exception.class) @ExceptionHandler(Exception.class)
@ResponseBody @ResponseBody
public String handleException(Exception exception) { public String handleException(Exception exception) {
return exception.getMessage(); return exception.getMessage();
} }
} }
@Controller @Controller
static abstract class MappingAbstractClass { static abstract class MappingAbstractClass {
@ -177,15 +177,15 @@ public class HandlerMethodAnnotationDetectionTests {
@RequestMapping(value="/path1/path2", method=RequestMethod.POST) @RequestMapping(value="/path1/path2", method=RequestMethod.POST)
@ModelAttribute("attr2") @ModelAttribute("attr2")
public abstract Date handle(Date date, Model model) throws Exception; public abstract Date handle(Date date, Model model) throws Exception;
@ExceptionHandler(Exception.class) @ExceptionHandler(Exception.class)
@ResponseBody @ResponseBody
public abstract String handleException(Exception exception); public abstract String handleException(Exception exception);
} }
/** /**
* CONTROLLER WITH ABSTRACT CLASS * CONTROLLER WITH ABSTRACT CLASS
* *
* <p>All annotations can be on methods in the abstract class except parameter annotations. * <p>All annotations can be on methods in the abstract class except parameter annotations.
*/ */
static class AbstractClassController extends MappingAbstractClass { static class AbstractClassController extends MappingAbstractClass {
@ -202,14 +202,15 @@ public class HandlerMethodAnnotationDetectionTests {
public Date handle(@RequestHeader("header2") Date date, Model model) throws Exception { public Date handle(@RequestHeader("header2") Date date, Model model) throws Exception {
return date; return date;
} }
public String handleException(Exception exception) { public String handleException(Exception exception) {
return exception.getMessage(); return exception.getMessage();
} }
} }
// SPR-9374
@Controller
@RequestMapping
static interface MappingInterface { static interface MappingInterface {
@InitBinder @InitBinder
@ -221,15 +222,15 @@ public class HandlerMethodAnnotationDetectionTests {
@RequestMapping(value="/path1/path2", method=RequestMethod.POST) @RequestMapping(value="/path1/path2", method=RequestMethod.POST)
@ModelAttribute("attr2") @ModelAttribute("attr2")
Date handle(@RequestHeader("header2") Date date, Model model) throws Exception; Date handle(@RequestHeader("header2") Date date, Model model) throws Exception;
@ExceptionHandler(Exception.class) @ExceptionHandler(Exception.class)
@ResponseBody @ResponseBody
String handleException(Exception exception); String handleException(Exception exception);
} }
/** /**
* CONTROLLER WITH INTERFACE * CONTROLLER WITH INTERFACE
* *
* No AOP: * No AOP:
* All annotations can be on interface methods except parameter annotations. * All annotations can be on interface methods except parameter annotations.
* *
@ -250,7 +251,7 @@ public class HandlerMethodAnnotationDetectionTests {
public Date handle(@RequestHeader("header2") Date date, Model model) throws Exception { public Date handle(@RequestHeader("header2") Date date, Model model) throws Exception {
return date; return date;
} }
public String handleException(Exception exception) { public String handleException(Exception exception) {
return exception.getMessage(); return exception.getMessage();
} }
@ -269,15 +270,15 @@ public class HandlerMethodAnnotationDetectionTests {
@RequestMapping(value="/path1/path2", method=RequestMethod.POST) @RequestMapping(value="/path1/path2", method=RequestMethod.POST)
@ModelAttribute("attr2") @ModelAttribute("attr2")
public abstract Date handle(C date, Model model) throws Exception; public abstract Date handle(C date, Model model) throws Exception;
@ExceptionHandler(Exception.class) @ExceptionHandler(Exception.class)
@ResponseBody @ResponseBody
public abstract String handleException(Exception exception); public abstract String handleException(Exception exception);
} }
/** /**
* CONTROLLER WITH PARAMETERIZED BASE CLASS * CONTROLLER WITH PARAMETERIZED BASE CLASS
* *
* <p>All annotations can be on methods in the abstract class except parameter annotations. * <p>All annotations can be on methods in the abstract class except parameter annotations.
*/ */
static class ParameterizedAbstractClassController extends MappingParameterizedAbstractClass<String, Date, Date> { static class ParameterizedAbstractClassController extends MappingParameterizedAbstractClass<String, Date, Date> {
@ -294,14 +295,13 @@ public class HandlerMethodAnnotationDetectionTests {
public Date handle(@RequestHeader("header2") Date date, Model model) throws Exception { public Date handle(@RequestHeader("header2") Date date, Model model) throws Exception {
return date; return date;
} }
public String handleException(Exception exception) { public String handleException(Exception exception) {
return exception.getMessage(); return exception.getMessage();
} }
} }
@RequestMapping
@Controller
static interface MappingParameterizedInterface<A, B, C> { static interface MappingParameterizedInterface<A, B, C> {
@InitBinder @InitBinder
@ -313,17 +313,17 @@ public class HandlerMethodAnnotationDetectionTests {
@RequestMapping(value="/path1/path2", method=RequestMethod.POST) @RequestMapping(value="/path1/path2", method=RequestMethod.POST)
@ModelAttribute("attr2") @ModelAttribute("attr2")
Date handle(C date, Model model) throws Exception; Date handle(C date, Model model) throws Exception;
@ExceptionHandler(Exception.class) @ExceptionHandler(Exception.class)
@ResponseBody @ResponseBody
String handleException(Exception exception); String handleException(Exception exception);
} }
/** /**
* CONTROLLER WITH PARAMETERIZED INTERFACE * CONTROLLER WITH PARAMETERIZED INTERFACE
* *
* <p>All annotations can be on interface except parameter annotations. * <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. * <p>Cannot be used as JDK dynamic proxy since parameterized interface does not contain type information.
*/ */
static class ParameterizedInterfaceController implements MappingParameterizedInterface<String, Date, Date> { static class ParameterizedInterfaceController implements MappingParameterizedInterface<String, Date, Date> {
@ -344,18 +344,18 @@ public class HandlerMethodAnnotationDetectionTests {
public Date handle(@RequestHeader("header2") Date date, Model model) throws Exception { public Date handle(@RequestHeader("header2") Date date, Model model) throws Exception {
return date; return date;
} }
@ExceptionHandler(Exception.class) @ExceptionHandler(Exception.class)
@ResponseBody @ResponseBody
public String handleException(Exception exception) { public String handleException(Exception exception) {
return exception.getMessage(); return exception.getMessage();
} }
} }
/** /**
* SPR-8248 * SPR-8248
* *
* <p>Support class contains all annotations. Subclass has type-level @{@link RequestMapping}. * <p>Support class contains all annotations. Subclass has type-level @{@link RequestMapping}.
*/ */
@Controller @Controller
@ -377,17 +377,17 @@ public class HandlerMethodAnnotationDetectionTests {
public Date handle(@RequestHeader("header2") Date date, Model model) throws Exception { public Date handle(@RequestHeader("header2") Date date, Model model) throws Exception {
return date; return date;
} }
@ExceptionHandler(Exception.class) @ExceptionHandler(Exception.class)
@ResponseBody @ResponseBody
public String handleException(Exception exception) { public String handleException(Exception exception) {
return exception.getMessage(); return exception.getMessage();
} }
} }
@Controller @Controller
@RequestMapping("/path1") @RequestMapping("/path1")
static class SupportClassController extends MappingSupportClass { static class SupportClassController extends MappingSupportClass {
} }
} }