本文源码基于SpringMVC 5.2.7版本
在《抽丝剥茧DispatcherServlet》一文中,提到了HandlerMapping。HandlerMapping不是具体的Handler,它的作用是找到与当前Request匹配的Handler。可以看出HandlerMapping是DispatcherServlet能够正常工作的一个重要组件。接下来详细介绍HandlerMapping及其重要实现类RequestMappingHandlerMapping。关于HandlerMapping,我们需要了解:

DispatcherServlet如何装配HandlerMapping
在《抽丝剥茧DispatcherServlet》中介绍了DispatcherServlet初始化过程,简单来说就是系统在启动的时候会调用DispatcherServlet的initStrategies方法,该方法依次装配DispatcherServlet需要的组件,其中就包括HandlerMapping。具体的源码位于org.springframework.web.servlet.DispatcherServlet#initHandlerMappings。装配HandlerMapping的过程如下:
- 如果"detectAllHandlerMappings"打开,则从IOC容器中获取所有类型为HandlerMapping的实例;否则进入2
- 从IOC容器中获取name为"handlerMapping"的实例;
- 如果步骤1、2之后已经有HandlerMapping则装配过程结束,否则进入4;
- 通过DispatcherServlet默认装配策略中创建HandlerMapping实例,并装配给DispatcherServlet,装配过程结束。
默认情况“detectAllHandlerMappings”是打开的,也就是说默认情况是从IOC容器中找到所有HandlerMapping的Bean。
DispatcherServlet默认装配策略
如果开发者没有配置HandlerMapping的Bean,那么DispatcherServlet就会执行默认装配策略。DispatcherServlet初始化对象时候就是从文件"DispatcherServlet.properties"中获取各组件默认的类名。DispatcherServlet.properties文件位于spring-webmvc.jar包,与DispatcherServlet.class在同一个目录。
public class DispatcherServlet extends FrameworkServlet {
... ...
static {
// Load default strategy implementations from properties file.
// This is currently strictly internal and not meant to be customized
// by application developers.
try {
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
}
}
... ...
}
DispatcherServlet.properties内入如下,就是一个个key-value,key是DispatcherServlet所需要的组件的接口全名称,value是具体的实现类(多个用“,”隔开)
# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
org.springframework.web.servlet.function.support.RouterFunctionMapping
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
org.springframework.web.servlet.function.support.HandlerFunctionAdapter
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
所谓的默认装配策略就是从DispatcherServlet.properties读取实际的实现类,然后实例化对象出来。
RequestMappingHandlerMapping结构
RequestMappingHandlerMapping是HandlerMapping的一个重要实现类,在SpringMVC中大部分请求都是通过RequestMappingHandlerMapping找到对应Handler的。RequestMappingHandlerMapping的类图如下:

