源码解析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());
	}
}
相关推荐
gopher9511几秒前
final,finally,finalize的区别
java·开发语言·jvm
Jason-河山9 分钟前
利用 Python 爬虫采集 1688商品详情
java·http
计算机源码社9 分钟前
分享一个餐饮连锁店点餐系统 餐馆食材采购系统Java、python、php三个版本(源码、调试、LW、开题、PPT)
java·python·php·毕业设计项目·计算机课程设计·计算机毕业设计源码·计算机毕业设计选题
Zww089113 分钟前
idea插件市场安装没反应
java·ide·intellij-idea
0DayHP14 分钟前
HTB:Bike[WriteUP]
运维·服务器
夜雨翦春韭14 分钟前
【代码随想录Day31】贪心算法Part05
java·数据结构·算法·leetcode·贪心算法
计算机学姐14 分钟前
基于微信小程序的调查问卷管理系统
java·vue.js·spring boot·mysql·微信小程序·小程序·mybatis
DieSnowK15 分钟前
[C++][第三方库][httplib]详细讲解
服务器·开发语言·c++·http·第三方库·新手向·httplib
problc25 分钟前
Android 组件化利器:WMRouter 与 DRouter 的选择与实践
android·java