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,实则最后返回的是一个拦截器链,希望本篇的细节剖析可以帮助大家建立更深的映像。

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

相关推荐
一只爱打拳的程序猿12 分钟前
【Spring】更加简单的将对象存入Spring中并使用
java·后端·spring
杨荧14 分钟前
【JAVA毕业设计】基于Vue和SpringBoot的服装商城系统学科竞赛管理系统
java·开发语言·vue.js·spring boot·spring cloud·java-ee·kafka
minDuck16 分钟前
ruoyi-vue集成tianai-captcha验证码
java·前端·vue.js
为将者,自当识天晓地。34 分钟前
c++多线程
java·开发语言
daqinzl42 分钟前
java获取机器ip、mac
java·mac·ip
激流丶1 小时前
【Kafka 实战】如何解决Kafka Topic数量过多带来的性能问题?
java·大数据·kafka·topic
Themberfue1 小时前
Java多线程详解⑤(全程干货!!!)线程安全问题 || 锁 || synchronized
java·开发语言·线程·多线程·synchronized·
让学习成为一种生活方式1 小时前
R包下载太慢安装中止的解决策略-R语言003
java·数据库·r语言
晨曦_子画1 小时前
编程语言之战:AI 之后的 Kotlin 与 Java
android·java·开发语言·人工智能·kotlin
假装我不帅2 小时前
asp.net framework从webform开始创建mvc项目
后端·asp.net·mvc