RequestMappingHandlerMapping主要相关接口、类的属性、方法如下
HandlerMapping接口:
- getHandler(HttpServletRequest request):根据request获取对应的Handler。
AbstractHandlerMapping抽象类:
- urlPathHelper:url路径解析的工具类,类型是org.springframework.web.util.UrlPathHelper。
- pathMatcher:路径匹配的工具类,类型是org.springframework.util.AntPathMatcher。
- interceptors:额外的拦截器,元素类型是Object,RequestMappingHandlerMapping初始化的时候会将其包装并添加到adaptedInterceptors。
- adaptedInterceptors:SpringMVC拦截器,是一个List,元素类型是org.springframework.web.servlet.HandlerInterceptor。元素来自2部分,其一,容器中配置的HandlerInterceptor类型的Bean,其二是上面介绍的interceptors。
AbstractHandlerMethodMapping抽象类:
- mappingRegistry:保存着Handler,类型是org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry。
MappingRegistry类:
- mappingLookup:Map,key是RequestMappingInfo对象, value是对应Handler。
- urlLookup:Map,key是url,value是RequestMappingInfo对象。
- register:成员方法,向MappingRegistry注册Handler。
RequestMappingInfo类:
RequestMappingInfo里面就是一系列条件,这些条件负责匹配request是否匹配该RequestMappingInfo。
- patternsCondition:路径模式条件,与@RequestMapping注解的path对应,类型是org.springframework.web.servlet.mvc.condition.PatternsRequestCondition。
- methodsCondition:请求方法条件,与@RequestMapping注解的method对应,类型是org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition。
- paramsCondition:参数参数条件,与@RequestMapping注解的params对应,类型是org.springframework.web.servlet.mvc.condition.ParamsRequestCondition。
- headersCondition:请求头条件,与@RequestMapping注解的headers
对应,类型是org.springframework.web.servlet.mvc.condition.HeadersRequestCondition。 - consumesCondition:请求头Content-type条件即请求内容类型条件,与@RequestMapping注解的consumes对应,类型是org.springframework.web.servlet.mvc.condition.ConsumesRequestCondition。
- producesCondition:请求头Accept条件,与@RequestMapping注解的produces
对应,类型是org.springframework.web.servlet.mvc.condition.ProducesRequestCondition。 - customConditionHolder:自定义的一些条件,一般没有,需要自定义实现,类型是org.springframework.web.servlet.mvc.condition.RequestConditionHolder。
RequestMappingHandlerMapping初始化
前面介绍了RequestMappingHandlerMapping相关主要接口和类的属性,那么这些属性是在什么时候赋值的,尤其Handler是什么时候注册到mappingRegistry的。
从RequestMappingHandlerMapping的类图还可以看到它实现类接口org.springframework.beans.factory.InitializingBean。Spring IOC机制在创建Bean的时候调用该接口的afterPropertiesSet的方法。在RequestMappingHandlerMapping实现中调用initHandlerMethods方法。
public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {
... ...
protected void initHandlerMethods() {
for (String beanName : getCandidateBeanNames()) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
processCandidateBean(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}
... ...
}
很明显,遍历候选的Bean然后处理,我们看下候选的Bean有哪些?
protected String[] getCandidateBeanNames() {
return (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :
obtainApplicationContext().getBeanNamesForType(Object.class));
}
过分,Object.class的Bean都是候选的Bean,那不就是all? 继续往下看,它对候选的Bean做了什么处理
protected void processCandidateBean(String beanName) {
Class<?> beanType = null;
try {
beanType = obtainApplicationContext().getType(beanName);
}
catch (Throwable ex) {
// An unresolvable bean type, probably from a lazy bean - let's ignore it.
if (logger.isTraceEnabled()) {
logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
}
}
if (beanType != null && isHandler(beanType)) {
detectHandlerMethods(beanName);
}
}
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
过滤有注解@Controller或者注解@RequestMapping的Bean,探测该Bean里面的可以处理Http请求的方法,即被注解@RequestMapping所注解的方法。对于符合条件的方法,会创建一个RequestMappingInfo对象。RequestMappingInfo对象包含了一些条件,这些条件决定了requst是否满足。如果所有条件都满足,则说明该RequestMappingInfo对应的方法将处理该request。源码如下
public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping
implements MatchableHandlerMapping, EmbeddedValueResolverAware {
... ...
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
//笔者注:先根据menthod创建RequestMappingInfo
RequestMappingInfo info = createRequestMappingInfo(method);
if (info != null) {
//笔者注:根据class创建RequestMappingInfo,然后合并RequestMappingInfo
RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
if (typeInfo != null) {
info = typeInfo.combine(info);
}
//笔者注:如果配置了路径前缀(一般不会配置),同样需要合并RequestMappingInfo
String prefix = getPathPrefix(handlerType);
if (prefix != null) {
info =
RequestMappingInfo.paths(prefix).options(this.config).build().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 RequestMappingInfo createRequestMappingInfo(
RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition)
{
RequestMappingInfo.Builder builder = RequestMappingInfo
.paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))
.methods(requestMapping.method())
.params(requestMapping.params())
.headers(requestMapping.headers())
.consumes(requestMapping.consumes())
.produces(requestMapping.produces())
.mappingName(requestMapping.name());
if (customCondition != null) {
builder.customCondition(customCondition);
}
return builder.options(this.config).build();
}
... ...
}
public final class RequestMappingInfo implements RequestCondition<RequestMappingInfo> {
... ...
public RequestMappingInfo build() {
PatternsRequestCondition patternsCondition = ObjectUtils.isEmpty(this.paths) ? null :
new PatternsRequestCondition(
this.paths, this.options.getUrlPathHelper(), this.options.getPathMatcher(),
this.options.useSuffixPatternMatch(), this.options.useTrailingSlashMatch(),
this.options.getFileExtensions());
ContentNegotiationManager manager = this.options.getContentNegotiationManager();
return new RequestMappingInfo(this.mappingName, patternsCondition,
ObjectUtils.isEmpty(this.methods) ?
null : new RequestMethodsRequestCondition(this.methods),
ObjectUtils.isEmpty(this.params) ?
null : new ParamsRequestCondition(this.params),
ObjectUtils.isEmpty(this.headers) ?
null : new HeadersRequestCondition(this.headers),
ObjectUtils.isEmpty(this.consumes) && !this.hasContentType ?
null : new ConsumesRequestCondition(this.consumes, this.headers),
ObjectUtils.isEmpty(this.produces) && !this.hasAccept ?
null : new ProducesRequestCondition(this.produces, this.headers, manager),
this.customCondition);
}
... ...
}
创建RequestMappingInfo的工作交给RequestMappingInfo.Builder完成。框架将扫描到的@RequstMapping注解中的参数值赋予Builder,Builder则去创建RequestMappingInfo对象。随后框架向MappingRegistry注册该RequestMappingInfo,注册的源码位于org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry#register,很简单就是往Map里面添加元素。值得注意的是注册的处理方法实际上是HandlerMethod对象。
public HandlerMethod(Object bean, Method method) {
Assert.notNull(bean, "Bean is required");
Assert.notNull(method, "Method is required");
this.bean = bean;
this.beanFactory = null;
this.beanType = ClassUtils.getUserClass(bean);
this.method = method;
this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
this.parameters = initMethodParameters();
evaluateResponseStatus();
this.description = initDescription(this.beanType, this.method);
}
bridgedMethod是方法的原始方法,如果method是桥接方法(isBridge()),则这里努力找到原始方法。桥接方法是java编译器配合类型擦除而自动生成的方法。这里不做说明,大家去看看java的桥接方法的来龙去脉。
RequestMappingHandlerMapping核心逻辑
RequestMappingHandlerMapping的作用就是从注册的Handler,找到request对应的Handler。在RequestMappingHandlerMapping初始化的时候,检测出了应用所有的@RequestMapping注解所修饰的方法,将其注册到注册中心,并根据@RequestMapping的配置创建一系列的RequestCondition对象。RequestMappingHandlerMapping匹配Handler的逻辑如下:

RequstMappingInfo的条件匹配过程是依次匹配methods条件、params条件、请求头条件等等,源码如下:
public final class RequestMappingInfo implements RequestCondition<RequestMappingInfo> {
... ...
public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {
RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(request);
if (methods == null) {
return null;
}
ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(request);
if (params == null) {
return null;
}
HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(request);
if (headers == null) {
return null;
}
ConsumesRequestCondition consumes = this.consumesCondition.getMatchingCondition(request);
if (consumes == null) {
return null;
}
ProducesRequestCondition produces = this.producesCondition.getMatchingCondition(request);
if (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());
}
... ...
}
RequstMappingInfo中的条件都是RequestCondition的实现类,RequestCondition接口有3个方法,各实现类必须实现。
- combine:合并两个同类的RequestCondition对象。
- getMatchingCondition:判断request是否匹配该条件。
- compareTo:比较两个同类的RequestCondition对象。
下面从构造实例、匹配两点依次分析各个实现类。
RequestMethodsRequestCondition
RequestMethodsRequestCondition用来匹配请求的方式,常见的方式有get、post、head、put、delete、options等等
构造实例
构造RequestMethodsRequestCondition是在创建RequestMappingInfo的时候创建的,我们看下org.springframework.web.servlet.mvc.method.RequestMappingInfo.DefaultBuilder#build。RequestMethodsRequestCondition的关键成员变量如下:
- methods:实例变量,RequestMethod集合,值来源于@RequestMapping的method
- requestMethodConditionCache:静态变量,预先给HTTP协议支持的每个方式创建的RequestMethodsRequestCondition对象,这个对象的methods只包含一个方式
匹配逻辑
跨域时浏览器先发出一个预检请求(OPTIONS方式),预检通过才发出实际请求。预检请求在请求头"Access-Control-Request-Method"中指定实际的请求方式,所以RequestMethodsRequestCondition匹配逻辑兼容这一点。源码在org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition#getMatchingCondition。
- 如果是跨域预请求(即OPTIONS方式)
- 如果RequestMethodsRequestCondition的methods为空则返回匹配自己
- 否则判断this.methods中是否包含请求头"Access-Control-Request-Method"指定的方式,包含则从requestMethodConditionCache返回该方式对应的对象,否则返回null表示未匹配
- 不是OPTIONS请求
- 如果RequestMethodsRequestCondition的methods为空则返回匹配自己
- 否则判断this.methods中是否包含该方式,包含则从requestMethodConditionCache返回该方式对应的对象,否则返回null表示未匹配
ParamsRequestCondition
ParamsRequestCondition是用来匹配请求参数是否符合@RequestMapping指定的要求,例如不能包含某个参数key或者某个参数key值必须等于某个值。
构造实例
ParamsRequestCondition的构造逻辑与RequestMethodsRequestCondition一样,也是随着RequestMappingInfo创建。其关键成员变量如下:
- expressions:ParamExpression类型的集合,@RequestMapping注解的params属性的每个值将解析成ParamExpression类型的对象
ParamExpression是org.springframework.web.servlet.mvc.condition.AbstractNameValueExpression的实现类,AbstractNameValueExpression支持正反逻辑的表达式
| 形式 | 描述 | 备注 |
| !myName | 请求参数中不包含myName或myName.x或myName.y | .x、.y是前端利用图片提交表单,参数中会带鼠标的点击位置 <input type="image" src="xxx.gif" οnclick="return dosubmit();"> |
| myName | 请求参数中包含myName或myName.x或myName.y | |
| myName=myValue | 请求参数包含myName或myName.x或myName.y且值等于myValue | |
| myName!=myValue | 请求参数包含myName或myName.x或myName.y且值不等于myValue |
匹配逻辑
ParamsRequestCondition匹配逻辑非常简单,依次匹配参数表达式集合,有1个不匹配则返回null,否则返回自身。源码在org.springframework.web.servlet.mvc.condition.ParamsRequestCondition#getMatchingCondition。
HeadersRequestCondition
HeadersRequestCondition用来匹配请求头是否符合@RequestMapping中指定的请求头表达式,例如不能包含某个请求头或者某个请求头值必须等于某个值。
构造实例
HeadersRequestCondition的关键成员变量如下:
- expressions:HeaderExpression类型集合,@RequestMapping注解的headers属性的每个值将解析成HeaderExpression类型的对象
- PRE_FLIGHT_MATCH:静态变量,expressions集合为空的HeadersRequestCondition对象,预检请求时返回
HeaderExpression也是是org.springframework.web.servlet.mvc.condition.AbstractNameValueExpression的实现类,其逻辑与ParamExpression基本一致,参见ParamExpression的匹配表格。区别在于HeaderExpression不需要匹配myName.x和myName.y。
匹配逻辑
HeadersRequestCondition匹配逻辑非常简单,源码在org.springframework.web.servlet.mvc.condition.HeadersRequestCondition#getMatchingCondition
- 如果是预检请求(OPTIONS)则直接返回静态变量PRE_FLIGHT_MATCH
- 否则依次匹配参数表达式集合,有1个不匹配则返回null,否则返回自身
ConsumesRequestCondition
ConsumesRequestCondition用来判断请求头Content-Type所表示media是否符合@RequestMapping指定的media。
构造实例
ConsumesRequestCondition关键成员变量如下:
- expressions:ConsumeMediaTypeExpression类型集合,@RequestMapping属性consumes的每个值将解析为ConsumeMediaTypeExpression对象
- bodyRequired:bool类型,表示请求的body是否必须有,请求头"Content-Length"不为"0"或者请求头"Transfer-Encoding"不为空的请求视为body不为空
- EMPTY_CONDITION:静态变量,表示没有配置Content-Type匹配条件即匹配所有
ConsumeMediaTypeExpression是org.springframework.web.servlet.mvc.condition.AbstractMediaTypeExpression实现类,支持正反表达式
| 形式 | 描述 | 备注 |
| */* | 匹配所有MediaType | |
| !application/json | 不匹配application/json |
所有支持的MediaType
ALL = new MediaType("*", "*");
APPLICATION_ATOM_XML = new MediaType("application", "atom+xml");
APPLICATION_CBOR = new MediaType("application", "cbor");
APPLICATION_FORM_URLENCODED = new MediaType("application", "x-www-form-urlencoded");
APPLICATION_JSON = new MediaType("application", "json");
APPLICATION_JSON_UTF8 = new MediaType("application", "json", StandardCharsets.UTF_8);
APPLICATION_OCTET_STREAM = new MediaType("application", "octet-stream");
APPLICATION_PDF = new MediaType("application", "pdf");
APPLICATION_PROBLEM_JSON = new MediaType("application", "problem+json");
APPLICATION_PROBLEM_JSON_UTF8 = new MediaType("application", "problem+json", StandardCharsets.UTF_8);
APPLICATION_PROBLEM_XML = new MediaType("application", "problem+xml");
APPLICATION_RSS_XML = new MediaType("application", "rss+xml");
APPLICATION_STREAM_JSON = new MediaType("application", "stream+json");
APPLICATION_XHTML_XML = new MediaType("application", "xhtml+xml");
APPLICATION_XML = new MediaType("application", "xml");
IMAGE_GIF = new MediaType("image", "gif");
IMAGE_JPEG = new MediaType("image", "jpeg");
IMAGE_PNG = new MediaType("image", "png");
MULTIPART_FORM_DATA = new MediaType("multipart", "form-data");
MULTIPART_MIXED = new MediaType("multipart", "mixed");
MULTIPART_RELATED = new MediaType("multipart", "related");
TEXT_EVENT_STREAM = new MediaType("text", "event-stream");
TEXT_HTML = new MediaType("text", "html");
TEXT_MARKDOWN = new MediaType("text", "markdown");
TEXT_PLAIN = new MediaType("text", "plain");
TEXT_XML = new MediaType("text", "xml");
匹配逻辑
匹配逻辑源码在org.springframework.web.servlet.mvc.condition.ConsumesRequestCondition#getMatchingCondition 。
- 1. 如果是预检请求则直接返回EMPTY_CONDITION,表示匹配
- 2. 如果expresstions为空,则返回自身,表示匹配
- 3. 如果bodyRequired为true,且请求body不为空则返回EMPTY_CONDITION,表示匹配。请求头"Content-Length"不为"0"或者请求头"Transfer-Encoding"不为空的请求视为body不为空
- 4. 解析请求头"Content-Type"表示的Media
- 5. 依次遍历expressions集合中元素(用expression表示)
- 5.1 如果expression匹配"Content-Type"表示的Media,则该expression作为候选加入到候选集
- 6. 如果候选集合不为空,则新建ConsumesRequestCondition对象返回,并将候选集赋予新对象的expressions集合;如果候选集合为空,则返回null表示不匹配
ProducesRequestCondition
ProducesRequestCondition用来判断请求头"Accept"表示的Media是否符合@RequestMapping指定的Media。
构造实例
ProducesRequestCondition关键成员变量如下:
- expressions:ProduceMediaTypeExpression类型集合,@RequestMapping属性produces的每个值将解析为ConsumeMediaTypeExpression对象
- contentNegotiationManager:ContentNegotiationManager类型对象,用来解析请求可接受的Media
- DEFAULT_CONTENT_NEGOTIATION_MANAGER:静态变量,默认的ContentNegotiationManager对象
- EMPTY_CONDITION:静态变量,ProducesRequestCondition对象,表示没有配置Accept匹配条件即匹配所有
- MEDIA_TYPE_ALL_LIST:静态变量,ProduceMediaTypeExpression集合,包含一个匹配所有的Medai(*/*)
ProduceMediaTypeExpression也是org.springframework.web.servlet.mvc.condition.AbstractMediaTypeExpression实现类,支持正反表达式。它与ConsumeMediaTypeExpression不同点在于
- ProduceMediaTypeExpression需要匹配MediaType的paramters
- ProduceMediaTypeExpression匹配是可接受Media列表与@RequestMapping.produces的双向兼容,任何一方兼容另一方都可;而ConsumeMediaTypeExpression则是@RequestMapping.consumes包含"Content-Type"
ProduceMediaTypeExpression匹配的源码在org.springframework.web.servlet.mvc.condition.ProducesRequestCondition.ProduceMediaTypeExpression#match。
匹配逻辑
- 1. 如果是预检请求则直接返回EMPTY_CONDITION,表示匹配
- 2. 如果expresstions为空,则返回自身,表示匹配
- 3. 解析请求可接受的Media列表,解析逻辑源码在org.springframework.web.accept.ContentNegotiationManager#resolveMediaTypes
- 4. 依次遍历expressions集合中元素(用expression表示)
- 4.1 如果expression匹配可接受的Media列表,则该expression作为候选加入到候选集合
- 5. 如果候选集合不为空,则新建ProducesRequestCondition对象返回并将候选集合赋予新对象的expressions集合;
- 6. 如果候选集合为空但是可接受列表中包含"*/*",则返回EMPTY_CONDITION,表示匹配
- 7. 否则返回null,表示不匹配
PatternsRequestCondition
PatternsRequestCondition用来匹配请求路径是否满足@RequestMapping注解指定的路径模式。路径模式是ant风格的模式。
构造实例
PatternsRequestCondition的关键成员变量如下:
- patterns:String集合,表示该条件支持的ant风格的路径模式,值来源于@RequestMapping的path属性
- pathHelper:UrlPathHelper对象,提供从request获取请求路径的工具方法
- pathMatcher:PathMatcher对象,默认实现类是org.springframework.util.AntPathMatcher,将请求路径与路径模式进行匹配的工具类
- useSuffixPatternMatch:bool类型,表示后缀是否参与匹配
- useTrailingSlashMatch:bool类型,表示是否需要在模式末尾加上"/"参与匹配
- fileExtensions:String集合,表示支持的后缀类型,与useSuffixPatternMatch配合使用
- EMPTY_PATH_PATTERN:静态变量,包含一个空字符串("")的路径模式
匹配逻辑
PatternsRequestCondition的匹配就是将ant风格的patterns与请求路径进行匹配。ant风格模式的匹配规则如下
- ?匹配1个任意字符。
- * 匹配0个或多个任意字符。
- ** 匹配0个或多个“目录” 。
- {变量名} 匹配路径变量, pattern上"{"、"}"之间的字符作为路径变量名,URL路径上匹配"{"、"}"之间的字符作为路径变量的值。例如pattern是/user/{userId},请求的URL是/user/12345,那么路径变量名就是userId,值就是12345。
- {变量名:java正则} 匹配符合指定正则的路径变量,且路径上变量值要符合冒号(:)后的java正则。例如pattern是/user/{userId:}。例如pattern是/user/{userId:\\d+},要求匹配的这个pattern的请求路径中userId必须是数字,请求的URL是/user/12345,那么路径变量名就是userId,值就是12345。但是如果请求的路径是/user/123abc就不会命中这个pattern是123abc不是纯数字。
PatternsRequestCondition匹配逻辑大致分以下步骤,org.springframework.web.servlet.mvc.condition.PatternsRequestCondition#getMatchingCondition
- 1. 通过UrlPathHelper获取请求路径(用path表示)
- 2. 依次遍历路径模式集合的元素(用pattern表示)
- 2.1 将path和pattern分别用 "/"分割成数组path目录数组和pattern目录数组
- 2.2 依次从path目录数组和pattern目录数组取出元素
- 2.2.1 将pattern数组元素(ant风格模式)变换为java正则表达式
- 2.2.2 判断path数组元素是否符合正则表达式
- 2.2.3 如果path目录数组中每个元素都能按顺序匹配到pattern目录数组(注意path目录数组和pattern目录数组元素不是一一对应,因为**匹配0个或多个目录),则说明该pattern符合条件,作为候选pattern加入候选集合
- 2.3 返回候选集合
- 3. 候选集合不为空,则表示匹配,则新建PatternsRequestCondition对象作为返回值并将候选集合赋予新的PatternsRequestCondition对象;如果集合为空,则返回null表示为匹配路径条件
ant规则匹配的源码org.springframework.util.AntPathMatcher#doMatch
在ant规则匹配中比较重要的一环是将ant风格pattern转换为java的正则表达式,那么如何将ant风格pattern转换为java的正则表达式?其逻辑如下,源码在org.springframework.util.AntPathMatcher.AntPathStringMatcher#AntPathStringMatcher(java.lang.String, boolean)
- 如果pattern中有字符"?",用字符"."替代
- 如果pattern中有字符"*",用字符串".*"替代
- 如果字符中有形如"{变量名}"的格式,用字符串"(.*)"替代 。与字符串".*"不同,"(.*)"在java正则匹配的时候能从group中取到命中的字符串,可以作为变量名对应的值
- 如果字符中有形如"{变量名:java正则}",用字符串"(java正则)"替代
- 其余部分全部转为字符常量(通过Pattern.quote实现),字符常量将精确匹配不作为任何模式匹配
举例:\\Q、\\E之间的就是字符常量,精确匹配"."就是真实的字符"."
| 替换前 | 替换后 | 路径示例 | 备注 |
| t?st.jsp | \\Qt\\E.\\Qst.jsp\\E | test.jsp | \\Q、\\E之间的就是字符常量,精确匹配"."就是真实的字符"." |
| t*st.jsp | \\Qt\\E.*\\Qst.jsp\\E | tabcst.jsp | |
| url_{view} | url_(.*) | url_abc123 | 其中view作为路径变量名,abc123作为view的值 |
| url_{view:[a-z]+} | url_([a-z]+) | url_abc | 其中view作为变量名,abc作为view的值,注意url_abc123无法命中,因为只能命中a-z中的字母 |
RequestMappingHandlerMapping匹配后处理(handleMatch)
匹配后处理主要处理三件事情
- 如果有PatternsRequestCondition匹配,则抽取路径变量并添加到requst属性"org.springframework.web.servlet.HandlerMapping.uriTemplateVariables"上
- 如果不支持"移除分号(;)"特性,即org.springframework.web.util.UrlPathHelper#removeSemicolonContent值为false,则解析【1】中的路径变量上的";"得到matrixVariables,并添加到request属性"org.springframework.web.servlet.HandlerMapping.matrixVariables"。将配合注解@MatrixVariable做参数解析
- 如果有ProducesRequestCondition匹配,则将对应的MediaType集合添加到request属性"org.springframework.web.servlet.HandlerMapping.producibleMediaTypes"上
源码现场在下面
public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMethodMapping<RequestMappingInfo> {
......
protected void handleMatch(RequestMappingInfo info, String lookupPath, HttpServletRequest request) {
super.handleMatch(info, lookupPath, request);
String bestPattern;
Map<String, String> uriVariables;
Set<String> patterns = info.getPatternsCondition().getPatterns();
if (patterns.isEmpty()) {
bestPattern = lookupPath;
uriVariables = Collections.emptyMap();
}
else {
bestPattern = patterns.iterator().next();
uriVariables = getPathMatcher().extractUriTemplateVariables(bestPattern, lookupPath);
}
request.setAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE, bestPattern);
if (isMatrixVariableContentAvailable()) {
Map<String, MultiValueMap<String, String>> matrixVars = extractMatrixVariables(request, uriVariables);
request.setAttribute(HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE, matrixVars);
}
Map<String, String> decodedUriVariables = getUrlPathHelper().decodePathVariables(request, uriVariables);
request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, decodedUriVariables);
if (!info.getProducesCondition().getProducibleMediaTypes().isEmpty()) {
Set<MediaType> mediaTypes = info.getProducesCondition().getProducibleMediaTypes();
request.setAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, mediaTypes);
}
}
... ...
}
如何抽取路径变量? 重新走了一遍AntPathMatcher的match过程,参考PatternsRequestCondition的匹配逻辑。
RequestMappingHandlerMapping配置
RequestMappingHandlerMapping配置方式很多,其实重要包装Spring IOC容器中有这个对象即可,任何配置Spring Bean的方式都可配置RequestMappingHandlerMapping。下面列以下典型的配置方式:
默认策略
如果开发者什么也不配置,DispatcherServlet也会根据默认策略,创建一组默认的HandlerMappings其中就包括一个RequestMappingHandlerMapping。本文的开头就已经介绍了默认策略,此处不做赘述。
web.xml方式启动MVC
web.xml方式又有两种配置RequestMappingHandlerMapping的方法(1) 配置一个普通的bean (2)利用Spring Schema机制配置标签<mvc:annotation-driven />
普通的Bean
最简单的配置方式,只要在Beans文件中配置一个简单RequestMappingHandlerMapping即可

