HandlerMethod handlerMethod
部分源码
AbstractHandlerMapping
/**
* Abstract base class for {@link org.springframework.web.servlet.HandlerMapping}
* implementations. Supports ordering, a default handler, handler interceptors,
* including handler interceptors mapped by path patterns.
*
* Note: This base class does not support exposure of the
* {@link #PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE}. Support for this attribute
* is up to concrete subclasses, typically based on request URL mappings.
*
* @author Juergen Hoeller
* @author Rossen Stoyanchev
* @since 07.04.2003
* @see #getHandlerInternal
* @see #setDefaultHandler
* @see #setAlwaysUseFullPath
* @see #setUrlDecode
* @see org.springframework.util.AntPathMatcher
* @see #setInterceptors
* @see org.springframework.web.servlet.HandlerInterceptor
*/
public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
implements HandlerMapping, Ordered {//注意这个order,说明hm也是按照order顺序优先级匹配的
//实现了order接口,在dispacherServlet中初始化的时候,我们会看到,会对它们进行排序
private int order = Integer.MAX_VALUE; // default: same as non-Ordered
private Object defaultHandler;//默认处理器
private UrlPathHelper urlPathHelper = new UrlPathHelper();//URL匹配工具
private PathMatcher pathMatcher = new AntPathMatcher();//路径匹配器
private final List
/**
* Abstract base class for {@link HandlerMapping} implementations that define a
* mapping between a request and a {@link HandlerMethod}.
*
* For each registered handler method, a unique mapping is maintained with
* subclasses defining the details of the mapping type {@code }.
*
* @param The mapping for a {@link HandlerMethod} containing the conditions
* needed to match the handler method to incoming request.
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @author Juergen Hoeller
* @since 3.1
*/
public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMapping implements InitializingBean {
/**
* Bean name prefix for target beans behind scoped proxies. Used to exclude those
* targets from handler method detection, in favor of the corresponding proxies.
* We're not checking the autowire-candidate status here, which is how the
* proxy target filtering problem is being handled at the autowiring level,
* since autowire-candidate may have been turned to {@code false} for other
* reasons, while still expecting the bean to be eligible for handler methods.
*
Originally defined in {@link org.springframework.aop.scope.ScopedProxyUtils}
* but duplicated here to avoid a hard dependency on the spring-aop module.
*/
private static final String SCOPED_TARGET_NAME_PREFIX = "scopedTarget.";
private boolean detectHandlerMethodsInAncestorContexts = false;
//映射信息和处理器的关系
private final Map handlerMethods = new LinkedHashMap();
//路径和映射信息的关系(注意是一对多的关系),快速匹配使用
private final MultiValueMap urlMap = new LinkedMultiValueMap();
/**
* Extract and return the URL paths contained in a mapping.
*/
protected abstract Set getMappingPathPatterns(T mapping);
/**
* Invoked after all handler methods have been detected.
* @param handlerMethods a read-only map with handler methods and mappings.
*/
protected void handlerMethodsInitialized(Map handlerMethods) {
}
/**
* Look up a handler method for the given request.
*/
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
//1.根据当前URL获取需要用来查找处理器的路径
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
if (logger.isDebugEnabled()) {
logger.debug("Looking up handler method for path " + lookupPath);
}
//2.开始获取处理器
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
if (logger.isDebugEnabled()) {
if (handlerMethod != null) {
logger.debug("Returning handler method [" + handlerMethod + "]");
}
else {
logger.debug("Did not find handler method for [" + lookupPath + "]");
}
}
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
/**
* 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 matches = new ArrayList();
//1.首先尝试直接拿路径匹配,这是理性的情况下,匹配速度最快
List directPathMatches = this.urlMap.get(lookupPath);
if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
// No choice but to go through all mappings...
//2.直接路径匹配失败,那就只能把所有注册的映射信息挨个匹配了,这样速度没有直接匹配那么快
addMatchingMappings(this.handlerMethods.keySet(), matches, request);
}
//可能会匹配到多个合适的映射,所以需要一种优先算法找到最佳匹配,这个过程使用了Comparator比较类,先排序,再找出最合适的,
//是不是和我们的找数组最大最小值或者冒泡排序很像呢,哈哈
if (!matches.isEmpty()) {
Comparator comparator = new MatchComparator(getMappingComparator(request));
Collections.sort(matches, comparator);
if (logger.isTraceEnabled()) {
logger.trace("Found " + matches.size() + " matching mapping(s) for [" + lookupPath + "] : " + matches);
}
Match bestMatch = matches.get(0);
if (matches.size() > 1) {
Match secondBestMatch = matches.get(1);
if (comparator.compare(bestMatch, secondBestMatch) == 0) {
Method m1 = bestMatch.handlerMethod.getMethod();
Method m2 = secondBestMatch.handlerMethod.getMethod();
throw new IllegalStateException(
"Ambiguous handler methods mapped for HTTP path '" + request.getRequestURL() + "': {" +
m1 + ", " + m2 + "}");
}
}
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.handlerMethod;
}
else {
return handleNoMatch(handlerMethods.keySet(), lookupPath, request);
}
}
private void addMatchingMappings(Collection mappings, List matches, HttpServletRequest request) {
for (T mapping : mappings) {
T match = getMatchingMapping(mapping, request);
if (match != null) {
matches.add(new Match(match, this.handlerMethods.get(mapping)));
}
}
}
/**
* 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 the match, or {@code null} if the mapping doesn't match
*/
protected abstract T getMatchingMapping(T mapping, HttpServletRequest request);
/**
* 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 getMappingComparator(HttpServletRequest 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);
}
/**
* A thin wrapper around a matched HandlerMethod and its mapping, for the purpose of
* comparing the best match with a comparator in the context of the current request.
*/
private class Match {
private final T mapping;
private final HandlerMethod handlerMethod;
public Match(T mapping, HandlerMethod handlerMethod) {
this.mapping = mapping;
this.handlerMethod = handlerMethod;
}
@Override
public String toString() {
return this.mapping.toString();
}
}
private class MatchComparator implements Comparator {
private final Comparator comparator;
public MatchComparator(Comparator comparator) {
this.comparator = comparator;
}
@Override
public int compare(Match match1, Match match2) {
return this.comparator.compare(match1.mapping, match2.mapping);
}
}
}
/**
* Encapsulates the following request mapping conditions:
*
* - {@link PatternsRequestCondition}
*
- {@link RequestMethodsRequestCondition}
*
- {@link ParamsRequestCondition}
*
- {@link HeadersRequestCondition}
*
- {@link ConsumesRequestCondition}
*
- {@link ProducesRequestCondition}
*
- {@code RequestCondition} (optional, custom request condition)
*
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @since 3.1
*/
public final class RequestMappingInfo implements RequestCondition {
private final PatternsRequestCondition patternsCondition;//URL匹配
private final RequestMethodsRequestCondition methodsCondition;//http method匹配
private final ParamsRequestCondition paramsCondition;//请求参数匹配
private final HeadersRequestCondition headersCondition;//请求头匹配
private final ConsumesRequestCondition consumesCondition;//请求类型匹配,
private final ProducesRequestCondition producesCondition;//返回类型匹配
private final RequestConditionHolder customConditionHolder;//暂时不知道什么东东
/**
* Creates a new instance with the given request conditions.
*/
public RequestMappingInfo(PatternsRequestCondition patterns, RequestMethodsRequestCondition methods,
ParamsRequestCondition params, HeadersRequestCondition headers, ConsumesRequestCondition consumes,
ProducesRequestCondition produces, RequestCondition> custom) {
this.patternsCondition = (patterns != null ? patterns : new PatternsRequestCondition());
this.methodsCondition = (methods != null ? methods : new RequestMethodsRequestCondition());
this.paramsCondition = (params != null ? params : new ParamsRequestCondition());
this.headersCondition = (headers != null ? headers : new HeadersRequestCondition());
this.consumesCondition = (consumes != null ? consumes : new ConsumesRequestCondition());
this.producesCondition = (produces != null ? produces : new ProducesRequestCondition());
this.customConditionHolder = new RequestConditionHolder(custom);
}
/**
* 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,
info.consumesCondition, info.producesCondition, customRequestCondition);
}
/**
* Returns the URL patterns of this {@link RequestMappingInfo};
* or instance with 0 patterns, never {@code null}.
*/
public PatternsRequestCondition getPatternsCondition() {
return this.patternsCondition;
}
/**
* Returns the HTTP request methods of this {@link RequestMappingInfo};
* or instance with 0 request methods, never {@code null}.
*/
public RequestMethodsRequestCondition getMethodsCondition() {
return this.methodsCondition;
}
/**
* Returns the "parameters" condition of this {@link RequestMappingInfo};
* or instance with 0 parameter expressions, never {@code null}.
*/
public ParamsRequestCondition getParamsCondition() {
return this.paramsCondition;
}
/**
* Returns the "headers" condition of this {@link RequestMappingInfo};
* or instance with 0 header expressions, never {@code null}.
*/
public HeadersRequestCondition getHeadersCondition() {
return this.headersCondition;
}
/**
* Returns the "consumes" condition of this {@link RequestMappingInfo};
* or instance with 0 consumes expressions, never {@code null}.
*/
public ConsumesRequestCondition getConsumesCondition() {
return this.consumesCondition;
}
/**
* Returns the "produces" condition of this {@link RequestMappingInfo};
* or instance with 0 produces expressions, never {@code null}.
*/
public ProducesRequestCondition getProducesCondition() {
return this.producesCondition;
}
/**
* Returns the "custom" condition of this {@link RequestMappingInfo}; or {@code null}.
*/
public RequestCondition> getCustomCondition() {
return this.customConditionHolder.getCondition();
}
/**
* Combines "this" request mapping info (i.e. the current instance) with another request mapping info instance.
* Example: combine type- and method-level request mappings.
* @return a new request mapping info instance; never {@code null}
*/
@Override
public RequestMappingInfo combine(RequestMappingInfo other) {
PatternsRequestCondition patterns = this.patternsCondition.combine(other.patternsCondition);
RequestMethodsRequestCondition methods = this.methodsCondition.combine(other.methodsCondition);
ParamsRequestCondition params = this.paramsCondition.combine(other.paramsCondition);
HeadersRequestCondition headers = this.headersCondition.combine(other.headersCondition);
ConsumesRequestCondition consumes = this.consumesCondition.combine(other.consumesCondition);
ProducesRequestCondition produces = this.producesCondition.combine(other.producesCondition);
RequestConditionHolder custom = this.customConditionHolder.combine(other.customConditionHolder);
return new RequestMappingInfo(patterns, methods, params, headers, consumes, produces, custom.getCondition());
}
/**
* Checks if all conditions in this request mapping info match the provided request and returns
* a potentially new request mapping info with conditions tailored to the current request.
*
For example the returned instance may contain the subset of URL patterns that match to
* the current request, sorted with best matching patterns on top.
* @return a new instance in case all conditions match; or {@code null} otherwise
*/
@Override
public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {
RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(request);
ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(request);
HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(request);
ConsumesRequestCondition consumes = this.consumesCondition.getMatchingCondition(request);
ProducesRequestCondition produces = this.producesCondition.getMatchingCondition(request);
if (methods == null || params == null || headers == null || consumes == null || produces == null) {
return null;
}
PatternsRequestCondition patterns = this.patternsCondition.getMatchingCondition(request);
if (patterns == null) {
return null;
}
RequestConditionHolder custom = this.customConditionHolder.getMatchingCondition(request);
if (custom == null) {
return null;
}
return new RequestMappingInfo(patterns, methods, params, headers, consumes, produces, custom.getCondition());
}
/**
* Compares "this" info (i.e. the current instance) with another info in the context of a request.
*
Note: It is assumed both instances have been obtained via
* {@link #getMatchingCondition(HttpServletRequest)} to ensure they have conditions with
* content relevant to current request.
*/
@Override
public int compareTo(RequestMappingInfo other, HttpServletRequest request) {
int result = this.patternsCondition.compareTo(other.getPatternsCondition(), request);
if (result != 0) {
return result;
}
result = this.paramsCondition.compareTo(other.getParamsCondition(), request);
if (result != 0) {
return result;
}
result = this.headersCondition.compareTo(other.getHeadersCondition(), request);
if (result != 0) {
return result;
}
result = this.consumesCondition.compareTo(other.getConsumesCondition(), request);
if (result != 0) {
return result;
}
result = this.producesCondition.compareTo(other.getProducesCondition(), request);
if (result != 0) {
return result;
}
result = this.methodsCondition.compareTo(other.getMethodsCondition(), request);
if (result != 0) {
return result;
}
result = this.customConditionHolder.compareTo(other.customConditionHolder, request);
if (result != 0) {
return result;
}
return 0;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj != null && obj instanceof RequestMappingInfo) {
RequestMappingInfo other = (RequestMappingInfo) obj;
return (this.patternsCondition.equals(other.patternsCondition) &&
this.methodsCondition.equals(other.methodsCondition) &&
this.paramsCondition.equals(other.paramsCondition) &&
this.headersCondition.equals(other.headersCondition) &&
this.consumesCondition.equals(other.consumesCondition) &&
this.producesCondition.equals(other.producesCondition) &&
this.customConditionHolder.equals(other.customConditionHolder));
}
return false;
}
@Override
public int hashCode() {
return (this.patternsCondition.hashCode() * 31 + // primary differentiation
this.methodsCondition.hashCode() + this.paramsCondition.hashCode() +
this.headersCondition.hashCode() + this.consumesCondition.hashCode() +
this.producesCondition.hashCode() + this.customConditionHolder.hashCode());
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder("{");
builder.append(this.patternsCondition);
builder.append(",methods=").append(this.methodsCondition);
builder.append(",params=").append(this.paramsCondition);
builder.append(",headers=").append(this.headersCondition);
builder.append(",consumes=").append(this.consumesCondition);
builder.append(",produces=").append(this.producesCondition);
builder.append(",custom=").append(this.customConditionHolder);
builder.append('}');
return builder.toString();
}
}