本文分析RequestMappingInfoHandlerMapping類和RequestMappingHandlerMapping類骤铃。
RequestMappingInfoHandlerMapping類
RequestMappingInfoHandlerMapping類繼承了AbstractHandlerMethodMapping類,利用RequestMappingInfo類充當了映射的角色。
public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMethodMapping<RequestMappingInfo> {
protected RequestMappingInfoHandlerMapping() {
setHandlerMethodMappingNamingStrategy(new RequestMappingInfoHandlerMethodMappingNamingStrategy());
}
@Override
protected Set<String> getMappingPathPatterns(RequestMappingInfo info) {
return info.getPatternsCondition().getPatterns();
}
@Override
protected RequestMappingInfo getMatchingMapping(RequestMappingInfo info, HttpServletRequest request) {
return info.getMatchingCondition(request);
}
@Override
protected Comparator<RequestMappingInfo> getMappingComparator(final HttpServletRequest request) {
return new Comparator<RequestMappingInfo>() {
@Override
public int compare(RequestMappingInfo info1, RequestMappingInfo info2) {
return info1.compareTo(info2, request);
}
};
}
// 省略一些代碼
}
有匹配
上一篇文章提到AbstractHandlerMethodMapping類的handleMatch相當于回調函數(shù),表示有匹配的處理器時的執(zhí)行動作,RequestMappingInfoHandlerMapping類重寫了handleMatch方法。
@Override
protected void handleMatch(RequestMappingInfo info, String lookupPath, HttpServletRequest request) {
super.handleMatch(info, lookupPath, request);
String bestPattern;
Map<String, String> uriVariables;
Map<String, String> decodedUriVariables;
Set<String> patterns = info.getPatternsCondition().getPatterns();
if (patterns.isEmpty()) {
bestPattern = lookupPath;
uriVariables = Collections.emptyMap();
decodedUriVariables = Collections.emptyMap();
}
else {
bestPattern = patterns.iterator().next();
uriVariables = getPathMatcher().extractUriTemplateVariables(bestPattern, lookupPath);
decodedUriVariables = getUrlPathHelper().decodePathVariables(request, uriVariables);
}
request.setAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE, bestPattern);
request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, decodedUriVariables);
if (isMatrixVariableContentAvailable()) {
Map<String, MultiValueMap<String, String>> matrixVars = extractMatrixVariables(request, uriVariables);
request.setAttribute(HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE, matrixVars);
}
if (!info.getProducesCondition().getProducibleMediaTypes().isEmpty()) {
Set<MediaType> mediaTypes = info.getProducesCondition().getProducibleMediaTypes();
request.setAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, mediaTypes);
}
}
- 調用父類AbstractHandlerMethodMapping類的handleMatch方法;
- 獲得匹配映射的URL模式磨总,根據請求路徑提取并解碼URI模板參數(shù)(即路徑模式中{}表示的參數(shù)),將解碼后的模板參數(shù)填充到請求中傳遞下去笼沥;
- 提取并解碼URL矩陣參數(shù)蚪燕,將解碼后的矩陣參數(shù)填充到請求中傳遞下去娶牌。
無匹配
上一篇文章提到AbstractHandlerMethodMapping類的handleNoMatch相當于回調函數(shù),表示無匹配的處理器時的執(zhí)行動作馆纳,RequestMappingInfoHandlerMapping類重寫了handleNoMatch方法诗良,針對不匹配的原因分別拋出不同的異常。
@Override
protected HandlerMethod handleNoMatch(
Set<RequestMappingInfo> infos, String lookupPath, HttpServletRequest request) throws ServletException {
PartialMatchHelper helper = new PartialMatchHelper(infos, request);
if (helper.isEmpty()) {
return null;
}
if (helper.hasMethodsMismatch()) {
Set<String> methods = helper.getAllowedMethods();
if (HttpMethod.OPTIONS.matches(request.getMethod())) {
HttpOptionsHandler handler = new HttpOptionsHandler(methods);
return new HandlerMethod(handler, HTTP_OPTIONS_HANDLE_METHOD);
}
throw new HttpRequestMethodNotSupportedException(request.getMethod(), methods);
}
if (helper.hasConsumesMismatch()) {
Set<MediaType> mediaTypes = helper.getConsumableMediaTypes();
MediaType contentType = null;
if (StringUtils.hasLength(request.getContentType())) {
try {
contentType = MediaType.parseMediaType(request.getContentType());
}
catch (InvalidMediaTypeException ex) {
throw new HttpMediaTypeNotSupportedException(ex.getMessage());
}
}
throw new HttpMediaTypeNotSupportedException(contentType, new ArrayList<MediaType>(mediaTypes));
}
if (helper.hasProducesMismatch()) {
Set<MediaType> mediaTypes = helper.getProducibleMediaTypes();
throw new HttpMediaTypeNotAcceptableException(new ArrayList<MediaType>(mediaTypes));
}
if (helper.hasParamsMismatch()) {
List<String[]> conditions = helper.getParamConditions();
throw new UnsatisfiedServletRequestParameterException(conditions, request.getParameterMap());
}
return null;
}
RequestMappingHandlerMapping類
經過層層分析終于來到了RequestMappingHandlerMapping類鲁驶,它繼承了RequestMappingInfoHandlerMapping抽象類鉴裹,重寫了一些與映射有關的方法。
成員變量
RequestMappingHandlerMapping類的成員變量都與自己有關钥弯,其代碼如下所示:
public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping
implements MatchableHandlerMapping, EmbeddedValueResolverAware {
private boolean useSuffixPatternMatch = true;
private boolean useRegisteredSuffixPatternMatch = false;
private boolean useTrailingSlashMatch = true;
private ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager();
private StringValueResolver embeddedValueResolver;
private RequestMappingInfo.BuilderConfiguration config = new RequestMappingInfo.BuilderConfiguration();
public void setUseSuffixPatternMatch(boolean useSuffixPatternMatch) {
this.useSuffixPatternMatch = useSuffixPatternMatch;
}
public void setUseRegisteredSuffixPatternMatch(boolean useRegisteredSuffixPatternMatch) {
this.useRegisteredSuffixPatternMatch = useRegisteredSuffixPatternMatch;
this.useSuffixPatternMatch = (useRegisteredSuffixPatternMatch || this.useSuffixPatternMatch);
}
public void setUseTrailingSlashMatch(boolean useTrailingSlashMatch) {
this.useTrailingSlashMatch = useTrailingSlashMatch;
}
public void setContentNegotiationManager(ContentNegotiationManager contentNegotiationManager) {
Assert.notNull(contentNegotiationManager, "ContentNegotiationManager must not be null");
this.contentNegotiationManager = contentNegotiationManager;
}
@Override
public void setEmbeddedValueResolver(StringValueResolver resolver) {
this.embeddedValueResolver = resolver;
}
// 省略一些代碼
}
- useSuffixPatternMatch表示是否啟用后綴模式匹配径荔,默認啟用,若啟用那么映射到/users的方法也匹配/users.*(這里的點號就是實際的點號脆霎,不是正則表達式的元字符总处,星號才表示任意匹配,Ant-Style模式)睛蛛;
- useRegisteredSuffixPatternMatch表示是否啟用注冊后綴模式匹配辨泳,默認禁用,若啟用那么后綴模式匹配只針對顯式注冊到內容協(xié)商管理器的路徑擴展名玖院。啟用useRegisteredSuffixPatternMatch會啟用useSuffixPatternMatch;
- useTrailingSlashMatch表示是否啟用末尾斜線匹配第岖,默認啟用难菌,若啟用那么映射到/users的方法也匹配/users/;
- 上述三個屬性可以繼承WebMvcConfigurerAdapter類并重寫configurePathMatch方法進行顯式配置蔑滓,如:
@Configuration public class WebMvcConfigurer extends WebMvcConfigurerAdapter { @Override public void configurePathMatch(PathMatchConfigurer configurer) { configurer.setUseSuffixPatternMatch(true); configurer.setUseRegisteredSuffixPatternMatch(true); configurer.setUseTrailingSlashMatch(true); } }
初始化
RequestMappingHandlerMapping類重寫了AbstractHandlerMethodMapping類的afterPropertiesSet方法郊酒,先將各個屬性保存到配置,然后調用AbstractHandlerMethodMapping類的afterPropertiesSet發(fā)現(xiàn)被@RequestMapping注解修飾的方法键袱。
@Override
public void afterPropertiesSet() {
this.config = new RequestMappingInfo.BuilderConfiguration();
this.config.setUrlPathHelper(getUrlPathHelper());
this.config.setPathMatcher(getPathMatcher());
this.config.setSuffixPatternMatch(this.useSuffixPatternMatch);
this.config.setTrailingSlashMatch(this.useTrailingSlashMatch);
this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch);
this.config.setContentNegotiationManager(getContentNegotiationManager());
super.afterPropertiesSet();
}
AbstractHandlerMethodMapping類的isHandler抽象方法用于在發(fā)現(xiàn)HandlerMethod的過程中判斷bean是否需要被掃描以發(fā)現(xiàn)其中的HandlerMethod燎窘,RequestMappingHandlerMapping類重寫的isHandler方法如下。從代碼可以看到對RequestMappingHandlerMapping類來說蹄咖,只有bean被@Controller注解或者@RequestMapping注解修飾時才會被掃描褐健。
@Override
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
上一篇文章提到若Java方法是所需的處理器那么AbstractHandlerMethodMapping類的getMappingForMethod抽象方法為其生成映射并返回,否則返回null澜汤。那么為什么只有被@RequestMapping注解修飾的方法才是處理器呢蚜迅?答案在RequestMappingHandlerMapping類重寫的getMappingForMethod方法中:
@Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMappingInfo info = createRequestMappingInfo(method);
if (info != null) {
RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
if (typeInfo != null) {
info = typeInfo.combine(info);
}
}
return info;
}
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
RequestCondition<?> condition = (element instanceof Class ?
getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}
protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
return null;
}
protected RequestCondition<?> getCustomMethodCondition(Method method) {
return null;
}
protected RequestMappingInfo createRequestMappingInfo(
RequestMapping requestMapping, RequestCondition<?> customCondition) {
return RequestMappingInfo
.paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))
.methods(requestMapping.method())
.params(requestMapping.params())
.headers(requestMapping.headers())
.consumes(requestMapping.consumes())
.produces(requestMapping.produces())
.mappingName(requestMapping.name())
.customCondition(customCondition)
.options(this.config)
.build();
}
-
createRequestMappingInfo方法的作用:
- 調用AnnotatedElementUtils.findMergedAnnotation將Java元素(@RequestMapping可以用于類或方法)上的注解合并到@RequestMapping注解上,之所以這么做是因為Spring4.3增加了@GetMapping衣式、@PostMapping等簡化@RequestMapping的注解门怪;
- 可以通過getCustomTypeCondition和getCustomMethodCondition兩個方法分別自定義被注解類和被注解方法的請求匹配條件略水;
- 若@RequestMapping注解不存在,返回null表明該元素不是處理器刹帕,否則返回映射吵血;
getMappingForMethod首先為方法創(chuàng)建映射,若該方法是處理器則為該方法所在類創(chuàng)建映射并合并到方法的映射上偷溺。
查找匹配的HandlerMethod
RequestMappingHandlerMapping是如何具體查找匹配處理器方法的呢蹋辅?上文末尾提到AbstractHandlerMethodMapping類提到getMatchingMapping抽象方法用來檢查處理器方法映射是否與請求相匹配,若匹配則返回一個與當前請求有關的映射亡蓉,否則返回null晕翠。RequestMappingHandlerMapping類的getMatchingMapping定義在其父類RequestMappingInfoHandlerMapping類中,代碼如下所示:
@Override
protected RequestMappingInfo getMatchingMapping(RequestMappingInfo info, HttpServletRequest request) {
return info.getMatchingCondition(request);
}
可以看到該方法將判斷過程委托給了RequestMappingInfo的getMatchingCondition方法砍濒,下面重點看一下RequestMappingInfo類淋肾。
RequestMappingInfo類
RequestMappingInfo類表示@RequestMapping注解有關的信息。
成員變量
RequestMappingInfo類的成員變量和構造函數(shù)如下所示爸邢,可以看到RequestMappingInfo類的成員變量幾乎都是RequestCondition樊卓,與@RequestMapping注解的各個屬性相對應,從變量名即可分辨出用途杠河。
public final class RequestMappingInfo implements RequestCondition<RequestMappingInfo> {
private final String name;
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;
public RequestMappingInfo(String name, PatternsRequestCondition patterns, RequestMethodsRequestCondition methods,
ParamsRequestCondition params, HeadersRequestCondition headers, ConsumesRequestCondition consumes,
ProducesRequestCondition produces, RequestCondition<?> custom) {
this.name = (StringUtils.hasText(name) ? name : null);
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);
}
public RequestMappingInfo(PatternsRequestCondition patterns, RequestMethodsRequestCondition methods,
ParamsRequestCondition params, HeadersRequestCondition headers, ConsumesRequestCondition consumes,
ProducesRequestCondition produces, RequestCondition<?> custom) {
this(null, patterns, methods, params, headers, consumes, produces, custom);
}
public RequestMappingInfo(RequestMappingInfo info, RequestCondition<?> customRequestCondition) {
this(info.name, info.patternsCondition, info.methodsCondition, info.paramsCondition, info.headersCondition,
info.consumesCondition, info.producesCondition, customRequestCondition);
}
public String getName() {
return this.name;
}
public PatternsRequestCondition getPatternsCondition() {
return this.patternsCondition;
}
public RequestMethodsRequestCondition getMethodsCondition() {
return this.methodsCondition;
}
public ParamsRequestCondition getParamsCondition() {
return this.paramsCondition;
}
public HeadersRequestCondition getHeadersCondition() {
return this.headersCondition;
}
public ConsumesRequestCondition getConsumesCondition() {
return this.consumesCondition;
}
public ProducesRequestCondition getProducesCondition() {
return this.producesCondition;
}
public RequestCondition<?> getCustomCondition() {
return this.customConditionHolder.getCondition();
}
// 省略一些代碼
}
合并方法
combine合并方法用來將一個RequestMappingInfo合并到另一個RequestMappingInfo中碌尔,如將Controller類的@RequestMapping注解信息合并到該類各方法的@RequestMapping注解上,在RequestMappingHandlerMapping類的getMappingForMethod方法中便做了此操作(見上文)券敌。
/**
* Combine "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}
*/
@Override
public RequestMappingInfo combine(RequestMappingInfo other) {
String name = combineNames(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(name, patterns,
methods, params, headers, consumes, produces, custom.getCondition());
}
private String combineNames(RequestMappingInfo other) {
if (this.name != null && other.name != null) {
String separator = RequestMappingInfoHandlerMethodMappingNamingStrategy.SEPARATOR;
return this.name + separator + other.name;
}
else if (this.name != null) {
return this.name;
}
else {
return other.name;
}
}
匹配請求
上文提到RequestMappingHandlerMapping類在查找匹配的HandlerMethod時將判斷過程委托給了RequestMappingInfo的getMatchingCondition方法唾戚,該方法代碼如下:
/**
* 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
*/
@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(this.name, patterns,
methods, params, headers, consumes, produces, custom.getCondition());
}
從代碼可以看到各個判斷條件依次調用了getMatchingCondition接口方法,若匹配則返回條件否則返回null待诅,因此只要這些條件有一個不匹配那么該RequestMappingInfo就不與請求匹配叹坦。
以PatternsRequestCondition為例,其getMatchingCondition代碼如下:
@Override
public PatternsRequestCondition getMatchingCondition(HttpServletRequest request) {
if (this.patterns.isEmpty()) {
return this;
}
String lookupPath = this.pathHelper.getLookupPathForRequest(request);
List<String> matches = getMatchingPatterns(lookupPath);
return matches.isEmpty() ? null :
new PatternsRequestCondition(matches, this.pathHelper, this.pathMatcher, this.useSuffixPatternMatch,
this.useTrailingSlashMatch, this.fileExtensions);
}
public List<String> getMatchingPatterns(String lookupPath) {
List<String> matches = new ArrayList<String>();
for (String pattern : this.patterns) {
String match = getMatchingPattern(pattern, lookupPath);
if (match != null) {
matches.add(match);
}
}
Collections.sort(matches, this.pathMatcher.getPatternComparator(lookupPath));
return matches;
}
private String getMatchingPattern(String pattern, String lookupPath) {
if (pattern.equals(lookupPath)) {
return pattern;
}
if (this.useSuffixPatternMatch) {
if (!this.fileExtensions.isEmpty() && lookupPath.indexOf('.') != -1) {
for (String extension : this.fileExtensions) {
if (this.pathMatcher.match(pattern + extension, lookupPath)) {
return pattern + extension;
}
}
}
else {
boolean hasSuffix = pattern.indexOf('.') != -1;
if (!hasSuffix && this.pathMatcher.match(pattern + ".*", lookupPath)) {
return pattern + ".*";
}
}
}
if (this.pathMatcher.match(pattern, lookupPath)) {
return pattern;
}
if (this.useTrailingSlashMatch) {
if (!pattern.endsWith("/") && this.pathMatcher.match(pattern + "/", lookupPath)) {
return pattern +"/";
}
}
return null;
}
- getMatchingCondition方法中先得到該請求的查找路徑卑雁;
- 然后利用getMatchingPatterns方法得到與查找路徑匹配的所有形式募书,可以看到getMatchingPattern中使用了useSuffixPatternMatch、fileExtensions和useTrailingSlashMatch屬性测蹲,這些分別與RequestMappingHandlerMapping類的useSuffixPatternMatch莹捡、useRegisteredSuffixPatternMatch和useTrailingSlashMatch屬性相對應。
- 這些屬性被賦值是因為在RequestMappingHandlerMapping為Java方法創(chuàng)建RequestMappingInfo對象時由createRequestMappingInfo方法調用了RequestMappingInfo.Builder扣甲。
總結
通過兩篇文章的深入分析篮赢,Spring MVC中@RequestMapping注解背后的原理逐漸明晰,其他HandlerMapping實現(xiàn)類大同小異琉挖,在此不再贅述荷逞。