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:
parent
c499df2315
commit
f61f4a960e
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue