public @interface RequestMapping {
String[] value() default {};
RequestMethod[] method() default {};
String[] params() default {};
String[] headers() default {};
String[] consumes() default {};
String[] produces() default {};
}
@RequestMapping 注解想必大家都不陌生。
springMVC在启动时会扫描所有的@RequestMapping并封装成对应的RequestMapingInfo。
一个请求过来会与RequestMapingInfo进行逐个比较,找到最适合那个RequestMapingInfo。
@RequestMapping 的属性基本上都是String数组类型,在转成RequestMappingInfo对象时,同样会转成其他类型。
RequestMappingHandlerMapping.java 可以看到转换的过程:
/**
* Created a RequestMappingInfo from a RequestMapping annotation.
*/
protected RequestMappingInfo createRequestMappingInfo(RequestMapping annotation, RequestCondition<?> customCondition) {
String[] patterns = resolveEmbeddedValuesInPatterns(annotation.value());
return new RequestMappingInfo(
new PatternsRequestCondition(patterns, getUrlPathHelper(), getPathMatcher(),
this.useSuffixPatternMatch, this.useTrailingSlashMatch, this.fileExtensions),
new RequestMethodsRequestCondition(annotation.method()),
new ParamsRequestCondition(annotation.params()),
new HeadersRequestCondition(annotation.headers()),
new ConsumesRequestCondition(annotation.consumes(), annotation.headers()),
new ProducesRequestCondition(annotation.produces(), annotation.headers(), getContentNegotiationManager()),
customCondition);
}
现在我们来看一下。RequestMappingInfo、PattrensRequestCondition、RequestMethodsRequestCondition……。
通过上图可以看出他们都实现了RequestCondition。
典型的接口+模板.一个接口ReqeustCondition,一个抽象类,定义基础,然后n多的具体实现.
实现中可以分为3类:基础实现,外观类 和 容器
1、基础实现:
1、consumes对应request的提交内容类型content type,如application/json, text/html
2、headers 对应http request 的请求头
3、params 对应http request parameter
4、Patterns对应url,就是注解value中的配置
5、produces指定返回的内容类型的content type,仅当request请求头中的(Accept)类型中包含该指定类型才返回
6、requestMethods对应 http method,如GET,POST,PUT,DELETE等
2、外观类:
RequestConditionHolder,用于不知道具体是RequestCondition哪个子类时.自定义的条件,使用的这个进行封装
3、容器:
CompositeRequestCondition和RequestMappingInfo本身不带任何的匹配条件,只是用于包装其他的RequestCondition进行匹配
CompositeRequestCondition封装基础实现,具体的匹配都委托给基础实现类.
RequestMappingInfo,对应@RequestMapping注解,一一对应注解内容与基础实现,.
原码分析:
RequestCondition:
public interface RequestCondition<T> {
/**
* Defines the rules for combining this condition (i.e. the current instance)
* with another condition. For example combining type- and method-level
* {@link RequestMapping} conditions.
* @param other the condition to combine with.
* @return a request condition instance that is the result of combining
* the two condition instances.
*
* 将不同的筛选条件合并
*/
T combine(T other);
/**
* Checks if this condition matches the given request and returns a
* potentially new request condition with content tailored to the
* current request. For example a condition with URL patterns might
* return a new condition that contains matching patterns sorted
* with best matching patterns on top.
* @return a condition instance in case of a match;
* or {@code null} if there is no match
*
* 根据request查找匹配到的筛选条件
*/
T getMatchingCondition(HttpServletRequest request);
/**
* Compares this condition to another condition in the context of
* a specific request. This method assumes both instances have
* been obtained via {@link #getMatchingCondition(HttpServletRequest)}
* to ensure they have content relevant to current request only.
*
* 不同筛选条件比较,用于排序
*/
int compareTo(T other, HttpServletRequest request);
}
老规矩,接下来得上抽象类AbstractRequestCondition
AbstractRequestCondition做的事不多,覆写equals,hashCode,toString.实现equals,hashCode,toString时预留模板方法getContent();
实现toString时预留模板方法getToStringInfix().
/**
* A base class for {@link RequestCondition} types providing implementations of
* {@link #equals(Object)}, {@link #hashCode()}, and {@link #toString()}.
*
* @author Rossen Stoyanchev
* @since 3.1
*/
public abstract class AbstractRequestCondition<T extends AbstractRequestCondition<T>> implements RequestCondition<T> {
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj != null && getClass().equals(obj.getClass())) {
AbstractRequestCondition<?> other = (AbstractRequestCondition<?>) obj;
return getContent().equals(other.getContent());
}
return false;
}
@Override
public int hashCode() {
return getContent().hashCode();
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder("[");
for (Iterator<?> iterator = getContent().iterator(); iterator.hasNext();) {
Object expression = iterator.next();
builder.append(expression.toString());
if (iterator.hasNext()) {
builder.append(getToStringInfix());
}
}
builder.append("]");
return builder.toString();
}
/**
* 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();
/**
* The notation to use when printing discrete items of content.
* For example " || " for URL patterns or " && " for param expressions.
*/
protected abstract String getToStringInfix();
}
接下来再看一下RequestMappingInfo这个类。
这是一个集合容器类,包含类有7个condition。6个对应@RequestMaping中的属性,1个是用于不知道具体是RequestCondition哪个子类时.自定义的条件,使用的这个进行封装
/**
* 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 implements RequestCondition<RequestMappingInfo> {
private final PatternsRequestCondition patternsCondition;
private final RequestMethodsRequestCondition methodsCondition;
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.
* <p>Example: combine type- and method-level request mappings.
* @return a new request mapping info instance; never {@code null}
*/
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.
* <p>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
*/
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.
* <p>Note: It is assumed both instances have been obtained via
* {@link #getMatchingCondition(HttpServletRequest)} to ensure they have conditions with
* content relevant to current request.
*/
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();
}
}