源码解析SpringMVC之RequestMapping注解原理

1、启动初始化

核心:得到应用上下文中存在的全部bean后依次遍历,分析每一个目标handler & 目标方法存在的注解@RequestMapping,将其相关属性封装为实例RequestMappingInfo。最终将 uri & handler 之间的映射关系维护在类AbstractHandlerMethodMapping中的内部类RequestMappingInfo中。

利用RequestMappingHandlerMappingInitializingBean接口特性来完成请求 uri & handler 之间的映射关系。具体详情参考其父类AbstractHandlerMethodMapping实现其功能,如下:

java 复制代码
public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {
	//内部类
	private final MappingRegistry mappingRegistry = new MappingRegistry();
	
	protected void initHandlerMethods() {
		for (String beanName : getCandidateBeanNames()) {//从应用上下文中获取全部的bean
			processCandidateBean(beanName);
		}
	}
	
	protected void processCandidateBean(String beanName) {
		Class<?> beanType = obtainApplicationContext().getType(beanName);
		// 判断是否为Handler的条件为:是否存在注解Controller 或者 RequestMapping
		if (beanType != null && isHandler(beanType)) {
			detectHandlerMethods(beanName);
		}
	}
		
	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);
			// 集合methods其key:反射中Method类。value:RequestMappingInfo
			Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
					(MethodIntrospector.MetadataLookup<T>) method -> {
						// 调用具体的实现类,例如RequestMappingHandlerMapping
						return getMappingForMethod(method, userType);
					});
			methods.forEach((method, mapping) -> {
				Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
				registerHandlerMethod(handler, invocableMethod, mapping);
			});
		}
	}
	
	protected void registerHandlerMethod(Object handler, Method method, T mapping) {
		this.mappingRegistry.register(mapping, handler, method);
	}
}

1.1、RequestMappingHandlerMapping

解析目标类 & 目标方法之@RequestMapping注解相关属性。

java 复制代码
public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping{
	@Override
	@Nullable
	protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
		//将目标方法存在的@RequestMapping注解相关属性封装为RequestMappingInfo
		RequestMappingInfo info = createRequestMappingInfo(method);
		if (info != null) {
			//如果目标handler也存在@RequestMapping注解,则也封装为RequestMappingInfo
			RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
			if (typeInfo != null) {
				// 将目标方法@RequestMapping相关属性与目标handler之@RequestMapping相关属性合并起来
				info = typeInfo.combine(info);
			}
			String prefix = getPathPrefix(handlerType);//获取目标handler之url前缀
			if (prefix != null) {
				info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
			}
		}
		return info;
	}
	
	private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
		RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
		return (requestMapping != null ? createRequestMappingInfo(requestMapping, null) : null);
	}
	
	protected RequestMappingInfo createRequestMappingInfo(RequestMapping requestMapping,RequestCondition condition) {
		// 获取某个具体目标方法其@RequestMapping注解相关属性,最终封装为RequestMappingInfo
		RequestMappingInfo.Builder builder = RequestMappingInfo
				.paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))
				.methods(requestMapping.method())
				.params(requestMapping.params())
				.headers(requestMapping.headers())
				.consumes(requestMapping.consumes())
				.produces(requestMapping.produces())
				.mappingName(requestMapping.name());
		return builder.options(this.config).build();
	}
}

1.2.MappingRegistry

java 复制代码
public abstract class AbstractHandlerMethodMapping<T> {

	class MappingRegistry {
		private final Map<T, MappingRegistration<T>> registry = new HashMap<>();
		//集合mappingLookup之key:RequestMappingInfo。value:HandlerMethod
		private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();
		private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();
		
		public void register(T mapping, Object handler, Method method) {
			...
			// 目标类的目标方法最终封装为HandlerMethod  handler实为目标bean的beanName
			HandlerMethod handlerMethod = createHandlerMethod(handler, method);
			this.mappingLookup.put(mapping, handlerMethod);
			List<String> directUrls = getDirectUrls(mapping);
			for (String url : directUrls) {
				this.urlLookup.add(url, mapping);
			}
			...
			this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
		}
	}
}

2、处理请求

SpringMVC利用请求url获取目标handler。

