From 2fa0e63e5ab84ac964c55a28000104d3855c886c Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Thu, 10 May 2012 16:19:14 -0400 Subject: [PATCH] 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 Backport-Issue: SPR-9384 Backport-Commit: f61f4a960e1b28e96abe8912a137720fcd0690f5 --- .../RequestMappingHandlerMapping.java | 37 +++---- ...HandlerMethodAnnotationDetectionTests.java | 102 +++++++++--------- 2 files changed, 70 insertions(+), 69 deletions(-) diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java index 8301ce7c4df..d1c55b7f02e 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -32,8 +32,8 @@ import org.springframework.web.servlet.mvc.method.RequestMappingInfo; import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping; /** - * Creates {@link RequestMappingInfo} instances from type and method-level - * {@link RequestMapping @RequestMapping} annotations in + * Creates {@link RequestMappingInfo} instances from type and method-level + * {@link RequestMapping @RequestMapping} annotations in * {@link Controller @Controller} classes. * * @author Arjen Poutsma @@ -45,16 +45,16 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi private boolean useSuffixPatternMatch = true; private boolean useTrailingSlashMatch = true; - + /** * Whether to use suffix pattern match (".*") when matching patterns to * requests. If enabled a method mapped to "/users" also matches to "/users.*". - *

The default value is {@code true}. + *

The default value is {@code true}. */ public void setUseSuffixPatternMatch(boolean useSuffixPatternMatch) { this.useSuffixPatternMatch = useSuffixPatternMatch; } - + /** * Whether to match to URLs irrespective of the presence of a trailing slash. * If enabled a method mapped to "/users" also matches to "/users/". @@ -78,21 +78,22 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi } /** - * {@inheritDoc} + * {@inheritDoc} * Expects a handler to have a type-level @{@link Controller} annotation. */ @Override 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)); } /** * Uses method and type-level @{@link RequestMapping} annotations to create * the RequestMappingInfo. - * + * * @return the created RequestMappingInfo, or {@code null} if the method * does not have a {@code @RequestMapping} annotation. - * + * * @see #getCustomMethodCondition(Method) * @see #getCustomTypeCondition(Class) */ @@ -114,22 +115,22 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi /** * Provide a custom method-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 * same condition type is returned from all calls to this method in order - * to ensure custom request conditions can be combined and compared. + * to ensure custom request conditions can be combined and compared. * @param method the handler method for which to create the condition * @return the condition, or {@code null} */ protected RequestCondition getCustomMethodCondition(Method method) { return null; } - + /** * 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 * same condition type is returned from all calls to this method in order - * to ensure custom request conditions can be combined and compared. - * @param method the handler method for which to create the condition + * to ensure custom request conditions can be combined and compared. + * @param handlerType the handler type for which to create the condition * @return the condition, or {@code null} */ protected RequestCondition getCustomTypeCondition(Class handlerType) { @@ -141,13 +142,13 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi */ private RequestMappingInfo createRequestMappingInfo(RequestMapping annotation, RequestCondition customCondition) { return new RequestMappingInfo( - new PatternsRequestCondition(annotation.value(), + new PatternsRequestCondition(annotation.value(), getUrlPathHelper(), getPathMatcher(), this.useSuffixPatternMatch, this.useTrailingSlashMatch), new RequestMethodsRequestCondition(annotation.method()), new ParamsRequestCondition(annotation.params()), new HeadersRequestCondition(annotation.headers()), new ConsumesRequestCondition(annotation.consumes(), annotation.headers()), - new ProducesRequestCondition(annotation.produces(), annotation.headers()), + new ProducesRequestCondition(annotation.produces(), annotation.headers()), customCondition); } diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HandlerMethodAnnotationDetectionTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HandlerMethodAnnotationDetectionTests.java index e624933bdd8..3816f4b5de8 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HandlerMethodAnnotationDetectionTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HandlerMethodAnnotationDetectionTests.java @@ -51,37 +51,37 @@ import org.springframework.web.servlet.HandlerExecutionChain; import org.springframework.web.servlet.ModelAndView; /** - * 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 + * Test various scenarios for detecting method-level and method parameter annotations depending + * on where they are located -- on interfaces, parent classes, in parameterized methods, or in * combination with proxies. - * + * * @author Rossen Stoyanchev */ @RunWith(Parameterized.class) public class HandlerMethodAnnotationDetectionTests { - + @Parameters public static Collection handlerTypes() { Object[][] array = new Object[12][2]; array[0] = new Object[] { SimpleController.class, true}; // CGLib proxy array[1] = new Object[] { SimpleController.class, false}; - + array[2] = new Object[] { AbstractClassController.class, true }; // CGLib proxy array[3] = new Object[] { AbstractClassController.class, false }; - - array[4] = new Object[] { ParameterizedAbstractClassController.class, false}; // CGLib proxy - array[5] = new Object[] { ParameterizedAbstractClassController.class, false}; - + + array[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[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); } @@ -101,7 +101,7 @@ public class HandlerMethodAnnotationDetectionTests { context.getBeanFactory().registerSingleton("advisor", new DefaultPointcutAdvisor(new SimpleTraceInterceptor())); } context.refresh(); - + handlerMapping.setApplicationContext(context); handlerAdapter.afterPropertiesSet(); exceptionResolver.afterPropertiesSet(); @@ -113,12 +113,12 @@ public class HandlerMethodAnnotationDetectionTests { SimpleDateFormat dateFormat = new SimpleDateFormat(datePattern); String dateA = "11:01:2011"; String dateB = "11:02:2011"; - + MockHttpServletRequest request = new MockHttpServletRequest("POST", "/path1/path2"); request.setParameter("datePattern", datePattern); request.addHeader("header1", dateA); request.addHeader("header2", dateB); - + HandlerExecutionChain chain = handlerMapping.getHandler(request); assertNotNull(chain); @@ -133,7 +133,7 @@ public class HandlerMethodAnnotationDetectionTests { assertEquals("failure", response.getContentAsString()); } - + /** * SIMPLE CASE */ @@ -156,15 +156,15 @@ public class HandlerMethodAnnotationDetectionTests { public Date handle(@RequestHeader("header2") Date date) throws Exception { return date; } - + @ExceptionHandler(Exception.class) @ResponseBody public String handleException(Exception exception) { return exception.getMessage(); } - } + } + - @Controller static abstract class MappingAbstractClass { @@ -177,15 +177,15 @@ public class HandlerMethodAnnotationDetectionTests { @RequestMapping(value="/path1/path2", method=RequestMethod.POST) @ModelAttribute("attr2") public abstract Date handle(Date date, Model model) throws Exception; - + @ExceptionHandler(Exception.class) @ResponseBody public abstract String handleException(Exception exception); - } + } /** * CONTROLLER WITH ABSTRACT CLASS - * + * *

All annotations can be on methods in the abstract class except parameter annotations. */ static class AbstractClassController extends MappingAbstractClass { @@ -202,14 +202,15 @@ public class HandlerMethodAnnotationDetectionTests { public Date handle(@RequestHeader("header2") Date date, Model model) throws Exception { return date; } - + public String handleException(Exception exception) { return exception.getMessage(); } } - - - @Controller + + // SPR-9374 + + @RequestMapping static interface MappingInterface { @InitBinder @@ -221,15 +222,15 @@ public class HandlerMethodAnnotationDetectionTests { @RequestMapping(value="/path1/path2", method=RequestMethod.POST) @ModelAttribute("attr2") Date handle(@RequestHeader("header2") 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. * @@ -250,7 +251,7 @@ public class HandlerMethodAnnotationDetectionTests { public Date handle(@RequestHeader("header2") Date date, Model model) throws Exception { return date; } - + public String handleException(Exception exception) { return exception.getMessage(); } @@ -269,15 +270,15 @@ public class HandlerMethodAnnotationDetectionTests { @RequestMapping(value="/path1/path2", method=RequestMethod.POST) @ModelAttribute("attr2") public abstract Date handle(C date, Model model) throws Exception; - + @ExceptionHandler(Exception.class) @ResponseBody public abstract String handleException(Exception exception); - } + } /** * CONTROLLER WITH PARAMETERIZED BASE CLASS - * + * *

All annotations can be on methods in the abstract class except parameter annotations. */ static class ParameterizedAbstractClassController extends MappingParameterizedAbstractClass { @@ -294,14 +295,13 @@ public class HandlerMethodAnnotationDetectionTests { public Date handle(@RequestHeader("header2") Date date, Model model) throws Exception { return date; } - + public String handleException(Exception exception) { return exception.getMessage(); } } - - @Controller + @RequestMapping static interface MappingParameterizedInterface { @InitBinder @@ -313,17 +313,17 @@ public class HandlerMethodAnnotationDetectionTests { @RequestMapping(value="/path1/path2", method=RequestMethod.POST) @ModelAttribute("attr2") Date handle(C date, Model model) throws Exception; - + @ExceptionHandler(Exception.class) @ResponseBody String handleException(Exception exception); - } + } /** * CONTROLLER WITH PARAMETERIZED INTERFACE - * + * *

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

Cannot be used as JDK dynamic proxy since parameterized interface does not contain type information. */ static class ParameterizedInterfaceController implements MappingParameterizedInterface { @@ -344,18 +344,18 @@ public class HandlerMethodAnnotationDetectionTests { public Date handle(@RequestHeader("header2") Date date, Model model) throws Exception { return date; } - + @ExceptionHandler(Exception.class) @ResponseBody public String handleException(Exception exception) { return exception.getMessage(); } - } - - + } + + /** * SPR-8248 - * + * *

Support class contains all annotations. Subclass has type-level @{@link RequestMapping}. */ @Controller @@ -377,17 +377,17 @@ public class HandlerMethodAnnotationDetectionTests { public Date handle(@RequestHeader("header2") Date date, Model model) throws Exception { return date; } - + @ExceptionHandler(Exception.class) @ResponseBody public String handleException(Exception exception) { return exception.getMessage(); } - } + } @Controller @RequestMapping("/path1") static class SupportClassController extends MappingSupportClass { - } + } }