提交 b6d7c85f 编写于 作者: R Rossen Stoyanchev

SPR-7812 Provide separate methods for type and method-level custom request...

SPR-7812 Provide separate methods for type and method-level custom request conditions. Polish javadoc in RequestMappingInfo and related HandlerMapping classes
上级 ddfb2d3c
......@@ -37,10 +37,14 @@ import org.springframework.web.method.HandlerMethodSelector;
import org.springframework.web.servlet.HandlerMapping;
/**
* Abstract base class for {@link org.springframework.web.servlet.HandlerMapping HandlerMapping} implementations that
* support mapping requests to {@link HandlerMethod}s rather than to handlers.
* Abstract base class for {@link HandlerMapping} implementations that define a
* mapping between a request and a {@link HandlerMethod}.
*
* @param <T> A type containing request mapping conditions required to match a request to a {@link HandlerMethod}.
* <p>For each registered handler method, a unique mapping is maintained with
* subclasses defining the details of the mapping type {@code <T>}.
*
* @param <T> The mapping for a {@link HandlerMethod} containing the conditions
* needed to match the handler method to incoming request.
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
......@@ -48,21 +52,19 @@ import org.springframework.web.servlet.HandlerMapping;
*/
public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping {
private final MultiValueMap<String, T> urlMap = new LinkedMultiValueMap<String, T>();
private final Map<T, HandlerMethod> handlerMethods = new LinkedHashMap<T, HandlerMethod>();
private final MultiValueMap<String, T> urlMap = new LinkedMultiValueMap<String, T>();
/**
* Return the map with all {@link HandlerMethod}s. The key of the map is the generic type
* <strong>{@code <T>}</strong> containing request mapping conditions.
* Return a map with all handler methods and their mappings.
*/
public Map<T, HandlerMethod> getHandlerMethods() {
return Collections.unmodifiableMap(handlerMethods);
}
/**
* Calls the initialization of the superclass and detects handlers.
* ApplicationContext initialization and handler method detection.
*/
@Override
public void initApplicationContext() throws ApplicationContextException {
......@@ -71,9 +73,10 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
}
/**
* Register handler methods found in beans of the current ApplicationContext.
* <p>The actual mapping for a handler is up to the concrete {@link #getMappingForMethod(String, Method)}
* implementation.
* Scan beans in the ApplicationContext, detect and register handler methods.
* @see #isHandler(Class)
* @see #getMappingForMethod(Method, Class)
* @see #handlerMethodsInitialized(Map)
*/
protected void initHandlerMethods() {
if (logger.isDebugEnabled()) {
......@@ -88,21 +91,21 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
}
/**
* Determines if the given type could contain handler methods.
* @param beanType the type to check
* @return true if this a type that could contain handler methods, false otherwise.
* Whether the given type is a handler with handler methods.
* @param beanType the type of the bean being checked
* @return "true" if this a handler type, "false" otherwise.
*/
protected abstract boolean isHandler(Class<?> beanType);
/**
* Invoked after all handler methods found in beans of the current ApplicationContext have been registered.
* @param handlerMethods a read-only map with mapping conditions (generic type {@code <T>}) and HandlerMethods.
* Invoked after all handler methods have been detected.
* @param handlerMethods a read-only map with handler methods and mappings.
*/
protected void handlerMethodsInitialized(Map<T, HandlerMethod> handlerMethods) {
}
/**
* Detect and register handler methods for the specified handler.
* Look for handler methods in a handler.
* @param handler the bean name of a handler or a handler instance
*/
protected void detectHandlerMethods(final Object handler) {
......@@ -124,22 +127,24 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
}
/**
* Provides a request mapping for the given bean method. A method for which no request mapping can be determined
* is not considered a handler method.
* Provide the mapping for a handler method. A method for which no
* mapping can be provided is not a handler method.
*
* @param method the method to create a mapping for
* @param handlerType the actual handler type (possibly a subtype of {@code method.getDeclaringClass()})
* @param method the method to provide a mapping for
* @param handlerType the handler type, possibly a sub-type of the method's
* declaring class
* @return the mapping, or {@code null} if the method is not mapped
*/
protected abstract T getMappingForMethod(Method method, Class<?> handlerType);
/**
* Registers a {@link HandlerMethod} with the given mapping.
* Register a handler method and its unique mapping.
*
* @param handler the bean name of the handler or the actual handler instance
* @param handler the bean name of the handler or the handler instance
* @param method the method to register
* @param mapping the mapping conditions associated with the handler method
* @throws IllegalStateException if another method was already register under the same mapping
* @throws IllegalStateException if another method was already registered
* under the same mapping
*/
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
HandlerMethod handlerMethod;
......@@ -172,10 +177,13 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
}
/**
* Get the URL paths associated with the given mapping.
* Extract and return the URL paths contained in a mapping.
*/
protected abstract Set<String> getMappingPathPatterns(T mapping);
/**
* Look up a handler method for the given request.
*/
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
......@@ -198,16 +206,15 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
}
/**
* Looks up the best-matching {@link HandlerMethod} for the given request.
*
* <p>This implementation iterators through all handler methods, calls
* {@link #getMatchingMapping(Object, String, HttpServletRequest)} for each of them,
* sorts all matches via {@linkplain #getMappingComparator(String, HttpServletRequest)} , and returns the
* top match, if any. If no matches are found, {@link #handleNoMatch(Set, HttpServletRequest)} is invoked.
*
* @param lookupPath mapping lookup path within the current servlet mapping if applicable
* @param request the current HTTP servlet request
* @return the best-matching handler method, or {@code null} if there is no match
* Look up the best-matching handler method for the current request.
* If multiple matches are found, the best match is selected.
*
* @param lookupPath mapping lookup path within the current servlet mapping
* @param request the current request
* @return the best-matching handler method, or {@code null} if no match
*
* @see #handleMatch(Object, String, HttpServletRequest)
* @see #handleNoMatch(Set, String, HttpServletRequest)
*/
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<T> mappings = urlMap.get(lookupPath);
......@@ -253,42 +260,38 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
}
/**
* Invoked when a request has been matched to a mapping.
*
* @param mapping the mapping selected for the request returned by
* {@link #getMatchingMapping(Object, String, HttpServletRequest)}.
* @param lookupPath mapping lookup path within the current servlet mapping if applicable
* @param request the current request
*/
protected void handleMatch(T mapping, String lookupPath, HttpServletRequest request) {
request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, lookupPath);
}
/**
* Checks if the mapping matches the current request and returns a mapping updated to contain only conditions
* relevant to the current request (for example a mapping may have several HTTP methods, the matching mapping
* will contain only 1).
* Check if a mapping matches the current request and return a (potentially
* new) mapping with conditions relevant to the current request.
*
* @param mapping the mapping to get a match for
* @param request the current HTTP servlet request
* @return a matching mapping, or {@code null} if the given mapping does not match the request
* @return the match, or {@code null} if the mapping doesn't match
*/
protected abstract T getMatchingMapping(T mapping, HttpServletRequest request);
/**
* Returns a comparator to sort request mappings with. The returned comparator should sort 'better' matches higher.
*
* @param request the current HTTP servlet request
* @return the comparator
* Return a comparator for sorting matching mappings.
* The returned comparator should sort 'better' matches higher.
* @param request the current request
* @return the comparator, never {@code null}
*/
protected abstract Comparator<T> getMappingComparator(HttpServletRequest request);
/**
* Invoked when no match was found. Default implementation returns {@code null}.
*
* @param mappings all registered request mappings
* @param lookupPath mapping lookup path within the current servlet mapping if applicable
* @param request the current HTTP request
* Invoked when a matching mapping is found.
* @param mapping the matching mapping
* @param lookupPath mapping lookup path within the current servlet mapping
* @param request the current request
*/
protected void handleMatch(T mapping, String lookupPath, HttpServletRequest request) {
request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, lookupPath);
}
/**
* Invoked when no matching mapping is not found.
* @param mappings all registered mappings
* @param lookupPath mapping lookup path within the current servlet mapping
* @param request the current request
* @throws ServletException in case of errors
*/
protected HandlerMethod handleNoMatch(Set<T> mappings, String lookupPath, HttpServletRequest request)
......@@ -296,6 +299,9 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
return null;
}
/**
* A temporary container for a mapping matched to a request.
*/
private class Match {
private final T mapping;
......
......@@ -29,8 +29,9 @@ import java.util.Iterator;
public abstract class AbstractRequestCondition<T extends AbstractRequestCondition<T>> implements RequestCondition<T> {
/**
* Returns the discrete items a request condition is composed of such as URL patterns,
* HTTP request methods, parameter expressions, etc.
* Return the discrete items a request condition is composed of.
* For example URL patterns, HTTP request methods, param expressions, etc.
* @return a collection of objects, never {@code null}
*/
protected abstract Collection<?> getContent();
......@@ -66,8 +67,8 @@ public abstract class AbstractRequestCondition<T extends AbstractRequestConditio
}
/**
* The notation to use when printing discrete items of content in the toString() method.
* For example URL patterns use " || " while parameter expressions use " && ".
* The notation to use when printing discrete items of content.
* For example " || " for URL patterns or " && " for param expressions.
*/
protected abstract String getToStringInfix();
......
......@@ -14,50 +14,49 @@
* limitations under the License.
*/
package org.springframework.web.servlet.mvc.method;
package org.springframework.web.servlet.mvc.condition;
import java.util.Collection;
import java.util.Collections;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.servlet.mvc.condition.AbstractRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
/**
* Wraps and delegates operations to a {@link RequestCondition} whose type is not known ahead of time. The main goal
* is to provide type-safe and null-safe way of comparing and combining optional custom {@link RequestCondition}s.
* A holder for a {@link RequestCondition} useful when the type of the held
* request condition is not known ahead of time - e.g. custom condition.
*
* <p>An implementation of {@code RequestCondition} itself, a
* {@code RequestConditionHolder} decorates the held request condition allowing
* it to be combined and compared with other custom request conditions while
* ensuring type and null safety.
*
* @author Rossen Stoyanchev
* @since 3.1
*/
final class CustomRequestCondition extends AbstractRequestCondition<CustomRequestCondition> {
public final class RequestConditionHolder extends AbstractRequestCondition<RequestConditionHolder> {
@SuppressWarnings("rawtypes")
private final RequestCondition customCondition;
private final RequestCondition condition;
/**
* Creates an instance that wraps the given custom request condition.
* @param requestCondition the custom request condition
* Create a new holder to wrap the given request condition.
* @param requestCondition the condition to hold, may be {@code null}
*/
CustomRequestCondition(RequestCondition<?> requestCondition) {
this.customCondition = requestCondition;
public RequestConditionHolder(RequestCondition<?> requestCondition) {
this.condition = requestCondition;
}
/**
* Creates an instance that does not wrap any custom request condition.
* Return the held request condition, or {@code null} if not holding one.
*/
CustomRequestCondition() {
this(null);
}
public RequestCondition<?> getCondition() {
return customCondition;
return condition;
}
@Override
protected Collection<?> getContent() {
return customCondition != null ? Collections.singleton(customCondition) : Collections.emptyList();
return condition != null ? Collections.singleton(condition) : Collections.emptyList();
}
@Override
......@@ -66,68 +65,71 @@ final class CustomRequestCondition extends AbstractRequestCondition<CustomReques
}
/**
* Delegates the operation to the wrapped custom request conditions. May also return "this" instance
* if the "other" does not contain a custom request condition and vice versa.
* Combine the request conditions held by the two RequestConditionHolder
* instances after making sure the conditions are of the same type.
* Or if one holder is empty, the other holder is returned.
*/
@SuppressWarnings("unchecked")
public CustomRequestCondition combine(CustomRequestCondition other) {
if (customCondition == null && other.customCondition == null) {
public RequestConditionHolder combine(RequestConditionHolder other) {
if (condition == null && other.condition == null) {
return this;
}
else if (customCondition == null) {
else if (condition == null) {
return other;
}
else if (other.customCondition == null) {
else if (other.condition == null) {
return this;
}
else {
assertCompatible(other);
RequestCondition<?> combined = (RequestCondition<?>) customCondition.combine(other.customCondition);
return new CustomRequestCondition(combined);
assertIsCompatible(other);
RequestCondition<?> combined = (RequestCondition<?>) condition.combine(other.condition);
return new RequestConditionHolder(combined);
}
}
private void assertCompatible(CustomRequestCondition other) {
if (customCondition != null && other.customCondition != null) {
Class<?> clazz = customCondition.getClass();
Class<?> otherClazz = other.customCondition.getClass();
if (!clazz.equals(otherClazz)) {
throw new ClassCastException("Incompatible custom request conditions: " + clazz + ", " + otherClazz);
}
/**
* Ensure the held request conditions are of the same type.
*/
private void assertIsCompatible(RequestConditionHolder other) {
Class<?> clazz = condition.getClass();
Class<?> otherClazz = other.condition.getClass();
if (!clazz.equals(otherClazz)) {
throw new ClassCastException("Incompatible request conditions: " + clazz + " and " + otherClazz);
}
}
/**
* Delegates the operation to the wrapped custom request condition; or otherwise returns the same
* instance if there is no custom request condition.
* Get the matching condition for the held request condition wrap it in a
* new RequestConditionHolder instance. Or otherwise if this is an empty
* holder, return the same holder instance.
*/
public CustomRequestCondition getMatchingCondition(HttpServletRequest request) {
if (customCondition == null) {
public RequestConditionHolder getMatchingCondition(HttpServletRequest request) {
if (condition == null) {
return this;
}
RequestCondition<?> match = (RequestCondition<?>) customCondition.getMatchingCondition(request);
return new CustomRequestCondition(match);
RequestCondition<?> match = (RequestCondition<?>) condition.getMatchingCondition(request);
return new RequestConditionHolder(match);
}
/**
* Delegates the operation to the wrapped custom request conditions after checking for the presence
* custom request conditions and asserting type safety. The presence of a custom request condition
* in one instance but not the other will cause it to be selected, and vice versa.
* Compare the request conditions held by the two RequestConditionHolder
* instances after making sure the conditions are of the same type.
* Or if one holder is empty, the other holder is preferred.
*/
@SuppressWarnings("unchecked")
public int compareTo(CustomRequestCondition other, HttpServletRequest request) {
if (customCondition == null && other.customCondition == null) {
public int compareTo(RequestConditionHolder other, HttpServletRequest request) {
if (condition == null && other.condition == null) {
return 0;
}
else if (customCondition == null) {
else if (condition == null) {
return 1;
}
else if (other.customCondition == null) {
else if (other.condition == null) {
return -1;
}
else {
assertCompatible(other);
return customCondition.compareTo(other.customCondition, request);
assertIsCompatible(other);
return condition.compareTo(other.condition, request);
}
}
......
......@@ -24,26 +24,26 @@ import org.springframework.web.servlet.mvc.condition.ParamsRequestCondition;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.ProducesRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestConditionHolder;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
/**
* A RequestMapingInfo encapsulates and operates on the following request mapping conditions:
* <ul>
* <li>{@link PatternsRequestCondition}</li>
* <li>{@link RequestMethodsRequestCondition}</li>
* <li>{@link ParamsRequestCondition}</li>
* <li>{@link HeadersRequestCondition}</li>
* <li>{@link ConsumesRequestCondition}</li>
* <li>{@link ProducesRequestCondition}</li>
* </ul>
*
* Optionally a custom request condition may be provided.
* Encapsulates the following request mapping conditions:
* <ol>
* <li>{@link PatternsRequestCondition}
* <li>{@link RequestMethodsRequestCondition}
* <li>{@link ParamsRequestCondition}
* <li>{@link HeadersRequestCondition}
* <li>{@link ConsumesRequestCondition}
* <li>{@link ProducesRequestCondition}
* <li>{@code RequestCondition<?>} (optional, custom request condition)
* </ol>
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @since 3.1
*/
public final class RequestMappingInfo {
public final class RequestMappingInfo implements RequestCondition<RequestMappingInfo> {
private final PatternsRequestCondition patternsCondition;
......@@ -57,12 +57,12 @@ public final class RequestMappingInfo {
private final ProducesRequestCondition producesCondition;
private final CustomRequestCondition customCondition;
private final RequestConditionHolder customConditionHolder;
private int hash;
/**
* Creates a new {@code RequestMappingInfo} instance.
* Creates a new instance with the given request conditions.
*/
public RequestMappingInfo(PatternsRequestCondition patterns,
RequestMethodsRequestCondition methods,
......@@ -77,11 +77,11 @@ public final class RequestMappingInfo {
this.headersCondition = headers != null ? headers : new HeadersRequestCondition();
this.consumesCondition = consumes != null ? consumes : new ConsumesRequestCondition();
this.producesCondition = produces != null ? produces : new ProducesRequestCondition();
this.customCondition = custom != null ? new CustomRequestCondition(custom) : new CustomRequestCondition();
this.customConditionHolder = new RequestConditionHolder(custom);
}
/**
* Re-create a {@link RequestMappingInfo} with the given custom {@link RequestCondition}.
* Re-create a RequestMappingInfo with the given custom request condition.
*/
public RequestMappingInfo(RequestMappingInfo info, RequestCondition<?> customRequestCondition) {
this(info.patternsCondition, info.methodsCondition, info.paramsCondition, info.headersCondition,
......@@ -140,7 +140,7 @@ public final class RequestMappingInfo {
* Returns the "custom" condition of this {@link RequestMappingInfo}; or {@code null}
*/
public RequestCondition<?> getCustomCondition() {
return customCondition.getCondition();
return customConditionHolder.getCondition();
}
/**
......@@ -155,7 +155,7 @@ public final class RequestMappingInfo {
HeadersRequestCondition headers = this.headersCondition.combine(other.headersCondition);
ConsumesRequestCondition consumes = this.consumesCondition.combine(other.consumesCondition);
ProducesRequestCondition produces = this.producesCondition.combine(other.producesCondition);
CustomRequestCondition custom = this.customCondition.combine(other.customCondition);
RequestConditionHolder custom = this.customConditionHolder.combine(other.customConditionHolder);
return new RequestMappingInfo(patterns, methods, params, headers, consumes, produces, custom.getCondition());
}
......@@ -167,7 +167,7 @@ public final class RequestMappingInfo {
* the current request, sorted with best matching patterns on top.
* @return a new instance in case all conditions match; or {@code null} otherwise
*/
public RequestMappingInfo getMatchingInfo(HttpServletRequest request) {
public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {
RequestMethodsRequestCondition methods = methodsCondition.getMatchingCondition(request);
ParamsRequestCondition params = paramsCondition.getMatchingCondition(request);
HeadersRequestCondition headers = headersCondition.getMatchingCondition(request);
......@@ -183,7 +183,7 @@ public final class RequestMappingInfo {
return null;
}
CustomRequestCondition custom = customCondition.getMatchingCondition(request);
RequestConditionHolder custom = customConditionHolder.getMatchingCondition(request);
if (custom == null) {
return null;
}
......@@ -194,7 +194,7 @@ public final class RequestMappingInfo {
/**
* Compares "this" info (i.e. the current instance) with another info in the context of a request.
* <p>Note: it is assumed both instances have been obtained via
* {@link #getMatchingInfo(HttpServletRequest)} to ensure they have conditions with
* {@link #getMatchingCondition(HttpServletRequest)} to ensure they have conditions with
* content relevant to current request.
*/
public int compareTo(RequestMappingInfo other, HttpServletRequest request) {
......@@ -222,7 +222,7 @@ public final class RequestMappingInfo {
if (result != 0) {
return result;
}
result = customCondition.compareTo(other.customCondition, request);
result = customConditionHolder.compareTo(other.customConditionHolder, request);
if (result != 0) {
return result;
}
......@@ -242,7 +242,7 @@ public final class RequestMappingInfo {
this.headersCondition.equals(other.headersCondition) &&
this.consumesCondition.equals(other.consumesCondition) &&
this.producesCondition.equals(other.producesCondition) &&
this.customCondition.equals(other.customCondition));
this.customConditionHolder.equals(other.customConditionHolder));
}
return false;
}
......@@ -257,7 +257,7 @@ public final class RequestMappingInfo {
result = 31 * result + headersCondition.hashCode();
result = 31 * result + consumesCondition.hashCode();
result = 31 * result + producesCondition.hashCode();
result = 31 * result + customCondition.hashCode();
result = 31 * result + customConditionHolder.hashCode();
hash = result;
}
return result;
......@@ -272,7 +272,7 @@ public final class RequestMappingInfo {
builder.append(",headers=").append(headersCondition);
builder.append(",consumes=").append(consumesCondition);
builder.append(",produces=").append(producesCondition);
builder.append(",custom=").append(customCondition);
builder.append(",custom=").append(customConditionHolder);
builder.append('}');
return builder.toString();
}
......
......@@ -36,8 +36,8 @@ import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.handler.AbstractHandlerMethodMapping;
/**
* An {@link AbstractHandlerMethodMapping} variant that uses {@link RequestMappingInfo} to represent request
* mapping conditions.
* Abstract base class for classes for which {@link RequestMappingInfo} defines
* the mapping between a request and a handler method.
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
......@@ -54,19 +54,18 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
}
/**
* Checks if the given RequestMappingInfo matches the current request and returns a potentially new
* RequestMappingInfo instances tailored to the current request, for example containing the subset
* of URL patterns or media types that match the request.
*
* @returns a RequestMappingInfo instance in case of a match; or {@code null} in case of no match.
* Check if the given RequestMappingInfo matches the current request and
* return a (potentially new) instance with conditions that match the
* current request -- for example with a subset of URL patterns.
* @returns an info in case of a match; or {@code null} otherwise.
*/
@Override
protected RequestMappingInfo getMatchingMapping(RequestMappingInfo info, HttpServletRequest request) {
return info.getMatchingInfo(request);
return info.getMatchingCondition(request);
}
/**
* Returns a {@link Comparator} for sorting {@link RequestMappingInfo} in the context of the given request.
* Provide a Comparator to sort RequestMappingInfos matched to a request.
*/
@Override
protected Comparator<RequestMappingInfo> getMappingComparator(final HttpServletRequest request) {
......@@ -78,8 +77,7 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
}
/**
* Exposes URI template variables and producible media types as request attributes.
*
* Expose URI template variables and producible media types in the request.
* @see HandlerMapping#URI_TEMPLATE_VARIABLES_ATTRIBUTE
* @see HandlerMapping#PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE
*/
......@@ -98,14 +96,15 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
}
/**
* Iterates all {@link RequestMappingInfo}s looking for mappings that match by URL but not by HTTP method.
*
* Iterate all RequestMappingInfos once again, look if any match by URL at
* least and raise exceptions accordingly.
*
* @throws HttpRequestMethodNotSupportedException
* if there are matches by URL but not by HTTP method
* @throws HttpMediaTypeNotAcceptableException
* if there are matches by URL but the consumable media types don't match the 'Content-Type' header
* if there are matches by URL but not by consumable media types
* @throws HttpMediaTypeNotAcceptableException
* if there are matches by URL but the producible media types don't match the 'Accept' header
* if there are matches by URL but not by producible media types
*/
@Override
protected HandlerMethod handleNoMatch(Set<RequestMappingInfo> requestMappingInfos,
......
......@@ -32,8 +32,9 @@ import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
/**
* A sub-class of {@link RequestMappingInfoHandlerMapping} that prepares {@link RequestMappingInfo}s
* from @{@link RequestMapping} annotations on @{@link Controller} classes.
* Creates {@link RequestMappingInfo} instances from type and method-level
* {@link RequestMapping @RequestMapping} annotations in
* {@link Controller @Controller} classes.
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
......@@ -44,25 +45,24 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
private boolean useSuffixPatternMatch = true;
/**
* Set whether to use a suffix pattern match (".*") when matching patterns to URLs.
* If enabled a method mapped to "/users" will also match to "/users.*".
* <p>Default is "true". Turn this convention off if you intend to interpret path mappings strictly.
* 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 "true".
*/
public void setUseSuffixPatternMatch(boolean useSuffixPatternMatch) {
this.useSuffixPatternMatch = useSuffixPatternMatch;
}
/**
* Returns the value of the useSuffixPatternMatch flag, see {@link #setUseSuffixPatternMatch(boolean)}.
* Whether to use suffix pattern matching.
*/
public boolean isUseSuffixPatternMatch() {
return useSuffixPatternMatch;
public boolean useSuffixPatternMatch() {
return this.useSuffixPatternMatch;
}
/**
* {@inheritDoc}
* The default implementation checks for the presence of a type-level {@link Controller}
* annotation via {@link AnnotationUtils#findAnnotation(Class, Class)}.
* Expects a handler to have a type-level @{@link Controller} annotation.
*/
@Override
protected boolean isHandler(Class<?> beanType) {
......@@ -70,58 +70,68 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
}
/**
* Determines if the given method is a handler method and creates a {@link RequestMappingInfo} for it.
* Uses method and type-level @{@link RequestMapping} annotations to create
* the RequestMappingInfo.
*
* <p>The default implementation expects the presence of a method-level @{@link RequestMapping}
* annotation via {@link AnnotationUtils#findAnnotation(Class, Class)}. The presence of
* type-level annotations is also checked and if present a RequestMappingInfo is created for each type-
* and method-level annotations and combined via {@link RequestMappingInfo#combine(RequestMappingInfo)}.
*
* @param method the method to create a RequestMappingInfo for
* @param handlerType the actual handler type, possibly a sub-type of {@code method.getDeclaringClass()}
* @return the info, or {@code null}
* @return the created RequestMappingInfo, or {@code null} if the method
* does not have a {@code @RequestMapping} annotation.
*
* @see #getCustomMethodCondition(Method)
* @see #getCustomTypeCondition(Class)
*/
@Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMapping methodAnnot = AnnotationUtils.findAnnotation(method, RequestMapping.class);
if (methodAnnot != null) {
RequestMapping typeAnnot = AnnotationUtils.findAnnotation(handlerType, RequestMapping.class);
RequestMappingInfo methodInfo = createRequestMappingInfo(methodAnnot, true, method, handlerType);
if (typeAnnot != null) {
RequestMappingInfo typeInfo = createRequestMappingInfo(typeAnnot, false, method, handlerType);
return typeInfo.combine(methodInfo);
}
else {
return methodInfo;
RequestMappingInfo info = null;
RequestMapping methodAnnotation = AnnotationUtils.findAnnotation(method, RequestMapping.class);
if (methodAnnotation != null) {
RequestCondition<?> methodCondition = getCustomMethodCondition(method);
info = createRequestMappingInfo(methodAnnotation, methodCondition);
RequestMapping typeAnnotation = AnnotationUtils.findAnnotation(handlerType, RequestMapping.class);
if (typeAnnotation != null) {
RequestCondition<?> typeCondition = getCustomTypeCondition(handlerType);
info = createRequestMappingInfo(typeAnnotation, typeCondition).combine(info);
}
}
return info;
}
/**
* Provide a custom method-level request condition.
* 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
* @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
* 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
* @return the condition, or {@code null}
*/
protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
return null;
}
/**
* Override this method to create a {@link RequestMappingInfo} from a @{@link RequestMapping} annotation. The main
* reason for doing so is to provide a custom {@link RequestCondition} to the RequestMappingInfo constructor.
*
* <p>This method is invoked both for type- and method-level @{@link RequestMapping} annotations. The resulting
* {@link RequestMappingInfo}s are combined via {@link RequestMappingInfo#combine(RequestMappingInfo)}.
*
* @param annot a type- or a method-level {@link RequestMapping} annotation
* @param isMethodAnnotation {@code true} if this is a method annotation; {@code false} if it is a type annotation
* @param method the method with which the created RequestMappingInfo will be combined
* @param handlerType the handler type
* @return a {@link RequestMappingInfo} instance; never {@code null}
* Created a RequestMappingInfo from a RequestMapping annotation.
*/
protected RequestMappingInfo createRequestMappingInfo(RequestMapping annot,
boolean isMethodAnnotation,
Method method,
Class<?> handlerType) {
private RequestMappingInfo createRequestMappingInfo(RequestMapping annotation, RequestCondition<?> customCondition) {
return new RequestMappingInfo(
new PatternsRequestCondition(annot.value(), getUrlPathHelper(), getPathMatcher(), useSuffixPatternMatch),
new RequestMethodsRequestCondition(annot.method()),
new ParamsRequestCondition(annot.params()),
new HeadersRequestCondition(annot.headers()),
new ConsumesRequestCondition(annot.consumes(), annot.headers()),
new ProducesRequestCondition(annot.produces(), annot.headers()), null);
new PatternsRequestCondition(annotation.value(),
getUrlPathHelper(), getPathMatcher(), useSuffixPatternMatch),
new RequestMethodsRequestCondition(annotation.method()),
new ParamsRequestCondition(annotation.params()),
new HeadersRequestCondition(annotation.headers()),
new ConsumesRequestCondition(annotation.consumes(), annotation.headers()),
new ProducesRequestCondition(annotation.produces(), annotation.headers()),
customCondition);
}
}
......@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.web.servlet.mvc.method;
package org.springframework.web.servlet.mvc.condition;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
......@@ -26,36 +26,40 @@ import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.condition.HeadersRequestCondition;
import org.springframework.web.servlet.mvc.condition.ParamsRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestConditionHolder;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
/**
* A test fixture for
* {code org.springframework.web.servlet.mvc.method.RequestConditionHolder} tests.
*
* @author Rossen Stoyanchev
*/
public class CustomRequestConditionTests {
public class RequestConditionHolderTests {
@Test
public void combineEmpty() {
CustomRequestCondition empty = new CustomRequestCondition();
CustomRequestCondition custom = new CustomRequestCondition(new ParamsRequestCondition("name"));
RequestConditionHolder empty = new RequestConditionHolder(null);
RequestConditionHolder notEmpty = new RequestConditionHolder(new ParamsRequestCondition("name"));
assertSame(empty, empty.combine(new CustomRequestCondition()));
assertSame(custom, custom.combine(empty));
assertSame(custom, empty.combine(custom));
assertSame(empty, empty.combine(new RequestConditionHolder(null)));
assertSame(notEmpty, notEmpty.combine(empty));
assertSame(notEmpty, empty.combine(notEmpty));
}
@Test
public void combine() {
CustomRequestCondition params1 = new CustomRequestCondition(new ParamsRequestCondition("name1"));
CustomRequestCondition params2 = new CustomRequestCondition(new ParamsRequestCondition("name2"));
CustomRequestCondition expected = new CustomRequestCondition(new ParamsRequestCondition("name1", "name2"));
RequestConditionHolder params1 = new RequestConditionHolder(new ParamsRequestCondition("name1"));
RequestConditionHolder params2 = new RequestConditionHolder(new ParamsRequestCondition("name2"));
RequestConditionHolder expected = new RequestConditionHolder(new ParamsRequestCondition("name1", "name2"));
assertEquals(expected, params1.combine(params2));
}
@Test(expected=ClassCastException.class)
public void combineIncompatible() {
CustomRequestCondition params = new CustomRequestCondition(new ParamsRequestCondition("name"));
CustomRequestCondition headers = new CustomRequestCondition(new HeadersRequestCondition("name"));
RequestConditionHolder params = new RequestConditionHolder(new ParamsRequestCondition("name"));
RequestConditionHolder headers = new RequestConditionHolder(new HeadersRequestCondition("name"));
params.combine(headers);
}
......@@ -65,7 +69,7 @@ public class CustomRequestConditionTests {
request.setParameter("name1", "value1");
RequestMethodsRequestCondition rm = new RequestMethodsRequestCondition(RequestMethod.GET, RequestMethod.POST);
CustomRequestCondition custom = new CustomRequestCondition(rm);
RequestConditionHolder custom = new RequestConditionHolder(rm);
RequestMethodsRequestCondition expected = new RequestMethodsRequestCondition(RequestMethod.GET);
assertEquals(expected, custom.getMatchingCondition(request).getCondition());
......@@ -73,7 +77,7 @@ public class CustomRequestConditionTests {
@Test
public void matchEmpty() {
CustomRequestCondition empty = new CustomRequestCondition();
RequestConditionHolder empty = new RequestConditionHolder(null);
assertSame(empty, empty.getMatchingCondition(new MockHttpServletRequest()));
}
......@@ -81,8 +85,8 @@ public class CustomRequestConditionTests {
public void compare() {
HttpServletRequest request = new MockHttpServletRequest();
CustomRequestCondition params11 = new CustomRequestCondition(new ParamsRequestCondition("1"));
CustomRequestCondition params12 = new CustomRequestCondition(new ParamsRequestCondition("1", "2"));
RequestConditionHolder params11 = new RequestConditionHolder(new ParamsRequestCondition("1"));
RequestConditionHolder params12 = new RequestConditionHolder(new ParamsRequestCondition("1", "2"));
assertEquals(1, params11.compareTo(params12, request));
assertEquals(-1, params12.compareTo(params11, request));
......@@ -92,19 +96,19 @@ public class CustomRequestConditionTests {
public void compareEmpty() {
HttpServletRequest request = new MockHttpServletRequest();
CustomRequestCondition empty = new CustomRequestCondition();
CustomRequestCondition empty2 = new CustomRequestCondition();
CustomRequestCondition custom = new CustomRequestCondition(new ParamsRequestCondition("name"));
RequestConditionHolder empty = new RequestConditionHolder(null);
RequestConditionHolder empty2 = new RequestConditionHolder(null);
RequestConditionHolder notEmpty = new RequestConditionHolder(new ParamsRequestCondition("name"));
assertEquals(0, empty.compareTo(empty2, request));
assertEquals(-1, custom.compareTo(empty, request));
assertEquals(1, empty.compareTo(custom, request));
assertEquals(-1, notEmpty.compareTo(empty, request));
assertEquals(1, empty.compareTo(notEmpty, request));
}
@Test(expected=ClassCastException.class)
public void compareIncompatible() {
CustomRequestCondition params = new CustomRequestCondition(new ParamsRequestCondition("name"));
CustomRequestCondition headers = new CustomRequestCondition(new HeadersRequestCondition("name"));
RequestConditionHolder params = new RequestConditionHolder(new ParamsRequestCondition("name"));
RequestConditionHolder headers = new RequestConditionHolder(new HeadersRequestCondition("name"));
params.compareTo(headers, new MockHttpServletRequest());
}
......
......@@ -54,11 +54,10 @@ import org.springframework.web.servlet.mvc.condition.ParamsRequestCondition;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.ProducesRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.util.UrlPathHelper;
/**
* Test fixture with {@link RequestMappingHandlerMapping}.
* Test fixture with {@link RequestMappingInfoHandlerMapping}.
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
......@@ -275,6 +274,7 @@ public class RequestMappingInfoHandlerMappingTests {
new ConsumesRequestCondition(annotation.consumes(), annotation.headers()),
new ProducesRequestCondition(annotation.produces(), annotation.headers()), null);
}
}
}
\ No newline at end of file
......@@ -67,14 +67,14 @@ public class RequestMappingInfoTests {
RequestMappingInfo expected = new RequestMappingInfo(
new PatternsRequestCondition("/foo*"), null, null, null, null, null, null);
assertEquals(expected, info.getMatchingInfo(request));
assertEquals(expected, info.getMatchingCondition(request));
info = new RequestMappingInfo(
new PatternsRequestCondition("/**", "/foo*", "/foo"), null, null, null, null, null, null);
expected = new RequestMappingInfo(
new PatternsRequestCondition("/foo", "/foo*", "/**"), null, null, null, null, null, null);
assertEquals(expected, info.getMatchingInfo(request));
assertEquals(expected, info.getMatchingCondition(request));
}
@Test
......@@ -86,14 +86,14 @@ public class RequestMappingInfoTests {
new RequestMappingInfo(
new PatternsRequestCondition("/foo"), null,
new ParamsRequestCondition("foo=bar"), null, null, null, null);
RequestMappingInfo match = info.getMatchingInfo(request);
RequestMappingInfo match = info.getMatchingCondition(request);
assertNotNull(match);
info = new RequestMappingInfo(
new PatternsRequestCondition("/foo"), null,
new ParamsRequestCondition("foo!=bar"), null, null, null, null);
match = info.getMatchingInfo(request);
match = info.getMatchingCondition(request);
assertNull(match);
}
......@@ -107,14 +107,14 @@ public class RequestMappingInfoTests {
new RequestMappingInfo(
new PatternsRequestCondition("/foo"), null, null,
new HeadersRequestCondition("foo=bar"), null, null, null);
RequestMappingInfo match = info.getMatchingInfo(request);
RequestMappingInfo match = info.getMatchingCondition(request);
assertNotNull(match);
info = new RequestMappingInfo(
new PatternsRequestCondition("/foo"), null, null,
new HeadersRequestCondition("foo!=bar"), null, null, null);
match = info.getMatchingInfo(request);
match = info.getMatchingCondition(request);
assertNull(match);
}
......@@ -128,14 +128,14 @@ public class RequestMappingInfoTests {
new RequestMappingInfo(
new PatternsRequestCondition("/foo"), null, null, null,
new ConsumesRequestCondition("text/plain"), null, null);
RequestMappingInfo match = info.getMatchingInfo(request);
RequestMappingInfo match = info.getMatchingCondition(request);
assertNotNull(match);
info = new RequestMappingInfo(
new PatternsRequestCondition("/foo"), null, null, null,
new ConsumesRequestCondition("application/xml"), null, null);
match = info.getMatchingInfo(request);
match = info.getMatchingCondition(request);
assertNull(match);
}
......@@ -149,14 +149,14 @@ public class RequestMappingInfoTests {
new RequestMappingInfo(
new PatternsRequestCondition("/foo"), null, null, null, null,
new ProducesRequestCondition("text/plain"), null);
RequestMappingInfo match = info.getMatchingInfo(request);
RequestMappingInfo match = info.getMatchingCondition(request);
assertNotNull(match);
info = new RequestMappingInfo(
new PatternsRequestCondition("/foo"), null, null, null, null,
new ProducesRequestCondition("application/xml"), null);
match = info.getMatchingInfo(request);
match = info.getMatchingCondition(request);
assertNull(match);
}
......@@ -170,7 +170,7 @@ public class RequestMappingInfoTests {
new RequestMappingInfo(
new PatternsRequestCondition("/foo"), null, null, null, null, null,
new ParamsRequestCondition("foo=bar"));
RequestMappingInfo match = info.getMatchingInfo(request);
RequestMappingInfo match = info.getMatchingCondition(request);
assertNotNull(match);
......@@ -178,7 +178,7 @@ public class RequestMappingInfoTests {
new PatternsRequestCondition("/foo"), null,
new ParamsRequestCondition("foo!=bar"), null, null, null,
new ParamsRequestCondition("foo!=bar"));
match = info.getMatchingInfo(request);
match = info.getMatchingCondition(request);
assertNull(match);
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册