前面跟小伙伴们分享了 SpringMVC 一个大致的初始化流程以及请求的大致处理流程,在请求处理过程中,涉及到九大组件,分别是:
这些组件相信小伙伴们在日常开发中多多少少都有涉及到,如果你对这些组件感到陌生,可以在公众号后台回复 ssm,免费获取松哥的入门视频教程。
那么接下来的几篇文章,松哥想和大家深入分析这九大组件,从用法到源码,挨个分析,今天我们就先来看看这九大组件中的第一个 HandlerMapping。
HandlerMapping 叫做处理器映射器,它的作用就是根据当前 request 找到对应的 Handler 和 Interceptor,然后封装成一个 HandlerExecutionChain 对象返回,我们来看下 HandlerMapping 接口:
public interface HandlerMapping {
String BEST_MATCHING_HANDLER_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingHandler";
@Deprecated
String LOOKUP_PATH = HandlerMapping.class.getName() + ".lookupPath";
String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping";
String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";
String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping";
String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";
String MATRIX_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".matrixVariables";
String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";
default boolean usesPathPatterns() {
return false;
}
@Nullable
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}
可以看到,除了一堆声明的常量外,其实就一个需要实现的方法 getHandler,该方法的返回值就是我们所了解到的 HandlerExecutionChain。
HandlerMapping 的继承关系如下:
这个继承关系虽然看着有点绕,其实仔细观察就两大类:
其他的都是一些辅助接口。
AbstractHandlerMethodMapping 体系下的都是根据方法名进行匹配的,而 AbstractUrlHandlerMapping 体系下的都是根据 URL 路径进行匹配的,这两者有一个共同的父类 AbstractHandlerMapping,接下来我们就对这三个关键类进行详细分析。
AbstractHandlerMapping 实现了 HandlerMapping 接口,无论是通过 URL 进行匹配还是通过方法名进行匹配,都是通过继承 AbstractHandlerMapping 来实现的,所以 AbstractHandlerMapping 所做的事情其实就是一些公共的事情,将以一些需要具体处理的事情则交给子类去处理,这其实就是典型的模版方法模式。
AbstractHandlerMapping 间接继承自 ApplicationObjectSupport,并重写了 initApplicationContext 方法(其实该方法也是一个模版方法),这也是 AbstractHandlerMapping 的初始化入口方法,我们一起来看下:
@Override
protected void initApplicationContext() throws BeansException {
extendInterceptors(this.interceptors);
detectMappedInterceptors(this.adaptedInterceptors);
initInterceptors();
}
三个方法都和拦截器有关。
extendInterceptors
protected void extendInterceptors(List<Object> interceptors) {
}
extendInterceptors 是一个模版方法,可以在子类中实现,子类实现了该方法之后,可以对拦截器进行添加、删除或者修改,不过在 SpringMVC 的具体实现中,其实这个方法并没有在子类中进行实现。
detectMappedInterceptors
protected void detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors) {
mappedInterceptors.addAll(BeanFactoryUtils.beansOfTypeIncludingAncestors(
obtainApplicationContext(), MappedInterceptor.class, true, false).values());
}
detectMappedInterceptors 方法会从 SpringMVC 容器以及 Spring 容器中查找所有 MappedInterceptor 类型的 Bean,查找到之后添加到 mappedInterceptors 属性中(其实就是全局的 adaptedInterceptors 属性)。一般来说,我们定义好一个拦截器之后,还要在 XML 文件中配置该拦截器,拦截器以及各种配置信息,最终就会被封装成一个 MappedInterceptor 对象。
initInterceptors
protected void initInterceptors() {
if (!this.interceptors.isEmpty()) {
for (int i = 0; i < this.interceptors.size(); i++) {
Object interceptor = this.interceptors.get(i);
if (interceptor == null) {
throw new IllegalArgumentException("Entry number " + i + " in interceptors array is null");
}
this.adaptedInterceptors.add(adaptInterceptor(interceptor));
}
}
}
initInterceptors 方法主要是进行拦截器的初始化操作,具体内容是将 interceptors 集合中的拦截器添加到 adaptedInterceptors 集合中。
至此,我们看到,所有拦截器最终都会被存入 adaptedInterceptors 变量中。
AbstractHandlerMapping 的初始化其实也就是拦截器的初始化过程。
❝为什么 AbstractHandlerMapping 中对拦截器如此重视呢?其实不是重视,大家想想,AbstractUrlHandlerMapping 和 AbstractHandlerMethodMapping 最大的区别在于查找处理器的区别,一旦处理器找到了,再去找拦截器,但是拦截器都是统一的,并没有什么明显区别,所以拦截器就统一在 AbstractHandlerMapping 中进行处理,而不会去 AbstractUrlHandlerMapping 或者 AbstractHandlerMethodMapping 中处理。
接下来我们再来看看 AbstractHandlerMapping#getHandler 方法,看看处理器是如何获取到的:
@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
// Ensure presence of cached lookupPath for interceptors and others
if (!ServletRequestPathUtils.hasCachedPath(request)) {
initLookupPath(request);
}
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
CorsConfiguration config = getCorsConfiguration(handler, request);
if (getCorsConfigurationSource() != null) {
CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request);
config = (globalConfig != null ? globalConfig.combine(config) : config);
}
if (config != null) {
config.validateAllowCredentials();
}
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
这个方法的执行流程是这样的:
接下来我们再来看看第五步的 getHandlerExecutionChain 方法的执行逻辑,正是在这个方法里边把 handler 变成了 HandlerExecutionChain:
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
if (mappedInterceptor.matches(request)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
else {
chain.addInterceptor(interceptor);
}
}
return chain;
}
这里直接根据已有的 handler 创建一个新的 HandlerExecutionChain 对象,然后遍历 adaptedInterceptors 集合,该集合里存放的都是拦截器,如果拦截器的类型是 MappedInterceptor,则调用 matches 方法去匹配一下,看一下是否是拦截当前请求的拦截器,如果是,则调用 chain.addInterceptor 方法加入到 HandlerExecutionChain 对象中;如果就是一个普通拦截器,则直接加入到 HandlerExecutionChain 对象中。
这就是 AbstractHandlerMapping#getHandler 方法的大致逻辑,可以看到,这里留了一个模版方法 getHandlerInternal 在子类中实现,接下来我们就来看看它的子类。
AbstractUrlHandlerMapping,看名字就知道,都是按照 URL 地址来进行匹配的,它的原理就是将 URL 地址与对应的 Handler 保存在同一个 Map 中,当调用 getHandlerInternal 方法时,就根据请求的 URL 去 Map 中找到对应的 Handler 返回就行了。
这里我们就先从他的 getHandlerInternal 方法开始看起:
@Override
@Nullable
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = initLookupPath(request);
Object handler;
if (usesPathPatterns()) {
RequestPath path = ServletRequestPathUtils.getParsedRequestPath(request);
handler = lookupHandler(path, lookupPath, request);
}
else {
handler = lookupHandler(lookupPath, request);
}
if (handler == null) {
// We need to care for the default handler directly, since we need to
// expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
Object rawHandler = null;
if (StringUtils.matchesCharacter(lookupPath, '/')) {
rawHandler = getRootHandler();
}
if (rawHandler == null) {
rawHandler = getDefaultHandler();
}
if (rawHandler != null) {
// Bean name or resolved handler?
if (rawHandler instanceof String) {
String handlerName = (String) rawHandler;
rawHandler = obtainApplicationContext().getBean(handlerName);
}
validateHandler(rawHandler, request);
handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
}
}
return handler;
}
这就是整个 getHandlerInternal 方法的逻辑,实际上并不难,里边主要涉及到 lookupHandler 和 buildPathExposingHandler 两个方法,需要和大家详细介绍下,我们分别来看。
lookupHandler
lookupHandler 有两个,我们分别来看。
@Nullable
protected Object lookupHandler(String lookupPath, HttpServletRequest request) throws Exception {
Object handler = getDirectMatch(lookupPath, request);
if (handler != null) {
return handler;
}
// Pattern match?
List<String> matchingPatterns = new ArrayList<>();
for (String registeredPattern : this.handlerMap.keySet()) {
if (getPathMatcher().match(registeredPattern, lookupPath)) {
matchingPatterns.add(registeredPattern);
}
else if (useTrailingSlashMatch()) {
if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", lookupPath)) {
matchingPatterns.add(registeredPattern + "/");
}
}
}
String bestMatch = null;
Comparator<String> patternComparator = getPathMatcher().getPatternComparator(lookupPath);
if (!matchingPatterns.isEmpty()) {
matchingPatterns.sort(patternComparator);
bestMatch = matchingPatterns.get(0);
}
if (bestMatch != null) {
handler = this.handlerMap.get(bestMatch);
if (handler == null) {
if (bestMatch.endsWith("/")) {
handler = this.handlerMap.get(bestMatch.substring(0, bestMatch.length() - 1));
}
if (handler == null) {
throw new IllegalStateException(
"Could not find handler for best pattern match [" + bestMatch + "]");
}
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
validateHandler(handler, request);
String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestMatch, lookupPath);
// There might be multiple 'best patterns', let's make sure we have the correct URI template variables
// for all of them
Map<String, String> uriTemplateVariables = new LinkedHashMap<>();
for (String matchingPattern : matchingPatterns) {
if (patternComparator.compare(bestMatch, matchingPattern) == 0) {
Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, lookupPath);
Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars);
uriTemplateVariables.putAll(decodedVars);
}
}
return buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables);
}
// No handler found...
return null;
}
@Nullable
private Object getDirectMatch(String urlPath, HttpServletRequest request) throws Exception {
Object handler = this.handlerMap.get(urlPath);
if (handler != null) {
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
validateHandler(handler, request);
return buildPathExposingHandler(handler, urlPath, urlPath, null);
}
return null;
}
/
的,举个简单例子,如果你定义的接口是 /user
,那么请求路径可以是 /user
也可以 /user/
,这两种默认都是支持的,所以这里的 useTrailingSlashMatch 分支主要是处理后面这种情况,处理方式很简单,就在 registeredPattern 后面加上 /
然后继续和请求路径进行匹配。路由配置 | 优先级 |
---|---|
不含任何特殊符号的路径,如:配置路由/a/b/c | 第一优先级 |
带有{}的路径,如:/a/{b}/c | 第二优先级 |
带有正则的路径,如:/a/{regex:\d{3}}/c | 第三优先级 |
带有*的路径,如:/a/b/* | 第四优先级 |
带有**的路径,如:/a/b/** | 第五优先级 |
最模糊的匹配:/** | 最低优先级 |
/
结尾,如果是以 /
结尾,则去掉结尾的 /
再去 handlerMap 中查找,如果还没找到,那就该抛异常出来了。如果找到的 handler 是 String 类型的,则再去 Spring 容器中查找对应的 Bean,接下来再调用 validateHandler 方法进行验证。myroot/*.html
,请求路径是 myroot/myfile.html
,那么最终获取到的就是 myfile.html
。lookupHandler 还有一个重载方法,不过只要大家把这个方法的执行流程搞清楚了,重载方法其实很好理解,这里松哥就不再赘述了,唯一要说的就是重载方法用了 PathPattern 去匹配 URL 路径,而这个方法用了 AntPathMatcher 去匹配 URL 路径。
buildPathExposingHandler
protected Object buildPathExposingHandler(Object rawHandler, String bestMatchingPattern,
String pathWithinMapping, @Nullable Map<String, String> uriTemplateVariables) {
HandlerExecutionChain chain = new HandlerExecutionChain(rawHandler);
chain.addInterceptor(new PathExposingHandlerInterceptor(bestMatchingPattern, pathWithinMapping));
if (!CollectionUtils.isEmpty(uriTemplateVariables)) {
chain.addInterceptor(new UriTemplateVariablesHandlerInterceptor(uriTemplateVariables));
}
return chain;
}
buildPathExposingHandler 方法向 HandlerExecutionChain 中添加了两个拦截器 PathExposingHandlerInterceptor 和 UriTemplateVariablesHandlerInterceptor,这两个拦截器在各自的 preHandle 中分别向 request 对象添加了一些属性,具体添加的属性小伙伴们可以自行查看,这个比较简单,我就不多说了。
在前面的方法中,涉及到一个重要的变量 handlerMap,我们定义的接口和处理器之间的关系都保存在这个变量中,那么这个变量是怎么初始化的呢?这就涉及到 AbstractUrlHandlerMapping 中的另一个方法 registerHandler:
protected void registerHandler(String[] urlPaths, String beanName) throws BeansException, IllegalStateException {
for (String urlPath : urlPaths) {
registerHandler(urlPath, beanName);
}
}
protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
Object resolvedHandler = handler;
if (!this.lazyInitHandlers && handler instanceof String) {
String handlerName = (String) handler;
ApplicationContext applicationContext = obtainApplicationContext();
if (applicationContext.isSingleton(handlerName)) {
resolvedHandler = applicationContext.getBean(handlerName);
}
}
Object mappedHandler = this.handlerMap.get(urlPath);
if (mappedHandler != null) {
if (mappedHandler != resolvedHandler) {
throw new IllegalStateException(
"Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
"]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
}
}
else {
if (urlPath.equals("/")) {
setRootHandler(resolvedHandler);
}
else if (urlPath.equals("/*")) {
setDefaultHandler(resolvedHandler);
}
else {
this.handlerMap.put(urlPath, resolvedHandler);
if (getPatternParser() != null) {
this.pathPatternHandlerMap.put(getPatternParser().parse(urlPath), resolvedHandler);
}
}
}
}
registerHandler(String[],String) 方法有两个参数,第一个就是定义的请求路径,第二个参数则是处理器 Bean 的名字,第一个参数是一个数组,那是因为同一个处理器可以对应多个不同的请求路径。
在重载方法 registerHandler(String,String) 里边,完成了 handlerMap 的初始化,具体流程如下:
这就是 AbstractUrlHandlerMapping 的主要工作,其中 registerHandler 将在它的子类中调用。
接下来我们来看 AbstractUrlHandlerMapping 的子类。
为了方便处理,SimpleUrlHandlerMapping 中自己定义了一个 urlMap 变量,这样可以在注册之前做一些预处理,例如确保所有的 URL 都是以 /
开始。SimpleUrlHandlerMapping 在定义时重写了父类的 initApplicationContext 方法,并在该方法中调用了 registerHandlers,在 registerHandlers 中又调用了父类的 registerHandler 方法完成了 handlerMap 的初始化操作:
@Override
public void initApplicationContext() throws BeansException {
super.initApplicationContext();
registerHandlers(this.urlMap);
}
protected void registerHandlers(Map<String, Object> urlMap) throws BeansException {
if (urlMap.isEmpty()) {
logger.trace("No patterns in " + formatMappingName());
}
else {
urlMap.forEach((url, handler) -> {
// Prepend with slash if not already present.
if (!url.startsWith("/")) {
url = "/" + url;
}
// Remove whitespace from handler bean name.
if (handler instanceof String) {
handler = ((String) handler).trim();
}
registerHandler(url, handler);
});
}
}
这块代码很简单,实在没啥好说的,如果 URL 不是以 /
开头,则手动给它加上 /
即可。有小伙伴们可能要问了,urlMap 的值从哪里来?当然是从我们的配置文件里边来呀,像下面这样:
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="urlMap">
<map>
<entry key="/aaa" value-ref="/hello"/>
</map>
</property>
</bean>
AbstractDetectingUrlHandlerMapping 也是 AbstractUrlHandlerMapping 的子类,但是它和 SimpleUrlHandlerMapping 有一些不一样的地方。
不一样的是哪里呢?
AbstractDetectingUrlHandlerMapping 会自动查找到 SpringMVC 容器以及 Spring 容器中的所有 beanName,然后根据 beanName 解析出对应的 URL 地址,再将解析出的 url 地址和对应的 beanName 注册到父类的 handlerMap 变量中。换句话说,如果你用了 AbstractDetectingUrlHandlerMapping,就不用像 SimpleUrlHandlerMapping 那样去挨个配置 URL 地址和处理器的映射关系了。我们来看下 AbstractDetectingUrlHandlerMapping#initApplicationContext 方法:
@Override
public void initApplicationContext() throws ApplicationContextException {
super.initApplicationContext();
detectHandlers();
}
protected void detectHandlers() throws BeansException {
ApplicationContext applicationContext = obtainApplicationContext();
String[] beanNames = (this.detectHandlersInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) :
applicationContext.getBeanNamesForType(Object.class));
for (String beanName : beanNames) {
String[] urls = determineUrlsForHandler(beanName);
if (!ObjectUtils.isEmpty(urls)) {
registerHandler(urls, beanName);
}
}
}
AbstractDetectingUrlHandlerMapping 重写了父类的 initApplicationContext 方法,并在该方法中调用了 detectHandlers 方法,在 detectHandlers 中,首先查找到所有的 beanName,然后调用 determineUrlsForHandler 方法分析出 beanName 对应的 URL,不过这里的 determineUrlsForHandler 方法是一个空方法,具体的实现在它的子类中,AbstractDetectingUrlHandlerMapping 只有一个子类 BeanNameUrlHandlerMapping,我们一起来看下:
public class BeanNameUrlHandlerMapping extends AbstractDetectingUrlHandlerMapping {
@Override
protected String[] determineUrlsForHandler(String beanName) {
List<String> urls = new ArrayList<>();
if (beanName.startsWith("/")) {
urls.add(beanName);
}
String[] aliases = obtainApplicationContext().getAliases(beanName);
for (String alias : aliases) {
if (alias.startsWith("/")) {
urls.add(alias);
}
}
return StringUtils.toStringArray(urls);
}
}
这个类很简单,里边就一个 determineUrlsForHandler 方法,这个方法的执行逻辑也很简单,就判断 beanName 是不是以 /
开始,如果是,则将之作为 URL。
如果我们想要在项目中使用 BeanNameUrlHandlerMapping,配置方式如下:
<bean class="org.javaboy.init.HelloController" name="/hello"/>
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" id="handlerMapping">
</bean>
注意,Controller 的 name 必须是以 /
开始,否则该 bean 不会被自动作为处理器。
至此,AbstractUrlHandlerMapping 体系下的东西就和大家分享完了。
AbstractHandlerMethodMapping 体系下只有三个类,分别是 AbstractHandlerMethodMapping、RequestMappingInfoHandlerMapping 以及 RequestMappingHandlerMapping,如下图:
在前面第三小节的 AbstractUrlHandlerMapping 体系下,一个 Handler 一般就是一个类,但是在 AbstractHandlerMethodMapping 体系下,一个 Handler 就是一个 Mehtod,这也是我们目前使用 SpringMVC 时最常见的用法,即直接用 @RequestMapping 去标记一个方法,该方法就是一个 Handler。
接下来我们就一起来看看 AbstractHandlerMethodMapping。
AbstractHandlerMethodMapping 类实现了 InitializingBean 接口,所以 Spring 容器会自动调用其 afterPropertiesSet 方法,在这里将完成初始化操作:
@Override
public void afterPropertiesSet() {
initHandlerMethods();
}
protected void initHandlerMethods() {
for (String beanName : getCandidateBeanNames()) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
processCandidateBean(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}
protected String[] getCandidateBeanNames() {
return (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :
obtainApplicationContext().getBeanNamesForType(Object.class));
}
protected void processCandidateBean(String beanName) {
Class<?> beanType = null;
try {
beanType = obtainApplicationContext().getType(beanName);
}
catch (Throwable ex) {
}
if (beanType != null && isHandler(beanType)) {
detectHandlerMethods(beanName);
}
}
可以看到,具体的初始化又是在 initHandlerMethods 方法中完成的,在该方法中,首先调用 getCandidateBeanNames 方法获取容器中所有的 beanName,然后调用 processCandidateBean 方法对这些候选的 beanName 进行处理,具体的处理思路就是根据 beanName 找到 beanType,然后调用 isHandler 方法判断该 beanType 是不是一个 Handler,isHandler 是一个空方法,在它的子类 RequestMappingHandlerMapping 中被实现了,该方法主要是检查该 beanType 上有没有 @Controller
或者 @RequestMapping
注解,如果有,说明这就是我们想要的 handler,接下来再调用 detectHandlerMethods 方法保存 URL 和 handler 的映射关系:
protected void detectHandlerMethods(Object handler) {
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass());
if (handlerType != null) {
Class<?> userType = ClassUtils.getUserClass(handlerType);
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> {
try {
return getMappingForMethod(method, userType);
}
catch (Throwable ex) {
throw new IllegalStateException("Invalid mapping on handler class [" +
userType.getName() + "]: " + method, ex);
}
});
methods.forEach((method, mapping) -> {
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}
上面这段代码里又涉及到两个方法:
我们分别来看:
getMappingForMethod
getMappingForMethod 是一个模版方法,具体的实现也是在子类 RequestMappingHandlerMapping 里边:
@Override
@Nullable
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);
}
String prefix = getPathPrefix(handlerType);
if (prefix != null) {
info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
}
}
return info;
}
首先根据 method 对象,调用 createRequestMappingInfo 方法获取一个 RequestMappingInfo,一个 RequestMappingInfo 包含了一个接口定义的详细信息,例如参数、header、produces、consumes、请求方法等等信息都在这里边。接下来再根据 handlerType 也获取一个 RequestMappingInfo,并调用 combine 方法将两个 RequestMappingInfo 进行合并。接下来调用 getPathPrefix 方法查看 handlerType 上有没有 URL 前缀,如果有,就添加到 info 里边去,最后将 info 返回。
这里要说一下 handlerType 里边的这个前缀是那里来的,我们可以在 Controller 上使用 @RequestMapping
注解,配置一个路径前缀,这样 Controller 中的所有方法都加上了该路径前缀,但是这种方式需要一个一个的配置,如果想一次性配置所有的 Controller 呢?我们可以使用 Spring5.1 中新引入的方法 addPathPrefix 来配置,如下:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setPatternParser(new PathPatternParser()).addPathPrefix("/itboyhub", HandlerTypePredicate.forAnnotation(RestController.class));
}
}
上面这个配置表示,所有的 @RestController
标记的类都自动加上 itboyhub
前缀。有了这个配置之后,上面的 getPathPrefix 方法获取到的就是 /itboyhub
了。
registerHandlerMethod
当找齐了 URL 和 handlerMethod 之后,接下来就是将这些信息保存下来,方式如下:
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
this.mappingRegistry.register(mapping, handler, method);
}
public void register(T mapping, Object handler, Method method) {
this.readWriteLock.writeLock().lock();
try {
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
validateMethodMapping(handlerMethod, mapping);
Set<String> directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping);
for (String path : directPaths) {
this.pathLookup.add(path, mapping);
}
String name = null;
if (getNamingStrategy() != null) {
name = getNamingStrategy().getName(handlerMethod, mapping);
addMappingName(name, handlerMethod);
}
CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
if (corsConfig != null) {
corsConfig.validateAllowCredentials();
this.corsLookup.put(handlerMethod, corsConfig);
}
this.registry.put(mapping,
new MappingRegistration<>(mapping, handlerMethod, directPaths, name, corsConfig != null));
}
finally {
this.readWriteLock.writeLock().unlock();
}
}
HC#hello
,HC 是 HelloController 中的大写字母。多说一句,第四步这个东西有啥用呢?这个其实是 Spring4 中开始增加的功能,算是一个小彩蛋吧,虽然日常开发很少用,但是我这里还是和大家说一下。
假如你有如下一个接口:
@RestController
@RequestMapping("/javaboy")
public class HelloController {
@GetMapping("/aaa")
public String hello99() {
return "aaa";
}
}
当你请求该接口的时候,不想通过路径,想直接通过方法名,行不行呢?当然可以!
在 jsp 文件中,添加如下超链接:
<%@ taglib prefix="s" uri="http://www.springframework.org/tags" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<a href="${s:mvcUrl('HC#hello99').build()}">Go!</a>
</body>
</html>
当这个 jsp 页面渲染完成后,href 属性就自动成了 hello99 方法的请求路径了。这个功能的实现,就依赖于前面第四步的内容。
至此,我们就把 AbstractHandlerMethodMapping 的初始化流程看完了。
接下来我们来看下当请求到来后,AbstractHandlerMethodMapping 会如何处理。
和前面第三小节一样,这里处理请求的入口方法也是 getHandlerInternal,如下:
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = initLookupPath(request);
this.mappingRegistry.acquireReadLock();
try {
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock();
}
}
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<>();
List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
}
if (!matches.isEmpty()) {
Match bestMatch = matches.get(0);
if (matches.size() > 1) {
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
matches.sort(comparator);
bestMatch = matches.get(0);
if (CorsUtils.isPreFlightRequest(request)) {
for (Match match : matches) {
if (match.hasCorsConfig()) {
return PREFLIGHT_AMBIGUOUS_MATCH;
}
}
}
else {
Match secondBestMatch = matches.get(1);
if (comparator.compare(bestMatch, secondBestMatch) == 0) {
Method m1 = bestMatch.getHandlerMethod().getMethod();
Method m2 = secondBestMatch.getHandlerMethod().getMethod();
String uri = request.getRequestURI();
throw new IllegalStateException(
"Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
}
}
}
request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.getHandlerMethod());
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.getHandlerMethod();
}
else {
return handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request);
}
}
这里就比较容易,通过 lookupHandlerMethod 找到对应的 HandlerMethod 返回即可,如果 lookupHandlerMethod 方法返回值不为 null,则通过 createWithResolvedBean 创建 HandlerMethod(主要是确认里边的 Bean 等),具体的创建过程松哥在后面的文章中会专门和大家分享。lookupHandlerMethod 方法也比较容易:
大致的流程就是这样,具体到请求并没有涉及到它的子类。
SpringMVC 九大组件,今天和小伙伴们把 HandlerMapping 过了一遍,其实只要认真看,这里并没有难点。