Spring Schema机制
只需要配置<mvc:annotation-driven /> ,这种方式的原理在于Spring Schema机制《Spring中自定义Schema配置Bean》。spring-mvc的包中也实现了自定义的Schema-org.springframework.web.servlet.config.MvcNamespaceHandler。在MvcNamespaceHandler识别xml的标签<annotation-driven>并注册RequestMappingHandlerMapping类型的Bean到Spring IOC容器。

@EnableWebMvc方式启动MVC
@EnableWebMvc方式只需要在某个bean前面加入注解@EnableWebMvc即可

这种方式是因为@EnableWebMvc引入了org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration。DelegatingWebMvcConfiguration是WebMvcConfigurationSupport子类,WebMvcConfigurationSupport配置了@Bean注入RequestMappingHandlerMapping。
public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {
... ...
@Bean
@SuppressWarnings("deprecation")
public RequestMappingHandlerMapping requestMappingHandlerMapping(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
mapping.setOrder(0);
mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
mapping.setContentNegotiationManager(contentNegotiationManager);
mapping.setCorsConfigurations(getCorsConfigurations());
... ...
}
... ...
}
SpringBoot方式启动MVC
同样进入boot时代,配置更简单了,只要在pom中引入starter-web即可
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${boot.version}</version>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
本文详细阐述了SpringMVC中的RequestMappingHandlerMapping的作用、装配过程、核心逻辑及配置方法,包括默认策略、web.xml配置、SpringBoot启动等。
1万+





