Spring MVC 源码分析之 DispatcherServlet#getHandler 方法

前言:

上篇我们分析了 Spring MVC 的工作流程源码,其核心是 DispatcherServlet#doDispatch 方法,这个方法中有获取映射器处理器操作,也就是调用 DispatcherServlet#getHandler 方法,本篇我们重点分析一下 DispatcherServlet#getHandler 的实现原理。

Spring MVC 知识传送门:

详解 Spring MVC(Spring MVC 简介)

Spring MVC 初始化源码分析

Spring MVC 工作流程源码分析

DispatcherServlet#getHandler 方法源码分析

DispatcherServlet#getHandler 方法就是从 HandlerMapping 中查询匹配当前 request 的 Handler,只要找到了就不在循环直接返回,我们我们重点关注 mapping.getHandler(request) 这行代码,这里实际调用的是接口的抽象类 AbstractHandlerMapping 中的 getHandler 方法,下面接着分析。

java 复制代码
//org.springframework.web.servlet.DispatcherServlet#getHandler
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	//this.handlerMappings 为空判断 DispatcherServlet 初始化时注册的 handlerMapping
	if (this.handlerMappings != null) {
		//不为空 迭代遍历
		Iterator var2 = this.handlerMappings.iterator();

		while(var2.hasNext()) {
			HandlerMapping mapping = (HandlerMapping)var2.next();
			//获取具体的 HandlerExecutionChain 重点关注
			HandlerExecutionChain handler = mapping.getHandler(request);
			if (handler != null) {
				//不为空 返回
				return handler;
			}
		}
	}

	return null;
}

AbstractHandlerMapping#getHandler 方法源码分析

AbstractHandlerMapping#getHandler 方法主要作用是获取当前请求的 HandlerExecutionChain,HandlerExecutionChain 包含了 HandlerMapping 和 拦截器,同时也对跨域请求做了一些处理,我们重点关注获取 Handler 和返回拦截器链的部分。

java 复制代码
//org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandler
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	//获取调用方法的 handler  重点关注
	Object handler = this.getHandlerInternal(request);
	//handler 为空判断
	if (handler == null) {
		//为空 获取默认的handler 
		handler = this.getDefaultHandler();
	}
	//再次为空判断
	if (handler == null) {
		//还为空 直接返回
		return null;
	} else {
		//handler 是否是 String 类型
		if (handler instanceof String) {
			//获取 hanlerName
			String handlerName = (String)handler;
			//从容器中获取具体的 handler
			handler = this.obtainApplicationContext().getBean(handlerName);
		}
		//获取当前请求的拦截器执行链 重点关注
		HandlerExecutionChain executionChain = this.getHandlerExecutionChain(handler, request);
		if (this.logger.isTraceEnabled()) {
			this.logger.trace("Mapped to " + handler);
		} else if (this.logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
			this.logger.debug("Mapped to " + executionChain.getHandler());
		}
		//跨域相关处理
		if (this.hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
			CorsConfiguration config = this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null;
			CorsConfiguration handlerConfig = this.getCorsConfiguration(handler, request);
			config = config != null ? config.combine(handlerConfig) : handlerConfig;
			executionChain = this.getCorsHandlerExecutionChain(request, executionChain, config);
		}
		//返回拦截器链
		return executionChain;
	}
}

AbstractHandlerMethodMapping#getHandlerInternal 方法源码分析

getHandlerInternal 方法是由 AbstractHandlerMapping 子类实现的,比如 AbstracUrlHandlerMapping、AbstractHandlerMethodMapping, 这里我们分析 AbstractHandlerMethodMapping#getHandlerInternal 方法,当前方法为了线程安全加了读锁,方法本身没有太多的逻辑,从 Request 中获取到 urlPaht 之后,就继续调用本类的 lookupHandlerMethod 方法,返回 HandlerMethod。

java 复制代码
//org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#getHandlerInternal
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
	//从request 中解析出 urlpath
	String lookupPath = this.initLookupPath(request);
	//加读锁
	this.mappingRegistry.acquireReadLock();

	HandlerMethod var4;
	try {
		//根据 urlpath 和 request 寻找具体的 HandlerMethod 重点关注
		HandlerMethod handlerMethod = this.lookupHandlerMethod(lookupPath, request);
		var4 = handlerMethod != null ? handlerMethod.createWithResolvedBean() : null;
	} finally {
		//释放读锁
		this.mappingRegistry.releaseReadLock();
	}

	return var4;
}

AbstractHandlerMethodMapping#lookupHandlerMethod 方法源码分析

AbstractHandlerMethodMapping#lookupHandlerMethod 方法就是根据 Request 的请求路径找到 HandlerMethaod,吐如果根据一一系列的匹配规则还是匹配不到,就给出匹配不到的提示,这里我们重点关注 this.handleMatch(bestMatch.mapping, lookupPath, request) 这行代码,这里面有对请求路径后拼接参数的处理。

