Browse Source

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: f61f4a960e
3.1.x
Rossen Stoyanchev 14 years ago
parent
commit
2fa0e63e5a
  1. 37
      org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java
  2. 102
      org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HandlerMethodAnnotationDetectionTests.java

37
org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java

@ -1,5 +1,5 @@ @@ -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; @@ -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 @@ -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.*".
* <p>The default value is {@code true}.
* <p>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 @@ -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 @@ -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 @@ -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);
}

102
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; @@ -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<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[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 { @@ -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 { @@ -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 { @@ -133,7 +133,7 @@ public class HandlerMethodAnnotationDetectionTests {
assertEquals("failure", response.getContentAsString());
}
/**
* SIMPLE CASE
*/
@ -156,15 +156,15 @@ public class HandlerMethodAnnotationDetectionTests { @@ -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 { @@ -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
*
*
* <p>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 { @@ -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 { @@ -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 { @@ -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 { @@ -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
*
*
* <p>All annotations can be on methods in the abstract class except parameter annotations.
*/
static class ParameterizedAbstractClassController extends MappingParameterizedAbstractClass<String, Date, Date> {
@ -294,14 +295,13 @@ public class HandlerMethodAnnotationDetectionTests { @@ -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<A, B, C> {
@InitBinder
@ -313,17 +313,17 @@ public class HandlerMethodAnnotationDetectionTests { @@ -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
*
*
* <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> {
@ -344,18 +344,18 @@ public class HandlerMethodAnnotationDetectionTests { @@ -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
*
*
* <p>Support class contains all annotations. Subclass has type-level @{@link RequestMapping}.
*/
@Controller
@ -377,17 +377,17 @@ public class HandlerMethodAnnotationDetectionTests { @@ -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 {
}
}
}

Loading…
Cancel
Save