上一篇分享的是《基于springcloud一套可落地实施的安全认证框架整合》,这篇给大家分享《SpringMVC-RequestMappingHandlerMapping》。
前言
使用SpringBoot进行web开发时,控制器类由@RestController注解修饰,通常@RestController注解与@RequestMapping配合使用,被修饰的类用于处理由DispatcherServlet分发下来的web请求。那么当一个web请求到达时,DispatcherServlet是如何将请求下发给对应的控制器处理呢。该篇文章将结合SpringMVC源码,对在分发请求过程中起重要作用的类RequestMappingHandlerMapping进行学习。
SpringBoot版本:2.4.1
正文
一. DispatcherServlet分发请求
当一个web请求到来时,DispatcherServlet负责接收请求并响应结果。DispatcherServlet首先需要找到当前请求对应的Handler(处理器)来处理请求,流程如下图所示。
HandlerMapping称为处理器映射器,是一个接口,定义web请求和Handler之间的映射。DispatcherServlet中有一个成员变量叫做handlerMappings,是一个HandlerMapping的集合,当请求到来时,DispatcherServlet遍历handlerMappings中的每一个HandlerMapping以获取对应的handler。上述步骤发生在DispatcherServlet的doDispatch()方法中,部分源码如下所示。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
//根据请求获取Handler
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
......
}
catch (Exception ex) {
......
}
catch (Throwable err) {
......
}
......
}
catch (Exception ex) {
......
}
catch (Throwable err) {
......
}
finally {
......
}
}
Handler的获取由DispatcherServlet的getHandler()方法完成,下面再看一下getHandler()具体做了什么事情。
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
前文已知handlerMappings是HandlerMapping的集合,因此getHandler()主要实现遍历每一个HandlerMapping并根据请求获取对应的Handler。仅看源码不够直观,现在通过打断点的方式实际看一下handlerMappings里的内容。
观察handlerMappings的内容可以发现,handlerMappings中加载了ApplicationContext中的所有HandlerMapping,例如BeanNameUrlHandlerMapping,将url与名称以/开头的bean建立了映射关系,再例如本文重点讨论的RequestMappingHandlerMapping,能够将@Controller注解修饰的类中的@RequestMapping注解的内容解析成RequestMappingInfo数据结构。每一种HandlerMapping都有自己相应的实现,来完成通过请求获取Handler的功能。
小节:DispatcherServlet分发请求主要是通过遍历HandlerMapping的集合并将请求传递给HandlerMapping以获取对应的Handler。
二. RequestMappingHandlerMapping初始化
首先通过类图认识一下RequestMappingHandlerMapping。
由类图可知,RequestMappingHandlerMapping的父类AbstractHandlerMethodMapping实现了InitializingBean接口,RequestMappingHandlerMapping和AbstractHandlerMethodMapping均实现了afterPropertiesSet()方法,该方法会在bean属性完成初始化后被调用。这里先分析RequestMappingHandlerMapping实现的afterPropertiesSet()方法。
public void afterPropertiesSet() {
this.config = new RequestMappingInfo.BuilderConfiguration();
this.config.setTrailingSlashMatch(useTrailingSlashMatch());
this.config.setContentNegotiationManager(getContentNegotiationManager());
if (getPatternParser() != null) {
this.config.setPatternParser(getPatternParser());
Assert.isTrue(!this.useSuffixPatternMatch && !this.useRegisteredSuffixPatternMatch,
"Suffix pattern matching not supported with PathPatternParser.");
}
else {
this.config.setSuffixPatternMatch(useSuffixPatternMatch());
this.config.setRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch());
this.config.setPathMatcher(getPathMatcher());
}
//调用AbstractHandlerMethodMapping的afterPropertiesSet()方法
super.afterPropertiesSet();
}
在RequestMappingHandlerMapping的afterPropertiesSet()方法中调用了AbstractHandlerMethodMapping的afterPropertiesSet()方法,下面再分析AbstractHandlerMethodMapping的afterPropertiesSet()方法。
public void afterPropertiesSet() {
initHandlerMethods();
}
protected void initHandlerMethods() {
//获取容器中所有bean的名称
for (String beanName : getCandidateBeanNames()) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
//根据bean名称处理bean
processCandidateBean(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}
protected void processCandidateBean(String beanName) {
Class<?> beanType = null;
try {
//先获取容器,然后根据bean名称获取bean的Class对象
beanType = obtainApplicationContext().getType(beanName);
}
catch (Throwable ex) {
if (logger.isTraceEnabled()) {
logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
}
}
//判断bean是否是由@Controller注解或者@RequestMapping注解修饰的对象
if (beanType != null && isHandler(beanType)) {
detectHandlerMethods(beanName);
}
}
在AbstractHandlerMethodMapping的afterPropertiesSet()方法中,调用了initHandlerMethods()方法,该方法主要是从容器中将所有bean获取出来(这里是获取的所有bean的名称),然后又在processCandidateBean()方法中判断每个bean是否是由@Controller注解或者@RequestMapping注解修饰的对象,如果是则判断该bean为一个处理器,则需要在detectHandlerMethods()方法中查找出该处理器的处理器方法。detectHandlerMethods()方法是一个重要方法,并且阅读起来有一点绕,下面详细看一下这个方法做的事情。
protected void detectHandlerMethods(Object handler) {
//获取handler的Class对象
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass());
if (handlerType != null) {
//获取handler的真实Class对象(假若handler是cglib代理生成的子类,则获取原始类的Class对象)
Class<?> userType = ClassUtils.getUserClass(handlerType);
//调用getMappingForMethod()获取method和RequestMappingInfo的map集合
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> {
try {
//在selectMethods()方法中实际调用的是getMappingForMethod()方法
return getMappingForMethod(method, userType);
}
catch (Throwable ex) {
throw new IllegalStateException("Invalid mapping on handler class [" +
userType.getName() + "]: " + method, ex);
}
});
if (logger.isTraceEnabled()) {
logger.trace(formatMappings(userType, methods));
}
methods.forEach((method, mapping) -> {
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
//将handler,method和RequestMappingInfo缓存,并建立映射关系
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}
detectHandlerMethods()中首先是获取handler的真实Class对象,然后使用MethodIntrospector.selectMethods(Class<?> targetType, final MetadataLookup<T> metadataLookup)方法将handler的方法解析成<Method, RequestMappingInfo>的map集合。metadataLookup是一个回调函数,metadataLookup的具体使用稍后再分析,现在再看一下MethodIntrospector.selectMethods()的具体实现。
public static <T> Map<Method, T> selectMethods(Class<?> targetType, final MetadataLookup<T> metadataLookup) {
final Map<Method, T> methodMap = new LinkedHashMap<>();
Set<Class<?>> handlerTypes = new LinkedHashSet<>();
Class<?> specificHandlerType = null;
//判断给定对象是否是JDK动态代理生成对象
//如果不是,(如果是CGLIB动态代理生成对象)则获取其原始类的Class对象,并添加到Class的Set集合中
if (!Proxy.isProxyClass(targetType)) {
specificHandlerType = ClassUtils.getUserClass(targetType);
handlerTypes.add(specificHandlerType);
}
//获取给定对象和给定对象父类实现的所有接口的Class对象,并添加到Class的Set集合中
handlerTypes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetType));
for (Class<?> currentHandlerType : handlerTypes) {
final Class<?> targetClass = (specificHandlerType != null ? specificHandlerType : currentHandlerType);
ReflectionUtils.doWithMethods(currentHandlerType, method -> {
//获取真实方法(如果方法是接口的方法则根据Class对象找到真实实现的方法)
Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
//执行回调函数metadataLookup
T result = metadataLookup.inspect(specificMethod);
if (result != null) {
//根据真实方法获取其桥接方法,但如果真实方法不是桥接方法则返回其本身
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
if (bridgedMethod == specificMethod || metadataLookup.inspect(bridgedMethod) == null) {
//将真实方法与其回调函数执行结果存放到map中
methodMap.put(specificMethod, result);
}
}
}, ReflectionUtils.USER_DECLARED_METHODS);
}
return methodMap;
}
在MethodIntrospector.selectMethods()中有一个Class对象的Set集合,里面存放了给定对象的Class对象以及给定对象实现的接口的Class对象(如果给定对象有父类,则还包括父类实现的接口的Class对象),然后遍历Set集合,并使用ReflectionUtils.doWithMethods(Class<?> clazz, MethodCallback mc, @Nullable MethodFilter mf)处理Set集合中的每一个Class对象。mc是一个回调函数,mc的具体使用稍后分析,最后再来看一下ReflectionUtils.doWithMethods()的具体使用。
public static void doWithMethods(Class<?> clazz, MethodCallback mc, @Nullable MethodFilter mf) {
//获取给定对象的所有声明方法
Method[] methods = getDeclaredMethods(clazz, false);
for (Method method : methods) {
//对method根据传入的MethodFilter进行过滤,满足指定的条件的method才执行回调方法
if (mf != null && !mf.matches(method)) {
continue;
}
try {
//对满足条件的method执行回调方法
mc.doWith(method);
}
catch (IllegalAccessException ex) {
throw new IllegalStateException("Not allowed to access method '" + method.getName() + "': " + ex);
}
}
//递归对给定对象的父对象执行相同操作
if (clazz.getSuperclass() != null && (mf != USER_DECLARED_METHODS || clazz.getSuperclass() != Object.class)) {
doWithMethods(clazz.getSuperclass(), mc, mf);
}
else if (clazz.isInterface()) {
for (Class<?> superIfc : clazz.getInterfaces()) {
doWithMethods(superIfc, mc, mf);
}
}
}
ReflectionUtils.doWithMethods()中做的事情很简单,先将给定的Class对象的所有声明方法获取出来,然后针对每一个声明方法用给定的MethodFilter进行过滤,再将过滤后的声明方法传入回到函数mc并执行,(现在往前推)回调函数mc中实际就是将声明方法传入回调函数metadataLookup并执行,然后将声明方法和metadataLookup执行得到的结果存入map集合,回调函数metadataLookup中实际就是将声明方法传入getMappingForMethod()方法,在getMappingForMethod()中会将声明方法和handler上的@RequestMapping注解信息解析成RequestMappingInfo并返回。
前文可知MethodIntrospector.selectMethods()中调用ReflectionUtils.doWithMethods()时传入的MethodFilter为ReflectionUtils.USER_DECLARED_METHODS,ReflectionUtils.USER_DECLARED_METHODS表示如果方法即不是桥接方法也不是合成方法时则匹配成功,此时调用matches()返回true。
说明一下getMappingForMethod()方法,该方法是AbstractHandlerMethodMapping声明的抽象方法,RequestMappingHandlerMapping对其的实现如下。
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
//将method上的@RequestMapping注解内容解析为RequestMappingInfo
RequestMappingInfo info = createRequestMappingInfo(method);
if (info != null) {
//将类上的@RequestMapping注解内容解析为RequestMappingInfo
RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
if (typeInfo != null) {
//将method和类上的@RequestMethod注解解析成的RequestMappingInfo组合
info = typeInfo.combine(info);
}
String prefix = getPathPrefix(handlerType);
if (prefix != null) {
info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
}
}
return info;
}
RequestMappingHandlerMapping在getMappingForMethod()中先后分别获取方法和类的@RequestMapping注解解析成的RequestMappingInfo并进行组合。@RequestMapping注解信息的解析发生在createRequestMappingInfo()方法中,其实现如下。
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
//查找@RequestMapping注解
RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
RequestCondition<?> condition = (element instanceof Class ?
getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
//能够查找到@RequestMapping注解,解析@RequestMapping的信息
return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}
protected RequestMappingInfo createRequestMappingInfo(
RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) {
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());
if (customCondition != null) {
builder.customCondition(customCondition);
}
return builder.options(this.config).build();
}
最后回到detectHandlerMethods()方法,该方法中执行完MethodIntrospector.selectMethods()后会得到method和RequestMappingInfo的map集合,然后遍历map集合并调用registerHandlerMethod()方法将handler,method和RequestMappingInfo缓存,并建立映射关系。registerHandlerMethod()方法中会调用mappingRegistry的register()方法,mappingRegistry是AbstractHandlerMethodMapping的一个内部类对象,register()方法主要是将handler,method和RequestMappingInfo写入mappingRegistry的pathLookUp,nameLookUp,corsLookUp和registry数据结构中。相关源码如下所示。
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
this.mappingRegistry.register(mapping, handler, method);
}
public void register(T mapping, Object handler, Method method) {
this.readWriteLock.writeLock().lock();
try {
//获取HandlerMethod的实例,HandlerMethod对handler method进行了一层封装,其持有handler的对象,并且可以方便获取方法入参和出参
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
//在registry中根据RequestMappingInfo获取已经缓存的cachedHandlerMethod,如果cachedHandlerMethod不为空且不等于handlerMethod,则报错
validateMethodMapping(handlerMethod, mapping);
//根据RequestMappingInfo获取path的Set集合,并建立path和RequestMappingInfo的映射关系:Map<String, List<RequestMappingInfo>> pathLookUp
Set<String> directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping);
for (String path : directPaths) {
this.pathLookup.add(path, mapping);
}
String name = null;
if (getNamingStrategy() != null) {
//拼接name
//规则:handler的Class对象名称取大写字母 + # + 方法名
name = getNamingStrategy().getName(handlerMethod, mapping);
//建立name与handlerMethod的映射关系:Map<String, List<HandlerMethod>> nameLookup
addMappingName(name, handlerMethod);
}
//获取跨域配置对象
CorsConfiguration config = initCorsConfiguration(handler, method, mapping);
if (config != null) {
config.validateAllowCredentials();
//建立handlerMethod和跨域配置对象的映射关系:Map<HandlerMethod, CorsConfiguration> corsLookup
this.corsLookup.put(handlerMethod, config);
}
//建立RequestMappingInfo与MappingRegistration的映射关系:Map<RequestMappingInfo, MappingRegistration<RequestMappingInfo>>
this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directPaths, name));
}
finally {
this.readWriteLock.writeLock().unlock();
}
}
MappingRegistration将传入的RequestMappingInfo,获取的handlerMethod,获得的directPaths和拼接的name做了一层封装。
static class MappingRegistration<T> {
private final T mapping;
private final HandlerMethod handlerMethod;
private final Set<String> directPaths;
@Nullable
private final String mappingName;
public MappingRegistration(T mapping, HandlerMethod handlerMethod,
@Nullable Set<String> directPaths, @Nullable String mappingName) {
Assert.notNull(mapping, "Mapping must not be null");
Assert.notNull(handlerMethod, "HandlerMethod must not be null");
this.mapping = mapping;
this.handlerMethod = handlerMethod;
this.directPaths = (directPaths != null ? directPaths : Collections.emptySet());
this.mappingName = mappingName;
}
public T getMapping() {
return this.mapping;
}
public HandlerMethod getHandlerMethod() {
return this.handlerMethod;
}
public Set<String> getDirectPaths() {
return this.directPaths;
}
@Nullable
public String getMappingName() {
return this.mappingName;
}
}
小节:RequestMappingHandlerMapping初始化时会先获取容器中所有被@Controller注解或@RequestMapping注解修饰的类的对象(handler对象),然后遍历这些对象和其父对象的所有方法,将这些方法的@RequestMapping注解信息(如果有)解析成RequestMappingInfo,最后将handler对象,handler方法和RequestMappingInfo加入缓存并建立映射关系。
三. RequestMappingHandlerMapping获取handler
回顾上文,一小节中提到,web请求来到DispatcherServlet之后,会先遍历HandlerMapping的集合,然后将请求传入HandlerMapping并获取handler。再贴出源码如下所示。
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
实际上,RequestMappingHandlerMapping获取handler是发生在其父类AbstractHandlerMapping的getHandler()方法中,源码如下。
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//根据request获取handler,实际获取到的handler是一个HandlerMethod对象
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
//根据request和handler创建HandlerExecutionChain对象,该对象还会包含和request匹配的拦截器
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
if (logger.isTraceEnabled()) {
logger.trace("Mapped to " + handler);
}
else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
logger.debug("Mapped to " + executionChain.getHandler());
}
//如果handler有跨域配置,则更新HandlerExecutionChain对象使得其可以进行跨域处理
if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
CorsConfiguration config = getCorsConfiguration(handler, request);
if (getCorsConfigurationSource() != null) {
CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request);
config = (globalConfig != null ? globalConfig.combine(config) : config);
}
if (config != null) {
config.validateAllowCredentials();
}
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
getHandler()关键的操作就是在getHandlerInternal()方法中根据request获取到了handler对象(实际是一个HandlerMethod对象),RequestMappingHandlerMapping的getHandlerInternal()会调用父类AbstractHandlerMethodMapping的getHandlerInternal()方法,现在看一下其做了什么事情。
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
//根据request获取请求路径
String lookupPath = initLookupPath(request);
this.mappingRegistry.acquireReadLock();
try {
//根据请求路径和request获取最匹配的HandlerMethod对象
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock();
}
}
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<>();
//通过缓存pathLookup获取请求路径映射的RequestMappingInfo集合
List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
if (directPathMatches != null) {
//集合中的每一个RequestMappingInfo均会和request进行匹配,匹配上的话就创建一个Match对象并加入Match对象集合
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
}
//Match集合不为空则从Match集合中找到最匹配的Match对象,并返回该Match对象的HandlerMethod对象
if (!matches.isEmpty()) {
Match bestMatch = matches.get(0);
if (matches.size() > 1) {
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
matches.sort(comparator);
bestMatch = matches.get(0);
if (logger.isTraceEnabled()) {
logger.trace(matches.size() + " matching mappings: " + matches);
}
if (CorsUtils.isPreFlightRequest(request)) {
return PREFLIGHT_AMBIGUOUS_MATCH;
}
Match secondBestMatch = matches.get(1);
if (comparator.compare(bestMatch, secondBestMatch) == 0) {
Method m1 = bestMatch.handlerMethod.getMethod();
Method m2 = secondBestMatch.handlerMethod.getMethod();
String uri = request.getRequestURI();
throw new IllegalStateException(
"Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
}
}
request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.handlerMethod;
}
else {
return handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request);
}
}
小节:RequestMappingHandlerMapping获取handler实际就是根据request在映射缓存中寻找最匹配的HandlerMethod对象并封装成HandlerExecutionChain。
总结
RequestMappingHandlerMapping主要用于@Controller注解和@RequestMapping注解结合使用的场景,能够将我们编写的控制器信息缓存并在请求到来时根据请求信息找到最合适的控制器来处理请求。最后,学习SpringMVC源码,通过打断点观察内部数据结构的方式往往能够更直观地帮助我们理解,值得尝试。
- 以上就是《SpringMVC-RequestMappingHandlerMapping》的分享。
- 也欢迎大家交流探讨,该文章若有不正确的地方,希望大家多多包涵。
- 创作不易,你们的支持就是我最大的动力,如果对大家有帮忙给个赞哦~~~