java 复制代码
//org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#lookupHandlerMethod
//根据 urlpath 和 request 寻找具体的 HandlerMethod
@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
	List<AbstractHandlerMethodMapping<T>.Match> matches = new ArrayList();
	//根据 url 从 mappingRegistry 中获取 RequestMappingInfo
	List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
	if (directPathMatches != null) {
		//不为空  添加到 matches 中
		this.addMatchingMappings(directPathMatches, matches, request);
	}
	//matches 为空判断
	if (matches.isEmpty()) {
		//根据 urlpath 匹配到的结果为空 就将所有的映射关系加入 matches
		this.addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
	}
	//再次为空判断
	if (matches.isEmpty()) {
		//为空 处理
		return this.handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request);
	} else {
		//获取第一个
		AbstractHandlerMethodMapping<T>.Match bestMatch = (AbstractHandlerMethodMapping.Match)matches.get(0);
		if (matches.size() > 1) {
			//排序
			Comparator<AbstractHandlerMethodMapping<T>.Match> comparator = new AbstractHandlerMethodMapping.MatchComparator(this.getMappingComparator(request));
			matches.sort(comparator);
			//获取排序后的第一个
			bestMatch = (AbstractHandlerMethodMapping.Match)matches.get(0);
			if (this.logger.isTraceEnabled()) {
				this.logger.trace(matches.size() + " matching mappings: " + matches);
			}
			//是否是跨域请求
			if (CorsUtils.isPreFlightRequest(request)) {
				Iterator var7 = matches.iterator();

				while(var7.hasNext()) {
					AbstractHandlerMethodMapping<T>.Match match = (AbstractHandlerMethodMapping.Match)var7.next();
					//有跨域配置
					if (match.hasCorsConfig()) {
						//返回跨域配置的 handlermethod
						return PREFLIGHT_AMBIGUOUS_MATCH;
					}
				}
			} else {
				//普通请求 获取匹配到的第一个
				AbstractHandlerMethodMapping<T>.Match secondBestMatch = (AbstractHandlerMethodMapping.Match)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 + "}");
				}
			}
		}
		//将匹配到的HandlerMethod 设置到 request 中
		request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.getHandlerMethod());
		//获取到的请求 mapping 进行处理 主要是针对pattern类型进行 请求路径 url 和请求参数的解析 存放到request
		this.handleMatch(bestMatch.mapping, lookupPath, request);
		return bestMatch.getHandlerMethod();
	}
}

RequestMappingInfoHandlerMapping#handleMatch方法源码分析

RequestMappingInfoHandlerMapping#handleMatch 方法主要是对获取到的 RequestMappingInfo 对象中的方法的请求路径和参数进行处理,并设置到 Request 属性值中,这里主要区分了请求路径后面拼接参数和不拼接参数两种情况的处理,重点关注this.extractMatchDetails 方法。

java 复制代码
//org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping#handleMatch
protected void handleMatch(RequestMappingInfo info, String lookupPath, HttpServletRequest request) {
	//调用父类的方法存储 urlpath 
	super.handleMatch(info, lookupPath, request);
	//获取活跃的 Pattern 条件
	RequestCondition<?> condition = info.getActivePatternsCondition();
	//是否是 PathPatternsRequestCondition 类型
	if (condition instanceof PathPatternsRequestCondition) {
		//是  /order/queryorder
		this.extractMatchDetails((PathPatternsRequestCondition)condition, lookupPath, request);
	} else {
		//否 例如 /order/queryorder/{id}
		this.extractMatchDetails((PatternsRequestCondition)condition, lookupPath, request);
	}
	//方法的 @RequestMapping 修饰的方法是否包含 produces 属性 
	if (!info.getProducesCondition().getProducibleMediaTypes().isEmpty()) {
		Set<MediaType> mediaTypes = info.getProducesCondition().getProducibleMediaTypes();
		//context-type 中的 mediaTypes 存储到request
		request.setAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, mediaTypes);
	}

}

RequestMappingInfoHandlerMapping#extractMatchDetails 方法源码分析

RequestMappingInfoHandlerMapping#extractMatchDetails 方法主要是针对 url 上是否拼接变量值进行了处理,并把解析出来的属性设置给 Request。

