diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java
index ac0ad594452..09819b692c1 100644
--- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java
@@ -36,8 +36,22 @@ import org.springframework.web.method.HandlerMethodSelector;
/**
* Abstract base class for {@link org.springframework.web.servlet.HandlerMapping HandlerMapping} implementations that
- * support {@link HandlerMethod}s.
+ * support mapping requests to {@link HandlerMethod}s rather than to handlers.
+ *
+ *
Each {@link HandlerMethod} is registered with a unique key. Subclasses define the key type and how to create it
+ * for a given handler method. Keys represent conditions for matching a handler method to a request.
*
+ *
Subclasses must also define how to create a key for an incoming request. The resulting key is used to perform
+ * a {@link HandlerMethod} lookup possibly resulting in a direct match. However, when a map lookup is insufficient,
+ * the keys of all handler methods are iterated and subclasses are allowed to make an exhaustive check of key
+ * conditions against the request.
+ *
+ *
Since there can be more than one matching key for a request, subclasses must define a comparator for sorting
+ * the keys of matching handler methods in order to find the most specific match.
+ *
+ * @param A unique key for the registration of mapped {@link HandlerMethod}s representing the conditions to
+ * match a handler method to a request.
+ *
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @since 3.1
@@ -80,6 +94,9 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap
*/
protected abstract boolean isHandler(String beanName);
+ /**
+ * Detect and register handler methods for the specified handler.
+ */
private void detectHandlerMethods(final String handlerName) {
Class> handlerType = getApplicationContext().getType(handlerName);
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestKey.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestKey.java
index e4918d748da..7e459718290 100644
--- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestKey.java
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestKey.java
@@ -33,10 +33,18 @@ import org.springframework.web.servlet.mvc.method.condition.RequestConditionFact
import org.springframework.web.util.UrlPathHelper;
/**
- * TODO
+ * Contains a set of conditions to match to a given request such as URL patterns, HTTP methods, request
+ * parameters and headers.
+ *
+ * A {@link RequestKey} can be combined with another {@link RequestKey} resulting in a new {@link RequestKey}
+ * with conditions from both (see {@link #combine(RequestKey, PathMatcher)}).
+ *
+ *
A {@link RequestKey} can be matched to a request resulting in a new {@link RequestKey} with the subset of
+ * conditions relevant to the request (see {@link #getMatchingKey(HttpServletRequest, PathMatcher, UrlPathHelper)}).
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
+ * @since 3.1
*/
public final class RequestKey {
@@ -52,14 +60,19 @@ public final class RequestKey {
private int hash;
+ /**
+ * Creates a new {@code RequestKey} instance with the given URL patterns and HTTP methods.
+ *
+ *
Package protected for testing purposes.
+ */
RequestKey(Collection patterns, Collection methods) {
this(patterns, methods, null, null, null);
}
/**
- * Creates a new {@code RequestKey} instance with the given parameters.
- *
- * Package protected for testing purposes.
+ * Creates a new {@code RequestKey} instance with a full set of conditions.
+ *
+ * Package protected for testing purposes.
*/
RequestKey(Collection patterns,
Collection methods,
@@ -152,12 +165,22 @@ public final class RequestKey {
}
/**
- * Combines this {@code RequestKey} with another. The typical use case for this is combining type
- * and method-level {@link RequestMapping @RequestMapping} annotations.
- *
- * @param methodKey the method-level RequestKey
+ * Combines this {@code RequestKey} with another as follows:
+ *
+ * - URL patterns:
+ *
+ * - If both keys have path patterns combine them according to the rules of the given {@link PathMatcher}.
+ *
- If either key contains path patterns but not both use only what is available.
+ *
- If neither key contains path patterns use "/".
+ *
+ * - HTTP methods are combined as union of all HTTP methods listed in both keys.
+ *
- Request parameter are combined into a logical AND.
+ *
- Request header are combined into a logical AND.
+ *
- Consumes .. TODO
+ *
+ * @param methodKey the key to combine with
* @param pathMatcher to {@linkplain PathMatcher#combine(String, String) combine} the patterns
- * @return the combined request key
+ * @return a new request key containing conditions from both keys
*/
public RequestKey combine(RequestKey methodKey, PathMatcher pathMatcher) {
Set patterns = combinePatterns(this.patterns, methodKey.patterns, pathMatcher);
@@ -199,15 +222,18 @@ public final class RequestKey {
}
/**
- * Returns a new {@code RequestKey} that contains all matching attributes of this key, given the {@link
- * HttpServletRequest}. Matching patterns in the returned RequestKey are sorted according to {@link
- * PathMatcher#getPatternComparator(String)} with the best matching pattern at the top.
- *
- * @param request the servlet request
- * @param pathMatcher to {@linkplain PathMatcher#match(String, String) match} patterns
- * @param urlPathHelper to create the {@linkplain UrlPathHelper#getLookupPathForRequest(HttpServletRequest) lookup
- * path}
- * @return a new request key that contains all matching attributes
+ * Returns a new {@code RequestKey} that contains all conditions of this key that are relevant to the request.
+ *
+ * - The list of URL path patterns is trimmed to contain the patterns that match the URL with matching patterns
+ * sorted via {@link PathMatcher#getPatternComparator(String)}.
+ *
- The list of HTTP methods is trimmed to contain only the method of the request.
+ *
- Request parameter and request header conditions are included in full.
+ *
- The list of consumes conditions is trimmed and sorted to match the request "Content-Type" header.
+ *
+ * @param request the current request
+ * @param pathMatcher to check for matching patterns
+ * @param urlPathHelper to derive the lookup path for the request
+ * @return a new request key that contains all matching attributes, or {@code null} if not all conditions match
*/
public RequestKey getMatchingKey(HttpServletRequest request, PathMatcher pathMatcher, UrlPathHelper urlPathHelper) {
if (!checkMethod(request) || !paramsCondition.match(request) || !headersCondition.match(request) ||
@@ -217,7 +243,7 @@ public final class RequestKey {
else {
List matchingPatterns = getMatchingPatterns(request, pathMatcher, urlPathHelper);
if (!matchingPatterns.isEmpty()) {
- Set matchingMethods = getMatchingMethods(request);
+ Set matchingMethods = getMatchingMethod(request);
return new RequestKey(matchingPatterns, matchingMethods, this.paramsCondition, this.headersCondition,
this.consumesCondition);
}
@@ -245,7 +271,7 @@ public final class RequestKey {
return matchingPatterns;
}
- private Set getMatchingMethods(HttpServletRequest request) {
+ private Set getMatchingMethod(HttpServletRequest request) {
if (this.methods.isEmpty()) {
return this.methods;
}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMethodMapping.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMethodMapping.java
index e6c0f30d8a9..c602fd87ae3 100644
--- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMethodMapping.java
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMethodMapping.java
@@ -23,6 +23,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
+
import javax.servlet.http.HttpServletRequest;
import org.springframework.core.annotation.AnnotationUtils;
@@ -44,7 +45,22 @@ import org.springframework.web.servlet.handler.MappedInterceptors;
import org.springframework.web.util.UrlPathHelper;
/**
- * TODO
+ * An {@link AbstractHandlerMethodMapping} variant that uses {@link RequestKey}s for the registration and the lookup
+ * of {@link HandlerMethod}s.
+ *
+ * A {@link RequestKey} for an incoming request contains the URL and the HTTP method of the request.
+ * A {@link RequestKey} for a handler method contains all conditions found in the method @{@link RequestMapping}
+ * annotation combined with all conditions found in the type @{@link RequestMapping} annotation, if present.
+ *
+ *
An incoming request matches to a handler method directly when a @{@link RequestMapping} annotation contains
+ * a single, non-pattern URL and a single HTTP method. When a {@link RequestKey} contains additional conditions
+ * (e.g. more URL patterns, request parameters, headers, etc) those conditions must be checked against the
+ * request rather than against the key that represents it. This results in the creation of a new handler method
+ * {@link RequestKey} with the subset of conditions relevant to the current request (see
+ * {@link RequestKey#getMatchingKey(HttpServletRequest, PathMatcher, UrlPathHelper)}).
+ * Such keys can then be compared against each other, in the context of the current request, making it possible
+ * to select to the best matching {@link RequestKey} in case of multiple matches and also the best matching
+ * pattern within the selected key.
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
@@ -168,6 +184,18 @@ public class RequestMappingHandlerMethodMapping extends AbstractHandlerMethodMap
}
}
+ /**
+ * Returns a new {@link RequestKey} with attributes matching to the current request or {@code null}.
+ * @see RequestKey#getMatchingKey(HttpServletRequest, PathMatcher, UrlPathHelper)
+ */
+ @Override
+ protected RequestKey getMatchingKey(RequestKey key, HttpServletRequest request) {
+ return key.getMatchingKey(request, pathMatcher, urlPathHelper);
+ }
+
+ /**
+ * Returns a {@link Comparator} that can be used to sort and select the best matching {@link RequestKey}.
+ */
@Override
protected Comparator getKeyComparator(HttpServletRequest request) {
return new RequestKeyComparator(request);
@@ -181,24 +209,10 @@ public class RequestMappingHandlerMethodMapping extends AbstractHandlerMethodMap
request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVariables);
}
- @Override
- protected RequestKey getMatchingKey(RequestKey key, HttpServletRequest request) {
- return key.getMatchingKey(request, pathMatcher, urlPathHelper);
- }
-
- @Override
- protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
- HandlerExecutionChain chain = super.getHandlerExecutionChain(handler, request);
- if (this.mappedInterceptors != null) {
- String lookupPath = urlPathHelper.getLookupPathForRequest(request);
- HandlerInterceptor[] handlerInterceptors = mappedInterceptors.getInterceptors(lookupPath, pathMatcher);
- if (handlerInterceptors.length > 0) {
- chain.addInterceptors(handlerInterceptors);
- }
- }
- return chain;
- }
-
+ /**
+ * Iterates all {@link RequestKey}s looking for keys that match by URL but not by HTTP method.
+ * @exception HttpRequestMethodNotSupportedException if there are matches by URL but not by HTTP method
+ */
@Override
protected HandlerMethod handleNoMatch(Set requestKeys, HttpServletRequest request)
throws HttpRequestMethodNotSupportedException {
@@ -223,13 +237,29 @@ public class RequestMappingHandlerMethodMapping extends AbstractHandlerMethodMap
}
/**
- * A comparator for RequestKey types. Effective comparison can only be done in the context of a specific request. For
- * example not all configured patterns may apply to the current request. Therefore an HttpServletRequest is required as
- * input.
+ * Adds mapped interceptors to the handler execution chain.
+ */
+ @Override
+ protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
+ HandlerExecutionChain chain = super.getHandlerExecutionChain(handler, request);
+ if (this.mappedInterceptors != null) {
+ String lookupPath = urlPathHelper.getLookupPathForRequest(request);
+ HandlerInterceptor[] handlerInterceptors = mappedInterceptors.getInterceptors(lookupPath, pathMatcher);
+ if (handlerInterceptors.length > 0) {
+ chain.addInterceptors(handlerInterceptors);
+ }
+ }
+ return chain;
+ }
+
+ /**
+ * A comparator for {@link RequestKey}s. Effective comparison can only be done in the context of a
+ * specific request. For example not all {@link RequestKey} patterns may apply to the current request.
+ * Therefore an HttpServletRequest is required as input.
*
- * Furthermore, the following assumptions are made about the input RequestKeys: - Each RequestKey has been fully
- * matched to the request
- The RequestKey contains matched patterns only
- Patterns are ordered with the best
- * matching pattern at the top
+ * Furthermore, the following assumptions are made about the input RequestKeys:
+ *
- Each RequestKey has been fully matched to the request
- The RequestKey contains matched
+ * patterns only
- Patterns are ordered with the best matching pattern at the top
*
* @see RequestMappingHandlerMethodMapping#getMatchingKey(RequestKey, HttpServletRequest)
*/