diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/DefaultAnnotationHandlerMapping.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/DefaultAnnotationHandlerMapping.java index 797e1735f16..968113e8aaf 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/DefaultAnnotationHandlerMapping.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/DefaultAnnotationHandlerMapping.java @@ -37,47 +37,37 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.servlet.handler.AbstractDetectingUrlHandlerMapping; /** - * Implementation of the {@link org.springframework.web.servlet.HandlerMapping} - * interface that maps handlers based on HTTP paths expressed through the - * {@link RequestMapping} annotation at the type or method level. + * Implementation of the {@link org.springframework.web.servlet.HandlerMapping} interface that maps handlers based on + * HTTP paths expressed through the {@link RequestMapping} annotation at the type or method level. * - *
Registered by default in {@link org.springframework.web.servlet.DispatcherServlet} - * on Java 5+. NOTE: If you define custom HandlerMapping beans in your - * DispatcherServlet context, you need to add a DefaultAnnotationHandlerMapping bean - * explicitly, since custom HandlerMapping beans replace the default mapping strategies. - * Defining a DefaultAnnotationHandlerMapping also allows for registering custom - * interceptors: + *
Registered by default in {@link org.springframework.web.servlet.DispatcherServlet} on Java 5+. NOTE: If you + * define custom HandlerMapping beans in your DispatcherServlet context, you need to add a + * DefaultAnnotationHandlerMapping bean explicitly, since custom HandlerMapping beans replace the default mapping + * strategies. Defining a DefaultAnnotationHandlerMapping also allows for registering custom interceptors: * - *
- * <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"> - * <property name="interceptors"> - * ... - * </property> - * </bean>+ *
<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"> + * <property name="interceptors"> ... </property> </bean>* - * Annotated controllers are usually marked with the {@link Controller} stereotype - * at the type level. This is not strictly necessary when {@link RequestMapping} is - * applied at the type level (since such a handler usually implements the - * {@link org.springframework.web.servlet.mvc.Controller} interface). However, - * {@link Controller} is required for detecting {@link RequestMapping} annotations - * at the method level if {@link RequestMapping} is not present at the type level. + * Annotated controllers are usually marked with the {@link Controller} stereotype at the type level. This is not + * strictly necessary when {@link RequestMapping} is applied at the type level (since such a handler usually implements + * the {@link org.springframework.web.servlet.mvc.Controller} interface). However, {@link Controller} is required for + * detecting {@link RequestMapping} annotations at the method level if {@link RequestMapping} is not present at the type + * level. * - *
NOTE: Method-level mappings are only allowed to narrow the mapping - * expressed at the class level (if any). HTTP paths need to uniquely map onto - * specific handler beans, with any given HTTP path only allowed to be mapped - * onto one specific handler bean (not spread across multiple handler beans). - * It is strongly recommended to co-locate related handler methods into the same bean. + *
NOTE: Method-level mappings are only allowed to narrow the mapping expressed at the class level (if any). + * HTTP paths need to uniquely map onto specific handler beans, with any given HTTP path only allowed to be mapped onto + * one specific handler bean (not spread across multiple handler beans). It is strongly recommended to co-locate related + * handler methods into the same bean. * - *
The {@link AnnotationMethodHandlerAdapter} is responsible for processing - * annotated handler methods, as mapped by this HandlerMapping. For - * {@link RequestMapping} at the type level, specific HandlerAdapters such as - * {@link org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter} apply. + *
The {@link AnnotationMethodHandlerAdapter} is responsible for processing annotated handler methods, as mapped by
+ * this HandlerMapping. For {@link RequestMapping} at the type level, specific HandlerAdapters such as {@link
+ * org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter} apply.
*
* @author Juergen Hoeller
* @author Arjen Poutsma
- * @since 2.5
* @see RequestMapping
* @see AnnotationMethodHandlerAdapter
+ * @since 2.5
*/
public class DefaultAnnotationHandlerMapping extends AbstractDetectingUrlHandlerMapping {
@@ -85,23 +75,19 @@ public class DefaultAnnotationHandlerMapping extends AbstractDetectingUrlHandler
private final Map Default is "true". Turn this convention off if you intend to interpret
- * your Note that paths which include a ".xxx" suffix or end with "/" already will not be
- * transformed using the default suffix pattern in any case.
+ * Set whether to register paths using the default suffix pattern as well: i.e. whether "/users" should be registered
+ * as "/users.*" and "/users/" too. Default is "true". Turn this convention off if you intend to interpret your
+ * Note that paths which include a ".xxx" suffix or end with "/"
+ * already will not be transformed using the default suffix pattern in any case.
*/
public void setUseDefaultSuffixPattern(boolean useDefaultSuffixPattern) {
this.useDefaultSuffixPattern = useDefaultSuffixPattern;
}
-
/**
- * Checks for presence of the {@link org.springframework.web.bind.annotation.RequestMapping}
- * annotation on the handler class and on any of its methods.
+ * Checks for presence of the {@link org.springframework.web.bind.annotation.RequestMapping} annotation on the handler
+ * class and on any of its methods.
*/
@Override
protected String[] determineUrlsForHandler(String beanName) {
@@ -120,11 +106,19 @@ public class DefaultAnnotationHandlerMapping extends AbstractDetectingUrlHandler
if (!typeLevelPattern.startsWith("/")) {
typeLevelPattern = "/" + typeLevelPattern;
}
+ boolean hasEmptyMethodLevelMappings = false;
for (String methodLevelPattern : methodLevelPatterns) {
- String combinedPattern = getPathMatcher().combine(typeLevelPattern, methodLevelPattern);
- addUrlsForPath(urls, combinedPattern);
+ if (methodLevelPattern == null) {
+ hasEmptyMethodLevelMappings = true;
+ } else {
+ String combinedPattern = getPathMatcher().combine(typeLevelPattern, methodLevelPattern);
+ addUrlsForPath(urls, combinedPattern);
+ }
+ }
+ if (hasEmptyMethodLevelMappings ||
+ org.springframework.web.servlet.mvc.Controller.class.isAssignableFrom(handlerType)) {
+ addUrlsForPath(urls, typeLevelPattern);
}
- addUrlsForPath(urls, typeLevelPattern);
}
return StringUtils.toStringArray(urls);
}
@@ -144,6 +138,9 @@ public class DefaultAnnotationHandlerMapping extends AbstractDetectingUrlHandler
/**
* Derive URL mappings from the handler's method-level mappings.
+ *
+ * The returned array may contain {@code null}, indicating an empty {@link RequestMapping} value.
+ *
* @param handlerType the handler type to introspect
* @return the array of mapped URLs
*/
@@ -156,19 +153,25 @@ public class DefaultAnnotationHandlerMapping extends AbstractDetectingUrlHandler
public void doWith(Method method) {
RequestMapping mapping = AnnotationUtils.findAnnotation(method, RequestMapping.class);
if (mapping != null) {
- String[] mappedPaths = mapping.value();
- for (String mappedPath : mappedPaths) {
- addUrlsForPath(urls, mappedPath);
+ String[] mappedPatterns = mapping.value();
+ if (mappedPatterns.length > 0) {
+ for (String mappedPattern : mappedPatterns) {
+ addUrlsForPath(urls, mappedPattern);
+ }
+ } else {
+ // empty method-level RequestMapping
+ urls.add(null);
}
}
}
- });
+ }, ReflectionUtils.NON_BRIDGED_METHODS);
}
return StringUtils.toStringArray(urls);
}
/**
* Add URLs and/or URL patterns for the given path.
+ *
* @param urls the Set of URLs for the current bean
* @param path the currently introspected path
*/
@@ -180,9 +183,9 @@ public class DefaultAnnotationHandlerMapping extends AbstractDetectingUrlHandler
}
}
-
/**
* Validate the given annotated handler against the current request.
+ *
* @see #validateMapping
*/
@Override
@@ -197,8 +200,9 @@ public class DefaultAnnotationHandlerMapping extends AbstractDetectingUrlHandler
}
/**
- * Validate the given type-level mapping metadata against the current request,
- * checking HTTP request method and parameter conditions.
+ * Validate the given type-level mapping metadata against the current request, checking HTTP request method and
+ * parameter conditions.
+ *
* @param mapping the mapping metadata to validate
* @param request current HTTP request
* @throws Exception if validation failed
@@ -220,9 +224,9 @@ public class DefaultAnnotationHandlerMapping extends AbstractDetectingUrlHandler
String[] mappedHeaders = mapping.headers();
if (!ServletAnnotationMappingUtils.checkHeaders(mappedHeaders, request)) {
- throw new ServletRequestBindingException("Header conditions \"" +
- StringUtils.arrayToDelimitedString(mappedHeaders, ", ") +
- "\" not met for actual request");
+ throw new ServletRequestBindingException(
+ "Header conditions \"" + StringUtils.arrayToDelimitedString(mappedHeaders, ", ") +
+ "\" not met for actual request");
}
}
diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/UriTemplateServletAnnotationControllerTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/UriTemplateServletAnnotationControllerTests.java
index 6c079491c80..8b0ca88b607 100644
--- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/UriTemplateServletAnnotationControllerTests.java
+++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/UriTemplateServletAnnotationControllerTests.java
@@ -308,6 +308,19 @@ public class UriTemplateServletAnnotationControllerTests {
assertEquals("test-42", response.getContentAsString());
}
+ /*
+ * See SPR-6640
+ */
+ @Test
+ public void menuTree() throws Exception {
+ initServlet(MenuTreeController.class);
+
+ MockHttpServletRequest request = new MockHttpServletRequest("GET", "/book/menu/type/M5");
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ servlet.service(request, response);
+ assertEquals("M5", response.getContentAsString());
+ }
+
/*
* Controllers
@@ -533,4 +546,14 @@ public class UriTemplateServletAnnotationControllerTests {
}
+ @RequestMapping("/*/menu/**")
+ public static class MenuTreeController {
+
+ @RequestMapping("type/{var}")
+ public void getFirstLevelFunctionNodes(@PathVariable("var") String var, Writer writer) throws IOException {
+ writer.write(var);
+ }
+ }
+
+
}
@RequestMapping paths strictly.
- * @RequestMapping paths strictly.