@RequestMapping的注册及查找过程

在这里插入图片描述

介绍

一个@RequestMapping请求的流程如下

  1. 通过RequestMappingHandlerMapping返回HandlerMethod这种类型的handler
  2. 找到能处理HandlerMethod的RequestMappingHandlerAdapter
  3. RequestMappingHandlerAdapter执行业务逻辑返回ModelAndView

Spring容器启动的时候,会将@RequestMapping中的信息和要执行的方法存放在AbstractHandlerMethodMapping的内部类MappingRegistry中的5个成员变量中,其中的T为RequestMappingInfo,是对@RequestMapping注解信息的封装

class MappingRegistry {

	// MappingRegistration包含
	// 1. 映射信息
	// 2. 映射信息中的直接路径(非模式化路径)
	// 3. 映射名
	// 4. 对应的处理器方法
	
	// 分别为下面4个map的key
	// 这个只是对4个key的封装,只在unregister方法中会使用
	private final Map<T, MappingRegistration<T>> registry = new HashMap<>();

	// RequestMappingInfo -> HandlerMethod
	private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();

	// 不包含通配符的url -> List<RequestMappingInfo>
	// 这里为什么是一个List呢?因为一个url有可能对应多个方法,即这些方法的@RequestMapping注解path属性一样,但是其他属性不一样
	private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();

	// 映射名 -> HandlerMethod
	private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();

	// HandlerMethod -> 跨域配置
	private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();
}

其中最主要的是如下2个map,其余的本节关联不大,不再分析

urlLookup:保存了如下映射关系
key=不包含通配符的url
value=List<RequestMappingInfo>

mappingLookup:
key=RequestMappingInfo
value=HandlerMethod

为什么需要这2个map呢?先留着这个问题,我们继续往后看

RequestMappingHandlerMapping的查找过程

RequestMappingHandlerMapping的继承关系如下
在这里插入图片描述
HandlerMapping接口一个getHandler方法,用来根据请求的url找到对应的HandlerExecutionChain对象。

public interface HandlerMapping {

	@Nullable
	HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;

}

而HandlerExecutionChain对象就是简单的将url对应的Handler和拦截器封装了一下

public class HandlerExecutionChain {

	private final Object handler;

	@Nullable
	private List<HandlerInterceptor> interceptorList;
}

通过debug发现,整个查找过程只是依次调用了如下3个方法

  1. AbstractHandlerMapping#getHandler
  2. AbstractHandlerMethodMapping#getHandlerInternal
  3. AbstractHandlerMethodMapping#lookupHandlerMethod

AbstractHandlerMapping#getHandler

@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	// getHandlerInternal 是模板方法,留给子类去实现
	Object handler = getHandlerInternal(request);
	if (handler == null) {
		// 没有获取到使用默认的handler
		handler = getDefaultHandler();
	}
	if (handler == null) {
		return null;
	}
	// Bean name or resolved handler?
	// 如果handler是string类型,则以它为名从容器中查找相应的bean
	if (handler instanceof String) {
		String handlerName = (String) handler;
		handler = obtainApplicationContext().getBean(handlerName);
	}

	// 拿到handler,和我们配置的拦截器,封装成HandlerExecutionChain对象返回
	HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
	// 跨域配置
	if (CorsUtils.isCorsRequest(request)) {
		CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
		CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
		CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
		executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
	}
	return executionChain;
}

可以看到找handler的过程是交由子类去实现的。并将对这个请求适用的拦截器,跨域配置,handler封装成HandlerExecutionChain返回给DispatcherServlet

AbstractHandlerMethodMapping#getHandlerInternal

@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
	// 获取请求路径,作为查找路径
	String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
	if (logger.isDebugEnabled()) {
		logger.debug("Looking up handler method for path " + lookupPath);
	}
	// 获取读锁
	this.mappingRegistry.acquireReadLock();
	try {
		HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
		if (logger.isDebugEnabled()) {
			if (handlerMethod != null) {
				logger.debug("Returning handler method [" + handlerMethod + "]");
			}
			else {
				logger.debug("Did not find handler method for [" + lookupPath + "]");
			}
		}
		// handlerMethod就是要执行的controller方法的封装
		return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
	}
	finally {
		this.mappingRegistry.releaseReadLock();
	}
}

这一步做的事情也不是很多,就是根据url找到对应的HandlerMethod

AbstractHandlerMethodMapping#lookupHandlerMethod