java 复制代码
public class DispatcherServlet extends FrameworkServlet {
	@Nullable
	protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		if (this.handlerMappings != null) {
			for (HandlerMapping mapping : this.handlerMappings) {
				// 围绕 RequestMappingHandlerMapping 展开
				HandlerExecutionChain handler = mapping.getHandler(request);
				if (handler != null) {
					return handler;
				}
			}
		}
		return null;
	}
}
java 复制代码
public abstract class AbstractHandlerMethodMapping<T>  implements InitializingBean {
	private final MappingRegistry mappingRegistry = new MappingRegistry();
	@Override
	protected HandlerMethod getHandlerInternal(HttpServletRequest request){
		// 获取requestUri
		String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
		// 从 mappingRegistry 获取目标handler
		HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
		return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
	}
	
	protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) {
		List<Match> matches = new ArrayList<>();
		// 利用 lookupPath 从MappingRegistry相关属性中获取目标handler之T
		List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
		// 通过章节1得知,返回的类为 RequestMappingInfo,即@RequestMapping注解的相关属性
		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);
		}
		// 如果局部matches中元素为空,说明请求头校验失败
		if (!matches.isEmpty()) {
			Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
			matches.sort(comparator);
			Match bestMatch = matches.get(0);
			...
			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);
		}
	}
}

请求头相关参数校验失败抛出的异常包括:HttpRequestMethodNotSupportedExceptionHttpMediaTypeNotSupportedExceptionHttpMediaTypeNotAcceptableExceptionUnsatisfiedServletRequestParameterException

但是这些类型的异常好像不能被全局拦截器拦截处理。

2.1.解析请求头相关属性

核心就是校验请求request中相关属性跟RequestMappingInfo中属性是否匹配。

java 复制代码
public abstract class AbstractHandlerMethodMapping<T>  implements InitializingBean {

	private void addMatchingMappings(Collection mappings, List matches, HttpServletRequest request) {
		for (T mapping : mappings) {
			T match = getMatchingMapping(mapping, request);
			if (match != null) {//正常请求校验都通过,最终返回重新包装后的RequestMappingInfo
				matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping)));
			}
		}
	}
}
java 复制代码
public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMethodMapping {
	@Override
	protected RequestMappingInfo getMatchingMapping(RequestMappingInfo info, HttpServletRequest request) {
		return info.getMatchingCondition(request);
	}
}

2.1.1、RequestMappingInfo

该类中的相关属性是对 注解@RequestMapping之字段属性的封装。

java 复制代码
public final class RequestMappingInfo implements RequestCondition<RequestMappingInfo> {
	public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {
		//请求方式: 校验Method属性,即是否为post or get 等相对应的请求方式
		RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(request);
		if (methods == null) {
			return null;
		}
		//请求参数
		ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(request);
		if (params == null) {
			return null;
		}
		//consumer参数:请求头中contentType是否一致。
		HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(request);
		if (headers == null) {
			return null;
		}
		//producer参数:请求头中Accept是否一致
		ConsumesRequestCondition consumes = this.consumesCondition.getMatchingCondition(request);
		if (consumes == null) {
			return null;
		}
		ProducesRequestCondition produces = this.producesCondition.getMatchingCondition(request);
		if (produces == null) {
			return null;
		}
		PatternsRequestCondition patterns = this.patternsCondition.getMatchingCondition(request);
		if (patterns == null) {
			return null;
		}
		RequestConditionHolder custom = this.customConditionHolder.getMatchingCondition(request);
		if (custom == null) {
			return null;
		}
		return new RequestMappingInfo(this.name, patterns,
				methods, params, headers, consumes, produces, custom.getCondition());
	}
}
相关推荐
FG.2 分钟前
Day13
java·面试
front_explorers3 分钟前
Umi项目必看,从Webpack到Rspack,KMI引领性能革命🚀
前端
旺仔牛仔QQ糖4 分钟前
都写那么多项目了, 傻傻分不清楚NODE_ENV 和 模式(Mode) 两者区别是什么
前端·面试
xcLeigh9 分钟前
HTML5实现简洁的体育赛事网站源码
前端·html
棉花糖超人12 分钟前
【从0-1的CSS】第1篇:CSS简介,选择器已经常用样式
前端·css
GISer_Jing17 分钟前
XHR / Fetch / Axios 请求的取消请求与请求重试
前端·javascript·网络
天涯学馆21 分钟前
微前端架构设计:从理论到实践的全面指南
前端·javascript·面试
Verin32 分钟前
Next.js+Wagmi+rainbowkit构建以太坊合约交互模版
前端·web3·以太坊
KenXu34 分钟前
🚀 Cursor 1.0 重磅发布!AI代码编辑器迎来革命性升级
前端
凌辰揽月36 分钟前
Web后端基础(Maven基础)
前端·pycharm·maven