HandlerMapping的子类AbstractHandlerMapping
是一个抽象类基类,是SpringMVC中两大分支的父类。AbstractHandlerMapping
支持排序,默认handler,处理拦截器,包括带路径映射的拦截器。
下图为HandlerMapping整个链路的类图:
我们知道HandlerMapping
的唯一作用就是根据请求找到handler,所以这个接口只有一个方法,就是getHandler()
。
接下去看下真正的实现基类AbstractHandlerMapping
AbstractHandlerMapping
同样,我们要知道Spring很喜欢使用接口+抽象类的方式设计出整体流程,再通过模板方法由子类具体实现。HandlerMapping
找到的handler不关只是handler还有Interceptor。
init
AbstractHandlerMapping
继承了 WebApplicationObjectSupport
,那么你可能就想知道是什么时候实现了初始化,答案是我们上文提到在初始化DispatcherServlet的过程中被初始化的,即在FrameworkServlet#configureAndRefreshWebApplicationContext
的refresh
方法中被初始化,这其中又是层层调用,可以idea断点进去看看。
//FrameworkServlet#configureAndRefreshWebApplicationContext
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
....省略.....
wac.refresh();
}
回到AbstractHandlerMapping#initApplicationContext
@Override
protected void initApplicationContext() throws BeansException {
extendInterceptors(this.interceptors);
detectMappedInterceptors(this.adaptedInterceptors);
initInterceptors();
}
extendInterceptors
为模板方法,主要是用于给子类提供一个添加(修改)Interceptors的入口,目前SpringMVC没有使用。
detectMappedInterceptors
是一个模板方法,用于将 Spring MVC容器及父容器中的所有 Mappedlnterceptor
类型的 Bean 添加到 mappedlnterceptors
属性:
protected void detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors) {
mappedInterceptors.addAll(
BeanFactoryUtils.beansOfTypeIncludingAncestors(
obtainApplicationContext(), MappedInterceptor.class, true, false).values());
}
initlnterceptors
方法的作用是初始化 Interceptor, 具体内容其实是将 interceptors 属性根据对象类型添加到adaptedlnterceptors,这时候你又要有疑问了,this.interceptors
是什么时候被塞进去的哇!看下这个方法上面的注释,哦!原来这个类里有方法setInterceptors
,通过查看调用关系,大致可以知道是在初始化bean的时候被调用了!
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));
}
}
}
这interceptor只支持三种:HandlerInterceptor
, WebRequestInterceptor
和 MappedInterceptor
。
HandlerInterceptor
, WebRequestInterceptor
区别:
二者都可以用于拦截请求,但是你仔细观察这两个类:
//HandlerInterceptor
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception;
void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)throws Exception;
void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception;
//WebRequestInterceptor
void preHandle(WebRequest request) throws Exception;
void postHandle(WebRequest request, ModelMap model) throws Exception;
void afterCompletion(WebRequest request, Exception ex) throws Exception;
WebRequestInterceptor
是没有返回值的,不能用于请求阻断,通常用于上下文的准备,且WebRequestInterceptor
使用的入参是WebRequest
。WebRequestInterceptor
接口故意简化,以使通用请求拦截器的依赖关系尽可能地小。如果需要更改响应,则应使用HandlerIntercepter或Filters。
参见:WebRequestInterceptor与MappedInterceptor区别
MappedInterceptor的是在InterceptorRegistration#getInterceptor
方法里通过判断配置的拦截器是否带pattern:
//InterceptorRegistration#getInterceptor
protected Object getInterceptor() {
if (this.includePatterns.isEmpty() && this.excludePatterns.isEmpty()) {
return this.interceptor;
}
String[] include = StringUtils.toStringArray(this.includePatterns);
String[] exclude = StringUtils.toStringArray(this.excludePatterns);
MappedInterceptor mappedInterceptor = new MappedInterceptor(include, exclude, this.interceptor);
if (this.pathMatcher != null) {
mappedInterceptor.setPathMatcher(this.pathMatcher);
}
return mappedInterceptor;
}
在当前类中还定义了一个拦截器List:
List<HandlerInterceptor> adaptedInterceptors = new ArrayList<>();
顾名思义,适配拦截器,在赋值进去的过程中会将如WebRequestInterceptor
拦截器调整成适配类型塞进去便于将他们归集起来。
getHandler
这个方法的调用在前面分析DispatcherServlet
的时候提到过DispatcherServlet#doDispatch
里会被调用从而获取处理器Handler以及拦截器。
getHandler
方法的实现共分两部分, getHandlerExecutionChain
之前是找 Handler, getHandlerExecutionChain
方法用于添加拦截器。
Handler 的过程是这样的:
I) 通过 getHandlerlntemal (request)
方法获取, 这是个模板方法, 留给子类具体实现(这也是其子类主要做的事情)
2) 如果没有获取到则使用默认的 Handler, 默认的 Handler 保存存于AbstractHandlerMapping
的一个 Object 类型的属性 defaultHandler
中, 可以在配置 HandlerMapping
时进行配置, 也可以在子类中进行设置,当然默认是null。
3) 如果找到的 Handler 是 String 类型, 则以它为名到 Spring MVC 的容器里查找相应的
@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);
}
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
if (logger.isTraceEnabled()) {
logger.trace("Mapped to " + handler);
}
else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
logger.debug("Mapped to " + executionChain.getHandler());
}
//跨域相关可以先跳过
if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
config = (config != null ? config.combine(handlerConfig) : handlerConfig);
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
再看下getHandlerExecutionChain
,使用 handler 创建出 HandlerExecutionChain
类型的变量, 然后将adaptedInterceptors
和符合要求的 mappedlnterceptors
添加进去, 最后将其返回。
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
else {
chain.addInterceptor(interceptor);
}
}
return chain;
}
AbstractUrlHandlerMapping系列
显而易见,这个系列是通过url来进行匹配合适的Handler。此系列大致原理是将 url 与对应的 Handler 保存在一个 Map 中, 在 getHandlerlnternal
方法中使用 url 从 Map 中获取 Handler, AbstractUrlHandlerMapping
中实现了具体用 url 从 Map 中获取 Handler 的过程而 Map的初始化则交给了具体的子孙类去完成。此处的Map即AbstractUrlHandlerMapping
中定义的handlerMap。
private final Map<String, Object> handlerMap = new LinkedHashMap<>();
那先来看下是怎么初始化这个Map的。
在AbstractUrlHandlerMapping
中是通过registerHandler这个方法来构建handlerMap的。AbstractUrlHandlerMapping
提供了两个registerHandler方法
第一个registerHandler是将多个url注册到一个处理器。处理器用的是String类型的beanName,可以通过beanName到容器中去找到真正的处理器Bean。具体处理就是通过遍历所有的url,然后再通过调用第二个registerHandler
将handler注册到handlerMap中。
protected void registerHandler(String[] urlPaths, String beanName) throws BeansException, IllegalStateException {
Assert.notNull(urlPaths, "URL path array must not be null");
for (String urlPath : urlPaths) {
registerHandler(urlPath, beanName);
}
}
第二个 registerHandler
方法:
1) 首先看 Map 里原来有没有传入的 url, 如果 没有就 put 进去, 如果有就看一下原来保存的和现在要注册的 Handler 是不是同一个, 如果不是同一个就有问题了, 总不能相同的 url 有两个不同的 Handler 吧(这个系列只根据 url 查找 Handler),这时就得抛异常;
2) 往 Map 里放的时候还需要看一下 url 是不是处理"/"或者"/*"' 如果是就不往 Map 里放了, 而是分别设置到 rootHandler
和 defaultHandler
。
protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
Object resolvedHandler = handler;
//如果的handler是string类型,并且没有设置lazyInitHandlers
//则从SpringMVC容器获取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("/")) {
if (logger.isTraceEnabled()) {
logger.trace("Root mapping to " + getHandlerDescription(handler));
}
setRootHandler(resolvedHandler);
}
else if (urlPath.equals("/*")) {
if (logger.isTraceEnabled()) {
logger.trace("Default mapping to " + getHandlerDescription(handler));
}
setDefaultHandler(resolvedHandler);
}
else {
this.handlerMap.put(urlPath, resolvedHandler);
if (logger.isTraceEnabled()) {
logger.trace("Mapped [" + urlPath + "] onto " + getHandlerDescription(handler));
}
}
}
}
了解完Map的初始化流程之后我们回到getHandlerInternal
承接上文我们知道这个方法在父类是模板方法,子类进行实现,现在来看看里面做了什么
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
request.setAttribute(LOOKUP_PATH, lookupPath);
Object handler = lookupHandler(lookupPath, request);
if (handler == null) {
// 保存找到的原始handler
Object rawHandler = null;
if ("/".equals(lookupPath)) {
rawHandler = getRootHandler();
}
if (rawHandler == null) {
rawHandler = getDefaultHandler();
}
if (rawHandler != null) {
// String类型的直接在容器里查找bean
if (rawHandler instanceof String) {
String handlerName = (String) rawHandler;
rawHandler = obtainApplicationContext().getBean(handlerName);
}
//校验方法,模板方法,当前MVC没有使用
validateHandler(rawHandler, request);
handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
}
}
return handler;
}
整个流程通俗易懂,我们主要看lookupHandler
以及buildPathExposingHandler
。
lookupHandler
方法用于使用 lookupPath 从 Map 中查找 Handler, 不过很多时候并不能直接从 Map 中 get 到 因为很多 Handler 都是用了 Pattern 的匹配模式, 如 "/show/article/*",这里的星号*可以代表任意内容而不是真正匹配 url 中的星号, 如果Pattern 中包含 PathVariable也不能直接从 Map 中获取到。 另外一个 url 还可能跟多个 Pattern 相匹配, 这时还需要选择其中最优的
@Nullable
protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
// 直接匹配
Object handler = this.handlerMap.get(urlPath);
if (handler != null) {
// String类型从容器获取
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
validateHandler(handler, request);
return buildPathExposingHandler(handler, urlPath, urlPath, null);
}
// pattern匹配,比如使用带*的模式与url匹配
List<String> matchingPatterns = new ArrayList<>();
for (String registeredPattern : this.handlerMap.keySet()) {
if (getPathMatcher().match(registeredPattern, urlPath)) {
matchingPatterns.add(registeredPattern);
}
else if (useTrailingSlashMatch()) {
if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", urlPath)) {
matchingPatterns.add(registeredPattern + "/");
}
}
}
//找寻最优匹配
String bestMatch = null;
Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);
if (!matchingPatterns.isEmpty()) {
matchingPatterns.sort(patternComparator);
if (logger.isTraceEnabled() && matchingPatterns.size() > 1) {
logger.trace("Matching patterns " + matchingPatterns);
}
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, urlPath);
// 之前是通过sort方法进行排序, 然后拿第一个作为bestPatternMatch的不过有可能有多个Pattern的顺序相同
// 也就是sort方法返回0, 这里就是处理这种情况
Map<String, String> uriTemplateVariables = new LinkedHashMap<>();
for (String matchingPattern : matchingPatterns) {
if (patternComparator.compare(bestMatch, matchingPattern) == 0) {
Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath);
Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars);
uriTemplateVariables.putAll(decodedVars);
}
}
if (logger.isTraceEnabled() && uriTemplateVariables.size() > 0) {
logger.trace("URI variables " + uriTemplateVariables);
}
return buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables);
}
// No handler found...
return null;
}
buildPathExposingHandler
方法用于给查找到的 Handler 注册两个拦截器 PathExposingHandlerInterceptor
和 UriTemplateVariablesHandlerInterceptor
, 这是两个内部拦截器, 主要作用是将与当前 url 实际匹配的 Pattern 、匹配条件(后面介绍)和 url 模板参数等设置到 request 的属性里 而不需要再重新查找一遍。
在 buildPathExposingHandler
方法中给 Handler 注册两个内部拦截器 PathExposingHandlerInterceptor
和 UriTemplateVariablesHandlerlnterceptor
, 这两个拦截器分别在 preHandle
中调用(exposePathWithinMapping
和 exposeUriTemplateVariables
方法将相应内容设置到 request 的属性。
SimpleUrlHandlerMapping
SimpleUrlHandlerMapping
定义了一个Map变量(自己定义一个Map主要有两个作用,第 一是方便配置, 第二是可以在注册前做一些预处理, 如确保所有url都以 "/" 开头),将所有的url和Handler的对应关系放在里面, 最后注册到父类的Map中;而AbstractDetectingUrlHandlerMapping
则是将容器中的所有bean都拿出来, 按一定规则注册到父类的Map中。 下面分别来看一下。
SimpleUrlHandlerMapping
在创建时通过重写父类的initApplicationContext
方法调用了registerHandlers
方法完成Handler的注册,registerHandlers
内部又调用了AbstractUrlHandlerMapping
的registerHandler
方法将我们配置的urlMap 注册到AbstractUrlHandlerMapping
的Map中(如果要使用SimpleUrlHandlerMapping
就需要在注册时给它配置urlMap):
@Override
public void initApplicationContext() throws BeansException {
super.initApplicationContext();
registerHandlers(this.urlMap);
}
/**
* Register all handlers specified in the URL map for the corresponding paths.
* @param urlMap a Map with URL paths as keys and handler beans or bean names as values
* @throws BeansException if a handler couldn't be registered
* @throws IllegalStateException if there is a conflicting handler registered
*/
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);
});
if (logger.isDebugEnabled()) {
List<String> patterns = new ArrayList<>();
if (getRootHandler() != null) {
patterns.add("/");
}
if (getDefaultHandler() != null) {
patterns.add("/**");
}
patterns.addAll(getHandlerMap().keySet());
logger.debug("Patterns " + patterns + " in " + formatMappingName());
}
}
}
AbstractDetectingUrlHandlerMapping
bstractDetectingUrlHandlerMapping
也是通过重写initApplicationContext
来注册Handler的,里面调用了detectHandlers
方法, 在detectHandlers
中根据配置的detectHandlersInAncestorContexts
参数决定是从Spring MVC容器还是Spring MVC及其父容器中找到所有bean的 beanName, 然后用determineUrlsForHandler
方法对每个beanName解析出对应的urls, 如果解析结果不为空则将解析出的urls和beanName (作为Handler)注册到父类的Map, 注册方法依然是调用AbstractUrlHandlerMapping
的registerHandler
方法。 使用beanName解析urls的determineUrlsForHandler
方法是模板方法, 交给具体子类实现。
protected void detectHandlers() throws BeansException {
ApplicationContext applicationContext = obtainApplicationContext();
String[] beanNames = (this.detectHandlersInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) :
applicationContext.getBeanNamesForType(Object.class));
// Take any bean name that we can determine URLs for.
for (String beanName : beanNames) {
String[] urls = determineUrlsForHandler(beanName);
if (!ObjectUtils.isEmpty(urls)) {
// URL paths found: Let's consider it a handler.
registerHandler(urls, beanName);
}
}
if ((logger.isDebugEnabled() && !getHandlerMap().isEmpty()) || logger.isTraceEnabled()) {
logger.debug("Detected " + getHandlerMap().size() + " mappings in " + formatMappingName());
}
}
BeanNameUrlHandlerMapping
BeanNameUrlHandlerMapping
继承自AbstractDetectingUrlHandlerMapping
,实现了模板方法determineUrlsForHandler
检查 beanName 和 alias 是不是以 "/" 开头, 如果是则将其作为 url
@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);
}
关于BeanNameUrlHandlerMapping/SimpleUrlHandlerMapping的使用做了个小demo进行示例:demo
AbstractHandlerMethodMapping
AbstractHandlerMapping
的另一个重要子类AbstractHandlerMethodMapping
。这条链路下的子类RequestMappingHandlerMapping
,RequestMappingInfoHandlerMapping
应该是我们最熟悉的handlerMapping
了!我们使用@RequestMapping
注解的方法即是使用这种方式,他又有专门的HandlerMethod
,即是Method类型Handler。
首先我们很清楚地知道,HandlerMapping
的唯一目的就是找到handler,所以这个类必不可少的功能就是找到handler,找到之前就需要知道有哪些handler可以用来匹配,所以这个类就整个了MappingRegistry
内部类将全部的Controller里面的是处理器的方法收集到几个Map中,到时候再去匹配合适的处理器。
初始化
先看下这个MappingRegistry:
private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();
private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();
private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();
先看这几个关键性的Map,我们发现这里用了泛型,官方对这个泛型的解释:
<T> – the mapping for a
HandlerMethod
containing the conditions needed to match the handler method to an incoming request.
也就是用来代表匹配Handler的条件专门使用的一种类,这里的条件就不只是url了, 还可以有很多其他条件, 如request的类型 (Get、Post等)、请求的参数、Header等都可以作为匹配HandlerMetbod的条件。 默认使用的是RequestMappingInfo
,可以在RequestMappingInfoHandlerMapping
类的定义看到:
public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMethodMapping<RequestMappingInfo>
RequestMappinglnfo
实现了RequestCondition
接口, 此接口专门用于保存从request提取出的用于匹配Handler的条件,抽象实现AbstractRequestCondition
中重写了equals、hashCode和toString三个方法,它有8个子类,除了CompositeRequestCondition
外每一个子类表示一种匹配条件。
RequestCondition的另一个实现就是这里要用的RequestMappinglnfo, 它里面其实是用七个变量保存了七个RequestCondition, 在匹配时使用那七个变量进行匹配,这也就是可以在@RequestMapping中给处理器指定多种匹配方式的原因。
mappingLookup:保存着匹配条件(也就是RequestCondition)和HandlerMethod的对应关系。
urlLookup:保存着url与匹配条件(也就是RequestCondition)的对应关系, 当然这里的url是Pattern式的,可以使用通配符。 另外,这里使用的Map并不是普通的Map, 而是 MultiValueMap, 这是一种 一个key对应多个值的Map, 其实它的value是一个List类型的值 看MultiValueMap的定义就能明白,MultiValueMap 定义如下:
MultiValueMap<K, V> extends Map<K, List<V>>
由于RequestCondition
可以同时使用多种不同的匹配方式而不只是url 一种,所以反过来说同一个url就可能有多个RequestCondtion
与之对应 。这里的RequestCondition
其实就是在@RequestMapping
中注释的内容。
nameLookup:保存着name与HandlerMethod的对应关系,它使用的也是MultiValueMap
类型的Map, 也就是说一个name可以有多个HandlerMethod
。 这里的name是使用HandlerMethodMappingNamingStrategy
策略的实现类从HandlerMethod
中解析出来的,默认使用RequestMappinginfoHandlerMethodMappingNamingStrategy
实现类,解析规则是:类名里的大写字母组合+ "#" +方法名。 这个Map 在正常的匹配过程并不需要使用,它主要用在 MvcUriComponentsBuilder
里面,可以用来根据name获取相应的url。
AbstractHandlerMethodMapping
实现了InitializingBean
接口, 所以spring容器会自动调用其afterPropertiesSet
方法,afterProperties
又调用initHandlerMethods
方法。
@Override
public void afterPropertiesSet() {
initHandlerMethods();
}
/**
* Scan beans in the ApplicationContext, detect and register handler methods.
* @see #getCandidateBeanNames()
* @see #processCandidateBean
* @see #handlerMethodsInitialized
*/
protected void initHandlerMethods() {
//从容器中获取所有的候选bean
for (String beanName : getCandidateBeanNames()) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
processCandidateBean(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}
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);
}
}
//判断是不是一个handler,模板方法,子类实现
if (beanType != null && isHandler(beanType)) {
detectHandlerMethods(beanName);
}
}
isHandler
检查类前是否有@Controller
或者@RequestMapping
注解修饰:
//RequestMappingHandlerMapping
@Override
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
protected void detectHandlerMethods(Object handler) {
//这步其实没啥必要,目前来看就一个地方调用,且类型一定是String
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass());
if (handlerType != null) {
//如果是cglib代理的子对象类型, 则返回父类型, 否则直接返回传入的类型
Class<?> userType = ClassUtils.getUserClass(handlerType);
//获取当前bean里所有符合Handler要求的Method
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);
}
});
if (logger.isTraceEnabled()) {
logger.trace(formatMappings(userType, methods));
}
methods.forEach((method, mapping) -> {
// 判断Method是否可调用(不是private方法),这里返回的是目标类型可调用的方法
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
//注册符合要求的Method, 也就是保存到三个Map中
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}
这段代码有点不太好理解,但是我们只要知道目的是为了捞到所有method以及RequestMappinglnfo(从子类角度来看)
public static <T> Map<Method, T> selectMethods(Class<?> targetType, final MetadataLookup<T> metadataLookup) {
final Map<Method, T> methodMap = new LinkedHashMap<>();
Set<Class<?>> handlerTypes = new LinkedHashSet<>();
Class<?> specificHandlerType = null;
if (!Proxy.isProxyClass(targetType)) {
specificHandlerType = ClassUtils.getUserClass(targetType);
handlerTypes.add(specificHandlerType);
}
handlerTypes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetType));
for (Class<?> currentHandlerType : handlerTypes) {
final Class<?> targetClass = (specificHandlerType != null ? specificHandlerType : currentHandlerType);
//循环currentHandlerType类的所有方法
//每获取一个方法的时候,就会回调函数式方法
ReflectionUtils.doWithMethods(currentHandlerType, method -> {
Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
//判断方法上面是否有@RequestMapping注解,如果有封装对象返回
//会调用函数式方法 getMappingForMethod
T result = metadataLookup.inspect(specificMethod);
if (result != null) {
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
if (bridgedMethod == specificMethod || metadataLookup.inspect(bridgedMethod) == null) {
methodMap.put(specificMethod, result);
}
}
}, ReflectionUtils.USER_DECLARED_METHODS);
}
return methodMap;
}
getMappingForMethod
方法里首先拿到方法的RequestMappingInfo
,但是方法级别的比如url显然可能不是完整的,需要再与类上的url进行拼接,所以就有了第二步操作,如下:
//RequestMappingHandlerMapping
@Override
@Nullable
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
//1.根据方法上的@RequestMapping创建RequestMappingInfo
//寻找有@RequestMapping注解的方法,然后注解里面的内容封装成对象
RequestMappingInfo info = createRequestMappingInfo(method);
if (info != null) {
//2.查找类上是否有@RequestMapping,如果有则和方法上的组合
//类上面的@RequestMapping注解也封装成对象
// 这个类型信息,是解析类上的RequestMapping注解信息,然后跟方法的进行拼接组合
RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
if (typeInfo != null) {
//把方法上面的注解属性结合到类上面的RequestMappingInfo对象中
info = typeInfo.combine(info);
}
String prefix = getPathPrefix(handlerType);
if (prefix != null) {
info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
}
}
return info;
}
@Nullable
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
//将查询出的多个annotationType类型注解属性合并到查询的第一个注解中,因为存在@GetMapping
RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
RequestCondition<?> condition = (element instanceof Class ?
getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
//根据RequestMapping注解的属性来创建RequestMappingInfo
return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}
接下去我们看一下是是如何将找到的HandlerMethod
注册,通过定义的内部类MappingRegistry
将handler注册进三个Map之中。
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
this.mappingRegistry.register(mapping, handler, method);
}
public void register(T mapping, Object handler, Method method) {
// Assert that the handler method is not a suspending one.
if (KotlinDetector.isKotlinType(method.getDeclaringClass()) && KotlinDelegate.isSuspend(method)) {
throw new IllegalStateException("Unsupported suspending handler method detected: " + method);
}
this.readWriteLock.writeLock().lock();
try {
// 这里会创建一个HandlerMethod实例
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
// 校验mappingLookup已经存在mapping信息这里会抛出异常
validateMethodMapping(handlerMethod, mapping);
//保存url对象和handlerMethod的映射关系
// Key: RequestMappingInfo和Value: HandlerMethod
this.mappingLookup.put(mapping, handlerMethod);
// 一个mapping是允许存在多条路径的
List<String> directUrls = getDirectUrls(mapping);
for (String url : directUrls) {
//建立url和RequestMappingInfo映射关系
//urlLookup是个LinkedMultiValueMap,也就是说一个路径可以有多个mapping
// 在进行匹配的时候,就是先根据url找到合适的mapping,然后根据找到的mapping再去找到HandlerMethod
this.urlLookup.add(url, mapping);
}
String name = null;
if (getNamingStrategy() != null) {
// 大写类名#方法名
name = getNamingStrategy().getName(handlerMethod, mapping);
addMappingName(name, handlerMethod);
}
//跨域访问相关
CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
if (corsConfig != null) {
this.corsLookup.put(handlerMethod, corsConfig);
}
this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
}
finally {
this.readWriteLock.writeLock().unlock();
}
}
到这里我们才刚刚讲完初始化流程接下去分析getHandlerlntenal
方法获取处理器的过程。
getHandlerInternal
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
request.setAttribute(LOOKUP_PATH, lookupPath);
this.mappingRegistry.acquireReadLock();
try {
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock();
}
}
1)根据request获取 lookupPath
, 可以简单地理解为请求的url;
2)使用lookupHandlerMethod
方法通过lookupPath
和request
找handlerMethod
;
3)如果可以找到 handlerMethod
则调用它的createWithResolvedBean
方法创建新的HandlerMethod
并返回。
createWitbResolvedBean
的作用是判断handlerMethod
里的handler
是不是String类型, 如果是则改为将其作为beanName 从容器中所取到的bean, 不过HandlerMetbod里的属性都是final类型的, 不可以修改, 所以在createWithResolvedBean
方法中又用原来的属性和修改后的handler新建了一个HandlerMetbod
。
直接看下关键的lookupHandlerMethod
方法
@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
//Match是内部类 保存匹配条件和Handler
List<Match> matches = new ArrayList<>();
//首先根据lookupPath获取到匹配条件,以子类视角即RequestMappingInfo
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
if (directPathMatches != null) {
// 将找到的匹配条件添加到matches
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
// 如果不能直接使用lookupPath得到匹配条件,则将所有匹配条件加入matches
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}
//将包含匹配条件和Handler的matches排序,并取第一个作为bestMatch, 如果前面两个排序相同则抛出异常
if (!matches.isEmpty()) {
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
matches.sort(comparator);
Match bestMatch = matches.get(0);
if (matches.size() > 1) {
if (logger.isTraceEnabled()) {
logger.trace(matches.size() + " matching mappings: " + matches);
}
if (CorsUtils.isPreFlightRequest(request)) {
return PREFLIGHT_AMBIGUOUS_MATCH;
}
Match secondBestMatch = matches.get(1);
//如果两个RequestMappinginfo什么都相同,报错
if (comparator.compare(bestMatch, secondBestMatch) == 0) {
Method m1 = bestMatch.handlerMethod.getMethod();
Method m2 = secondBestMatch.handlerMethod.getMethod();
String uri = request.getRequestURI();
throw new IllegalStateException(
"Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
}
}
request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.handlerMethod;
}
else {
return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
}
}
小结
分析了 Spring MVC 中的 HandlerMapping
的各种具体实现方式。 HandlerMapping
的整体结构在 AbstractHandlerMapping
中设计, 简单来说其功能就是根据 request 找到 Handler 和 Interceptors, 组合成 HandlerExecutionChain
类型并返回。 找 Handler 的过程通过模板方法 getHandlerInternal
留给子类实现。查找 Interceptors 则是 AbstractHandlerMapping
自已完成的,查找的具体方法是通过几个与Interceptor
相关的Map完成的 在初始化过程中对那些Map进行了初始化。
AbstractHandlerMapping
的子类分了两个系列: AbstractUrlHandlerMapping
系列和AbstractHandlerMethodMapping
系列, 前者是通过url匹配的, 后者匹配的内容比较多, 而且是直接匹配到Method, 这也是现在使用最多的一种方式。
参考
《看透 springMvc 源代码分析与实践》