java 复制代码
//org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping#extractMatchDetails
private void extractMatchDetails(PathPatternsRequestCondition condition, String lookupPath, HttpServletRequest request) {
	PathPattern bestPattern;
	Map uriVariables;
	//路径模式请求条件 是否为空
	if (condition.isEmptyPathMapping()) {
		//为空 获取第一个 pattern
		bestPattern = condition.getFirstPattern();
		//url 变量赋值为空
		uriVariables = Collections.emptyMap();
	} else {
		//获取解析后的 PathContainer
		PathContainer path = ServletRequestPathUtils.getParsedRequestPath(request).pathWithinApplication();
		//获取第一个
		bestPattern = condition.getFirstPattern();
		//根据 PathContainer 进行匹配
		PathMatchInfo result = bestPattern.matchAndExtract(path);
		Assert.notNull(result, () -> {
			return "Expected bestPattern: " + bestPattern + " to match lookupPath " + path;
		});
		//获取 url 上的变量
		uriVariables = result.getUriVariables();
		//作为属性设置给 Request
		request.setAttribute(MATRIX_VARIABLES_ATTRIBUTE, result.getMatrixVariables());
	}
	//设置到 request
	request.setAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE, bestPattern.getPatternString());
	request.setAttribute(URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriVariables);
}


//org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping#extractMatchDetails
private void extractMatchDetails(PatternsRequestCondition condition, String lookupPath, HttpServletRequest request) {
	String bestPattern;
	Map uriVariables;
	if (condition.isEmptyPathMapping()) {
		//为空直接赋值为 urlpath
		bestPattern = lookupPath;
		//变量赋值为空
		uriVariables = Collections.emptyMap();
	} else {
		//不为空
		bestPattern = (String)condition.getPatterns().iterator().next();
		//获取变量值 {id} 对应的值
		uriVariables = this.getPathMatcher().extractUriTemplateVariables(bestPattern, lookupPath);
		if (!this.getUrlPathHelper().shouldRemoveSemicolonContent()) {
			//作为属性设置给 Request
			request.setAttribute(MATRIX_VARIABLES_ATTRIBUTE, this.extractMatrixVariables(request, uriVariables));
		}
		//对路径进行编码
		uriVariables = this.getUrlPathHelper().decodePathVariables(request, uriVariables);
	}
	//设置到 request
	request.setAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE, bestPattern);
	request.setAttribute(URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriVariables);
}

AbstractHandlerMapping#getHandlerExecutionChain 方法源码分析

AbstractHandlerMapping#getHandlerExecutionChain 方法的主要作用就是构造一个 HandlerExecutionChain,会把传入的 handler 和所有拦截器通过责任链模式构造成一个 HandlerExecutionChain,当调用这个 handler 时就会通过这个责任链执行拦截器内的处理方法。

java 复制代码
//org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandlerExecutionChain
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
	//handler 是否是  HandlerExecutionChain 类型 是 强转 不是 就用 handler 创建一个 HandlerExecutionChain
	HandlerExecutionChain chain = handler instanceof HandlerExecutionChain ? (HandlerExecutionChain)handler : new HandlerExecutionChain(handler);
	//获取适配器拦截器 循环遍历
	Iterator var4 = this.adaptedInterceptors.iterator();

	while(var4.hasNext()) {
		HandlerInterceptor interceptor = (HandlerInterceptor)var4.next();
		//interceptor 是否是映射器拦截器 MappedInterceptor 
		if (interceptor instanceof MappedInterceptor) {
			//是 强转
			MappedInterceptor mappedInterceptor = (MappedInterceptor)interceptor;
			//是否能匹配到当前 Request
			if (mappedInterceptor.matches(request)) {
				//是 添加到 chain中
				chain.addInterceptor(mappedInterceptor.getInterceptor());
			}
		} else {
			//不是 可能是普通拦截器 加入到 chain 中
			chain.addInterceptor(interceptor);
		}
	}
	//返回 chain
	return chain;
}

本文简单分析了 Spring MVC 工作流程中获取 Handler 的实现,整个过程先通过 Request 请求的一些属性,从整个 HandlerMapping 中获取到具体的 Handler,然后和当前请求应该使用的拦截器一起,通过责任链模式构造出一个拦截器链,看似是从 HandlerMapping 中获取处理当前请求的 Handler,实则最后返回的是一个拦截器链,希望本篇的细节剖析可以帮助大家建立更深的映像。

欢迎提出建议及对错误的地方指出纠正。

相关推荐
xlsw_27 分钟前
java全栈day20--Web后端实战(Mybatis基础2)
java·开发语言·mybatis
神仙别闹1 小时前
基于java的改良版超级玛丽小游戏
java
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭2 小时前
SpringBoot如何实现缓存预热?
java·spring boot·spring·缓存·程序员
暮湫2 小时前
泛型(2)
java
超爱吃士力架2 小时前
邀请逻辑
java·linux·后端
南宫生2 小时前
力扣-图论-17【算法学习day.67】
java·学习·算法·leetcode·图论
转码的小石2 小时前
12/21java基础
java
李小白662 小时前
Spring MVC(上)
java·spring·mvc
GoodStudyAndDayDayUp3 小时前
IDEA能够从mapper跳转到xml的插件
xml·java·intellij-idea
装不满的克莱因瓶3 小时前
【Redis经典面试题六】Redis的持久化机制是怎样的?
java·数据库·redis·持久化·aof·rdb