兜兜转转终于来到根据请求获取相应的HandlerMethod对象了

@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
	// 匹配结果列表
	List<Match> matches = new ArrayList<>();
	// 直接匹配
	List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
	// 如果有匹配的,就添加进匹配列表中
	if (directPathMatches != null) {
		addMatchingMappings(directPathMatches, matches, request);
	}
	// 还没有匹配,就遍历所有的处理方法去找
	if (matches.isEmpty()) {
		// No choice but to go through all mappings...
		addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
	}

	// 匹配结果不为空
	if (!matches.isEmpty()) {
		// 匹配结果比较器,直接使用匹配信息进行比较
		Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
		// 对多个匹配结果进行排序
		matches.sort(comparator);
		if (logger.isTraceEnabled()) {
			logger.trace("Found " + matches.size() + " matching mapping(s) for [" + lookupPath + "] : " + matches);
		}
		// 最佳匹配
		Match bestMatch = matches.get(0);
		if (matches.size() > 1) {
			if (CorsUtils.isPreFlightRequest(request)) {
				return PREFLIGHT_AMBIGUOUS_MATCH;
			}
			Match secondBestMatch = matches.get(1);
			// 第一个元素和第二个元素进行比较
			if (comparator.compare(bestMatch, secondBestMatch) == 0) {
				// 结果为0,至少有2个最佳匹配
				Method m1 = bestMatch.handlerMethod.getMethod();
				Method m2 = secondBestMatch.handlerMethod.getMethod();
				// 多个最佳匹配,抛出异常
				throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" +
						request.getRequestURL() + "': {" + 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);
	}
}

这个方法就涉及到我们最开始提到的那2个map。

urlLookup
key=不包含通配符的url
value=List<RequestMappingInfo>

mappingLookup
key=RequestMappingInfo
value=HandlerMethod

  1. 先根据url从urlLookup中查找对应的RequestMappingInfo,如果找到的List<RequestMappingInfo>不为空,则判断其他匹配条件是否符合
  2. 如果其他条件也有符合的,则不再遍历所有的RequestMappingInfo,否则遍历所有的RequestMappingInfo,因为考虑到有通配符形式的url必须遍历所有的RequestMappingInfo才能找出来符合条件的
  3. 如果最终找到的RequestMappingInfo有多个,则按照特定的规则找出一个最匹配的,并返回其对应的HandlerMethod

可能有小伙伴有很多疑惑,为什么一个请求请求地址会有多个实现。我给你举一个例子,有3个相同的handler方法,@RequestMapping中的其他属性都相同,只是method不同,这样就有可能根据一个url找到3个RequestMappingInfo,这样就有选择一个最优RequestMappingInfo的过程

当然还有其他情况,比如一个url同时匹配2个通配符路径。

可以看出来查找的过程还是挺简单的,接着我们就来分析一下注册的过程

RequestMappingHandlerMapping的初始化过程

从上面的分析中我们可以得出结论,在Spring容器启动后,RequestMappingInfo和HandlerMethod的映射关系已经被保存到MappingRegistry对象中,那么它是多会保存的以及以何种形式保存的?

通过查看调用关系法相,MappingRegistry中各种映射关系的初始化在register()方法,所以我们只需要在其register()方法上加一个断点,查看其调用链路即可

在这里插入图片描述

  1. RequestMappingHandlerMapping#afterPropertiesSet
  2. AbstractHandlerMethodMapping#afterPropertiesSet
  3. AbstractHandlerMethodMapping#initHandlerMethods
  4. AbstractHandlerMethodMapping#detectHandlerMethods
  5. AbstractHandlerMethodMapping#registerHandlerMethod

从RequestMappingInfoHandlerMapping可以看出AbstractHandlerMethodMapping中的泛型类为RequestMappingInfo

RequestMappingInfoHandlerMapping extends AbstractHandlerMethodMapping<RequestMappingInfo>

接着仔细分析这些方法

RequestMappingHandlerMapping#afterPropertiesSet

@Override
public void afterPropertiesSet() {
	// 创建映射信息构造器配置,用于构造映射信息RequestMappingInfo
	this.config = new RequestMappingInfo.BuilderConfiguration();
	this.config.setUrlPathHelper(getUrlPathHelper());
	this.config.setPathMatcher(getPathMatcher());
	this.config.setSuffixPatternMatch(this.useSuffixPatternMatch);
	this.config.setTrailingSlashMatch(this.useTrailingSlashMatch);
	this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch);
	// 设置内容协商管理器组件
	this.config.setContentNegotiationManager(getContentNegotiationManager());

	// 执行处理器方法的初始化逻辑
	super.afterPropertiesSet();
}

主要做了一些初始化设置

AbstractHandlerMethodMapping#afterPropertiesSet

@Override
public void afterPropertiesSet() {
	initHandlerMethods();
}

单纯调用initHandlerMethods方法

AbstractHandlerMethodMapping#initHandlerMethods

protected void initHandlerMethods() {
	if (logger.isDebugEnabled()) {
		logger.debug("Looking for request mappings in application context: " + getApplicationContext());
	}
	
	// 获取ApplicationContext中所有BeanName
	String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
			BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :
			obtainApplicationContext().getBeanNamesForType(Object.class));

	for (String beanName : beanNames) {
		// 排除Scoped目标类型Bean
		if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
			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.isDebugEnabled()) {
					logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
				}
			}
			// 如果获取的BeanName类型不为空,且是一个处理器类型的Bean
			// 如果beanType上有Controller注解或者RequestMapping注解则是handler
			if (beanType != null && isHandler(beanType)) {
				detectHandlerMethods(beanName);
			}
		}
	}
	handlerMethodsInitialized(getHandlerMethods());
}

从容器中拿到所有的beanName,排除Scope类型的bean,根据beanName得到beanType,如果一个beanType是一个handler,则调用AbstractHandlerMethodMapping#detectHandlerMethods方法

AbstractHandlerMethodMapping#detectHandlerMethods

注册的重头戏就在这里了

protected void detectHandlerMethods(Object handler) {
	// 如果传入的handler是String类型,则表示是一个BeanName,从上下文获取
	Class<?> handlerType = (handler instanceof String ?
			obtainApplicationContext().getType((String) handler) : handler.getClass());

	if (handlerType != null) {
		Class<?> userType = ClassUtils.getUserClass(handlerType);
		// Method -> Method上对应的RequestMappingInfo
		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.isDebugEnabled()) {
			logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods);
		}
		methods.forEach((method, mapping) -> {
			// 获取真实的可执行的方法,因为上述查找逻辑在特殊情况下查找到的方法可能存在于代理上
			// 需要获取非代理方法作为可执行方法调用
			Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
			registerHandlerMethod(handler, invocableMethod, mapping);
		});
	}
}

就是获取Handler上的Method,以及根据Method上@RequestMapping注解封装的RequestMappingInfo,

最后根据Handler,Method,RequestMappingInfo将映射关系注册到map中

AbstractHandlerMethodMapping#registerHandlerMethod

protected void registerHandlerMethod(Object handler, Method method, T mapping) {
	this.mappingRegistry.register(mapping, handler, method);
}

AbstractHandlerMethodMapping#MappingRegistry#register

主要就是初始化本文最开始说的MappingRegistry中的5个成员变量

public void register(T mapping, Object handler, Method method) {
	// 获取写锁
	this.readWriteLock.writeLock().lock();
	try {
		// 把类和方法封装为HandlerMethod
		HandlerMethod handlerMethod = createHandlerMethod(handler, method);
		// 保证方法映射唯一
		// 如果一个相同的url对应多个handlerMethod则会抛出异常
		assertUniqueMethodMapping(handlerMethod, mapping);
		// 向映射查找表中添加  映射信息->对应的处理器方法
		this.mappingLookup.put(mapping, handlerMethod);

		if (logger.isInfoEnabled()) {
			logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod);
		}

		// 存储 不带统配符的url -> RequestMappingInfo 的映射关系
		List<String> directUrls = getDirectUrls(mapping);
		for (String url : directUrls) {
			this.urlLookup.add(url, mapping);
		}

		String name = null;
		// 命名策略不为空
		if (getNamingStrategy() != null) {
			// 通过命名策略获取映射名
			name = getNamingStrategy().getName(handlerMethod, mapping);
			// 注册到nameLookup
			addMappingName(name, handlerMethod);
		}

		// 获取HandlerMethod对应的CORS配置
		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();
	}
}

至此整个注册过程分析完毕,总结起来就是初始化Map和从Map中取值的过程

参考博客

[1]https://www.cnblogs.com/Java-Starter/p/10315964.html
[2]https://cloud.tencent.com/developer/article/1497621

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java识堂

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值