Spring MVC 源码 - HandlerMapping 组件(三)之 AbstractHandlerMethodMapping
HandlerMapping 组件
HandlerMapping 组件,请求的处理器匹配器,负责为请求找到合适的 HandlerExecutionChain 处理器执行链,包含处理器(handler)和拦截器们(interceptors)
handler 处理器是 Object 类型,可以将其理解成 HandlerMethod 对象(例如我们使用最多的 @RequestMapping 注解所标注的方法会解析成该对象),包含了方法的所有信息,通过该对象能够执行该方法
HandlerInterceptor 拦截器对处理请求进行增强处理,可用于在执行方法前、成功执行方法后、处理完成后进行一些逻辑处理
HandlerMapping 组件(三)之 AbstractHandlerMethodMapping
先来回顾一下HandlerMapping 接口体系的结构:

在《HandlerMapping 组件(一)之 AbstractHandlerMapping》文档中已经分析了 HandlerMapping 组件的 AbstractHandlerMapping 抽象类基类
那么本文就接着来分析图中红色框部分的 AbstractHandlerMethodMapping 系,该系是基于 Method 进行匹配。例如,我们所熟知的 @RequestMapping 等注解的方式。一共就三个类,不多
涉及到的内容比较多,可以直接查看我的总结
回顾
先来回顾一下在 DispatcherServlet 中处理请求的过程中通过 HandlerMapping 组件,获取到 HandlerExecutionChain 处理器执行链的方法,是通过AbstractHandlerMapping 的 getHandler 方法来获取的,如下:
@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {// <1> 获得处理器(HandlerMethod 或者 HandlerExecutionChain),该方法是抽象方法,由子类实现Object handler = getHandlerInternal(request);// <2> 获得不到,则使用默认处理器// <3> 还是获得不到,则返回 null// <4> 如果找到的处理器是 String 类型,则从 Spring 容器中找到对应的 Bean 作为处理器// <5> 创建 HandlerExecutionChain 对象(包含处理器和拦截器)// ... 省略相关代码return executionChain;
}
在 AbstractHandlerMapping 获取 HandlerExecutionChain 处理器执行链的方法中,需要先调用 getHandlerInternal(HttpServletRequest request) 抽象方法,获取请求对应的处理器,该方法由子类去实现,也就上图中黄色框和红色框两类子类,本文分析红色框部分内容
注解
Spring MVC 的请求匹配的注解,体系结构如下:

关于这些注解,大家已经非常熟悉了,各自的属性就不再进行讲述了,可具体查看源码:
org.springframework.web.bind.annotation.@Mapping
org.springframework.web.bind.annotation.@RequestMapping
org.springframework.web.bind.annotation.@GetMapping
org.springframework.web.bind.annotation.@PostMapping
org.springframework.web.bind.annotation.@PutMapping
org.springframework.web.bind.annotation.@DeleteMapping
org.springframework.web.bind.annotation.@PatchMapping
AbstractHandlerMethodMapping
org.springframework.web.servlet.result.method.AbstractHandlerMethodMapping,实现 InitializingBean 接口,继承 AbstractHandlerMapping 抽象类,以 Method 方法 作为 Handler 处理器 的 HandlerMapping 抽象类,提供 Mapping 的初始化、注册等通用的骨架方法。
那么具体是什么呢?AbstractHandlerMethodMapping 定义为了 <T> 泛型,交给子类做决定。例如,子类 RequestMappingInfoHandlerMapping 使用 RequestMappingInfo 类作为 <T> 泛型,也就是我们在上面注解模块看到的 @RequestMapping 等注解信息。
构造方法
public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {/*** 是否只扫描可访问的 HandlerMethod 们*/private boolean detectHandlerMethodsInAncestorContexts = false;/*** Mapping 命名策略*/@Nullableprivate HandlerMethodMappingNamingStrategy<T> namingStrategy;/*** Mapping 注册表*/private final MappingRegistry mappingRegistry = new MappingRegistry();
}
<T> 泛型,就是我们前面要一直提到的 Mapping 类型
mappingRegistry:Mapping 注册表,详细见下文
namingStrategy :org.springframework.web.servlet.handler.HandlerMethodMappingNamingStrategy 接口,HandlerMethod 的 Mapping 的名字生成策略接口
@FunctionalInterface
public interface HandlerMethodMappingNamingStrategy<T> {/*** 根据 HandlerMethod 获取名称,就是为对应的 Mappring 对象生成一个名称,便于获取*/String getName(HandlerMethod handlerMethod, T mapping);
}
// org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMethodMappingNamingStrateg.java
public class RequestMappingInfoHandlerMethodMappingNamingStrategy implements HandlerMethodMappingNamingStrategy<RequestMappingInfo> {/** Separator between the type and method-level parts of a HandlerMethod mapping name. */public static final String SEPARATOR = "#";@Overridepublic String getName(HandlerMethod handlerMethod, RequestMappingInfo mapping) {// 情况一,mapping 名字非空,则使用 mapping 的名字if (mapping.getName() != null) {return mapping.getName();}// 情况二,使用类名大写 + "#" + 方法名StringBuilder sb = new StringBuilder();String simpleTypeName = handlerMethod.getBeanType().getSimpleName();for (int i = 0; i < simpleTypeName.length(); i++) {if (Character.isUpperCase(simpleTypeName.charAt(i))) {sb.append(simpleTypeName.charAt(i));}}sb.append(SEPARATOR).append(handlerMethod.getMethod().getName());return sb.toString();}}
情况一,如果 Mapping 已经配置名字,则直接返回。例如,@RequestMapping(name = "login", value = "user/login") 注解的方法,它对应的 Mapping 的名字就是 login
情况二,如果 Mapping 未配置名字,则使用使用类名大写 + "#" + 方法名。例如,@RequestMapping(value = "user/login") 注解的方法,假设它所在的类为 UserController ,对应的方法名为 login ,则它对应的 Mapping 的名字就是 USERCONTROLLER#login
MappingRegistry 注册表
AbstractHandlerMethodMapping 的内部类,Mapping 注册表
构造方法
class MappingRegistry {/*** 注册表** Key: Mapping* Value:{@link MappingRegistration}(Mapping + HandlerMethod)*/private final Map<T, MappingRegistration<T>> registry = new HashMap<>();/*** 注册表2** Key:Mapping* Value:{@link HandlerMethod}*/private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();/*** 直接 URL 的映射** Key:直接 URL(就是固定死的路径,而非多个)* Value:Mapping 数组*/private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();/*** Mapping 的名字与 HandlerMethod 的映射** Key:Mapping 的名字* Value:HandlerMethod 数组*/private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();/*** 读写锁*/private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
}
registry:注册表。Key: Mapping,即 <T> 泛型;Value:MappingRegistration 对象(Mapping + HandlerMethod)
mappingLookup:注册表2。Key: Mapping,即 <T> 泛型;Value:HandlerMethod 对象
urlLookup:直接 URL 的映射。Key:直接 URL(就是固定死的路径,而非多个);Value:Mapping 数组
nameLookup:Mapping 的名字与 HandlerMethod 的映射。Key:Mapping 的名字;Value:HandlerMethod 数组
readWriteLock:读写锁,为了操作上述属性时保证线程安全
register
register(T mapping, Object handler, Method method)方法,将 Mapping、Method、handler(方法所在类)之间的映射关系进行注册,会生成 HandlerMethod 对象,就是处理器对象,方法如下:
public void register(T mapping, Object handler, Method method) {// <1> 获得写锁this.readWriteLock.writeLock().lock();try {// <2.1> 创建 HandlerMethod 对象HandlerMethod handlerMethod = createHandlerMethod(handler, method);// <2.2> 校验当前 mapping 是否存在对应的 HandlerMethod 对象,如果已存在但不是当前的 handlerMethod 对象则抛出异常assertUniqueMethodMapping(handlerMethod, mapping);// <2.3> 将 mapping 与 handlerMethod 的映射关系保存至 this.mappingLookupthis.mappingLookup.put(mapping, handlerMethod);// <3.1> 获得 mapping 对应的普通 URL 数组List<String> directUrls = getDirectUrls(mapping);// <3.2> 将 url 和 mapping 的映射关系保存至 this.urlLookupfor (String url : directUrls) {this.urlLookup.add(url, mapping);}// <4> 初始化 nameLookupString name = null;if (getNamingStrategy() != null) {// <4.1> 获得 Mapping 的名字name = getNamingStrategy().getName(handlerMethod, mapping);// <4.2> 将 mapping 的名字与 HandlerMethod 的映射关系保存至 this.nameLookupaddMappingName(name, handlerMethod);}// <5> 初始化 CorsConfiguration 配置对象CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);if (corsConfig != null) {this.corsLookup.put(handlerMethod, corsConfig);}// <6> 创建 MappingRegistration 对象// 并与 mapping 映射添加到 registry 注册表中this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));}finally {// <7> 释放写锁this.readWriteLock.writeLock().unlock();}
}
获得写锁
添加相关映射至Map<T, HandlerMethod> mappingLookup属性
调用 createHandlerMethod(Object handler, Method method) 方法,创建 HandlerMethod 对象,详情见下文
校验当前 Mapping 是否存在对应的 HandlerMethod 对象,如果已存在但不是前一步创建 HandlerMethod 对象则抛出异常,保证唯一性
将 Mapping 与 HandlerMethod 的映射关系保存至 mappingLookup
添加相关映射至MultiValueMap<String, T> urlLookup属性
调用 getDirectUrls 方法,获得 Mapping 对应的直接 URL 数组,如下:
private List<String> getDirectUrls(T mapping) {List<String> urls = new ArrayList<>(1);// 遍历 Mapping 对应的路径for (String path : getMappingPathPatterns(mapping)) {// 非**模式**路径if (!getPathMatcher().isPattern(path)) {urls.add(path);}}return urls;
}
例如,@RequestMapping("/user/login") 注解对应的路径,就是直接路径
例如,@RequestMapping("/user/${id}") 注解对应的路径,不是直接路径,因为不确定性
4、添加相关映射至Map<String, List<HandlerMethod>> nameLookup属性
调用 HandlerMethodMappingNamingStrategy#getName(HandlerMethod handlerMethod, T mapping) 方法,获得 Mapping 的名字
调用 addMappingName(String name, HandlerMethod handlerMethod) 方法,添加 Mapping 的名字 + HandlerMethod 至 nameLookup,如下:
private void addMappingName(String name, HandlerMethod handlerMethod) {// 获得 Mapping 的名字,对应的 HandlerMethod 数组List<HandlerMethod> oldList = this.nameLookup.get(name);if (oldList == null) {oldList = Collections.emptyList();}// 如果已经存在,则不用添加for (HandlerMethod current : oldList) {if (handlerMethod.equals(current)) {return;}}// 添加到 nameLookup 中List<HandlerMethod> newList = new ArrayList<>(oldList.size() + 1);newList.addAll(oldList);newList.add(handlerMethod);this.nameLookup.put(name, newList);
}
和已有的进行合并,说明名称不是唯一哦
初始化 CorsConfiguration 配置对象,暂时忽略
创建 MappingRegistration 对象,并和 Mapping 进行映射添加至 registry
释放写锁
unregister
unregister(T mapping) 方法,取消上面方法注册的相关信息,方法如下:
public void unregister(T mapping) {
// 获得写锁
this.readWriteLock.writeLock().lock();
try {// 从 registry 中移除MappingRegistration<T> definition = this.registry.remove(mapping);if (definition == null) {return;}// 从 mappingLookup 中移除this.mappingLookup.remove(definition.getMapping());// 从 urlLookup 移除for (String url : definition.getDirectUrls()) {List<T> list = this.urlLookup.get(url);if (list != null) {list.remove(definition.getMapping());if (list.isEmpty()) {this.urlLookup.remove(url);}}}// 从 nameLookup 移除removeMappingName(definition);// 从 corsLookup 中移除this.corsLookup.remove(definition.getHandlerMethod());
}
finally {// 释放写锁this.readWriteLock.writeLock().unlock();
}
和 register 方法逻辑相反,依次移除相关映射
createHandlerMethod
createHandlerMethod(Object handler, Method method)方法,创建 Method 对应的 HandlerMethod 对象
protected HandlerMethod createHandlerMethod(Object handler, Method method) {HandlerMethod handlerMethod;// <1> 如果 handler 类型为 String, 说明对应一个 Bean 对象的名称// 例如 UserController 使用 @Controller 注解后,默认入参 handler 就是它的 beanName ,即 `userController`if (handler instanceof String) {String beanName = (String) handler;handlerMethod = new HandlerMethod(beanName, obtainApplicationContext().getAutowireCapableBeanFactory(), method);}// <2> 如果 handler 类型非 String ,说明是一个已经是一个 handler 对象,就无需处理,直接创建 HandlerMethod 对象else {handlerMethod = new HandlerMethod(handler, method);}return handlerMethod;
}
如果 handler 类型为 String, 说明对应一个 Bean 对象的名称。例如 UserController 使用 @Controller 注解后,默认入参 handler 就是它的 beanName ,即 userController
如果 handler 类型非 String ,说明是一个已经是一个 handler 对象,就无需处理,直接创建 HandlerMethod 对象
所以你会发现 HandlerMethod 处理器对象,就是handler(方法所在类)+method(方法对象)的组合,通过它能执行该方法
HandlerMethod 处理器
org.springframework.web.method.HandlerMethod,处理器对象,也就是某个方法的封装对象(Method+所在类的 Bean 对象),有以下属性:
public class HandlerMethod {/*** Bean 对象*/private final Object bean;@Nullableprivate final BeanFactory beanFactory;/*** Bean 的类型*/private final Class<?> beanType;/*** 方法对象*/private final Method method;/*** {@link #method} 的桥接方法* 存在泛型类型,编译器则会自动生成一个桥接方法(java1.5向后兼容)*/private final Method bridgedMethod;/*** 方法的参数类型数组*/private final MethodParameter[] parameters;/*** 响应的状态码,即 {@link ResponseStatus#code()}*/@Nullableprivate HttpStatus responseStatus;/*** 响应的状态码原因,即 {@link ResponseStatus#reason()}*/@Nullableprivate String responseStatusReason;/*** 解析自哪个 HandlerMethod 对象** 仅构造方法中传入 HandlerMethod 类型的参数适用,例如 {@link #HandlerMethod(HandlerMethod)}*/@Nullableprivate HandlerMethod resolvedFromHandlerMethod;/*** 父接口的方法的参数注解数组*/@Nullableprivate volatile List<Annotation[][]> interfaceParameterAnnotations;
}
根据上面的注释理解上面的属性,包含该方法的所有信息
它的构造函数非常多,不过原理都差不多,我们挑两个来看看
HandlerMethod(String beanName, BeanFactory beanFactory, Method method) 构造方法
对应 createHandlerMethod(Object handler, Method method)方法的 <1>,代码如下:
public HandlerMethod(String beanName, BeanFactory beanFactory, Method method) {Assert.hasText(beanName, "Bean name is required");Assert.notNull(beanFactory, "BeanFactory is required");Assert.notNull(method, "Method is required");// <1> 将 beanName 赋值给 bean 属性,说明 beanFactory + bean 的方式,获得 handler 对象this.bean = beanName;this.beanFactory = beanFactory;// <2> 初始化 beanType 属性Class<?> beanType = beanFactory.getType(beanName);if (beanType == null) {throw new IllegalStateException("Cannot resolve bean type for bean with name '" + beanName + "'");}this.beanType = ClassUtils.getUserClass(beanType);// <3> 初始化 method、bridgedMethod 属性this.method = method;// 如果不是桥接方法则直接为该方法this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);// <4> 初始化 parameters 属性,解析该方法(或者桥接方法)的参数类型this.parameters = initMethodParameters();// <5> 初始化 responseStatus、responseStatusReason 属性,通过 @ResponseStatus 注解evaluateResponseStatus();
}
将 beanName 赋值给 bean 属性,说明 beanFactory + bean 的方式,获得 handler 对象
初始化 beanType 属性
初始化 method、bridgedMethod 属性
初始化 parameters 属性,解析该方法(或者桥接方法)的参数类型,调用 initMethodParameters() 方法,如下:
private MethodParameter[] initMethodParameters() {int count = this.bridgedMethod.getParameterCount();// 创建 MethodParameter 数组MethodParameter[] result = new MethodParameter[count];// 遍历 bridgedMethod 方法的参数,逐个解析它的参数类型for (int i = 0; i < count; i++) {HandlerMethodParameter parameter = new HandlerMethodParameter(i);GenericTypeResolver.resolveParameterType(parameter, this.beanType);result[i] = parameter;}return result;
}
初始化 responseStatus、responseStatusReason 属性,通过 @ResponseStatus 注解
关于桥接方法呢,是 java1.5 引入泛型向后兼容的一种方法,具体可参考Effects of Type Erasure and Bridge Methods
HandlerMethod(Object bean, Method method) 构造方法
对应 createHandlerMethod(Object handler, Method method)方法的 <2>,代码如下:
public HandlerMethod(Object bean, Method method) {Assert.notNull(bean, "Bean is required");Assert.notNull(method, "Method is required");// <1> 初始化 Beanthis.bean = bean;this.beanFactory = null;// <2> 初始化 beanType 属性this.beanType = ClassUtils.getUserClass(bean);// <3> 初始化 method、bridgedMethod 属性this.method = method;// 如果不是桥接方法则直接为该方法this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);// <4> 初始化 parameters 属性,解析该方法(或者桥接方法)的参数类型this.parameters = initMethodParameters();// <5> 初始化 responseStatus、responseStatusReason 属性,通过 @ResponseStatus 注解evaluateResponseStatus();
}
和上面的构造方法差不多,不同的是这里的 bean 对象就是方法所在类的 Bean 对象
MappingRegistration 注册登记
AbstractHandlerMethodMapping 的私有静态内部类,Mapping 的注册登记信息,包含 Mapping、HandlerMethod、直接 URL 路径、Mapping 名称,代码如下:
private static class MappingRegistration<T> {/*** Mapping 对象*/private final T mapping;/*** HandlerMethod 对象*/private final HandlerMethod handlerMethod;/*** 直接 URL 数组(就是固定死的路径,而非多个)*/private final List<String> directUrls;/*** {@link #mapping} 的名字*/@Nullableprivate final String mappingName;public MappingRegistration(T mapping, HandlerMethod handlerMethod,@Nullable List<String> directUrls, @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.directUrls = (directUrls != null ? directUrls : Collections.emptyList());this.mappingName = mappingName;}
}
很简单,就是保存了 Mapping 注册时的一些信息
1.afterPropertiesSet 初始化方法
因为 AbstractHandlerMethodMapping 实现了 InitializingBean 接口,在 Sping 初始化该 Bean 的时候,会调用该方法,完成一些初始化工作,方法如下:
@Override
public void afterPropertiesSet() {// <x> 初始化处理器的方法们initHandlerMethods();
}protected void initHandlerMethods() {// <1> 遍历 Bean ,逐个处理for (String beanName : getCandidateBeanNames()) {// 排除目标代理类,AOP 相关,可查看注释if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {// <2> 处理 BeanprocessCandidateBean(beanName);}}// <3> 初始化处理器的方法们,目前是空方法,暂无具体的实现handlerMethodsInitialized(getHandlerMethods());
}
调用 getCandidateBeanNames() 方法,获取到 Spring 上下文中所有为 Object 类型的 Bean 的名称集合,然后进行遍历,方法如下
protected String[] getCandidateBeanNames() {// 获取上下文中所有的 Bean 的名称return (this.detectHandlerMethodsInAncestorContexts ?BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class): obtainApplicationContext().getBeanNamesForType(Object.class));
}
detectHandlerMethodsInAncestorContexts:是否只扫描可访问的 HandlerMethod 们,默认false
调用 processCandidateBean(String beanName) 方法,处理每个符合条件的 Bean 对象(例如有 @Controller 或者 @RequestMapping 注解的 Bean),解析这些 Bean 下面相应的方法,往 MappingRegistry 注册,详情见下文
调用 handlerMethodsInitialized(Map<T, HandlerMethod> handlerMethods) 方法,初始化 HandlerMethod 处理器们,空方法,暂无子类实现
2.processCandidateBean
processCandidateBean(String beanName)方法,处理符合条件的 Bean 对象,解析其相应的方法,往 MappingRegistry 注册,方法如下:
protected void processCandidateBean(String beanName) {// <1> 获得 Bean 对应的 Class 对象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.isTraceEnabled()) {logger.trace("Could not resolve type for bean '" + beanName + "'", ex);}}// <2> 判断 Bean 是否为处理器(例如有 @Controller 或者 @RequestMapping 注解)if (beanType != null && isHandler(beanType)) {// <3> 扫描处理器方法detectHandlerMethods(beanName);}
}
获得 Bean 对应的 Class 对象
调用 isHandler(Class<?> beanType) 抽象方法,判断 Bean 的类型是否需要处理,其 RequestMappingHandlerMapping 子类的实现:有 @Controller 或者 @RequestMapping 注解的 Bean
调用 detectHandlerMethods(Object handler) 方法,扫描 Bean 的方法进行处理
3.detectHandlerMethods
detectHandlerMethods(Object handler)方法,初始化 Bean 下面的方法们为 HandlerMethod 对象,并注册到 MappingRegistry 注册表中,代码如下:
protected void detectHandlerMethods(Object handler) {// <1> 获得 Bean 对应的 Class 对象Class<?> handlerType = (handler instanceof String ? obtainApplicationContext().getType((String) handler) : handler.getClass());if (handlerType != null) {// <2> 获得真实的 Class 对象,因为 `handlerType` 可能是代理类Class<?> userType = ClassUtils.getUserClass(handlerType);// <3> 获得匹配的方法和对应的 Mapping 对象Map<Method, T> methods = MethodIntrospector.selectMethods(userType,(MethodIntrospector.MetadataLookup<T>) method -> {try {// 创建该方法对应的 Mapping 对象,例如根据 @RequestMapping 注解创建 RequestMappingInfo 对象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));}// <4> 遍历方法,逐个注册 HandlerMethodmethods.forEach((method, mapping) -> {Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);registerHandlerMethod(handler, invocableMethod, mapping);});}
}
获得 Bean 对应的 Class 对象 handlerType
调用getUserClass(Class<?> clazz)方法,获得真实的 Class 对象,因为 handlerType 可能是代理类,如下:
public static Class<?> getUserClass(Class<?> clazz) {// 如果 Class 对象的名称包含 "$$",则是 CG_CLASS 代理类,则获取其父类if (clazz.getName().contains(CGLIB_CLASS_SEPARATOR)) {Class<?> superclass = clazz.getSuperclass();if (superclass != null && superclass != Object.class) {return superclass;}}return clazz;
}
获得匹配的方法和对应的 Mapping 对象,其中泛型 T,也就是 Mapping 对象,需要通过 getMappingForMethod(Method method, Class<?> handlerType) 抽象方法返回,其 RequestMappingHandlerMapping 子类的实现,根据 @RequestMapping 注解为方法创建 RequestMappingInfo 对象
所以这里的 Mapping 对象就是 RequestMappingInfo 对象
遍历方法(方法与 Mapping 一一映射了),调用registerHandlerMethod(Object handler, Method method, T mapping),逐个注册对应的 HandlerMethod 对象,如下:
protected void registerHandlerMethod(Object handler, Method method, T mapping) {this.mappingRegistry.register(mapping, handler, method);
}
也就是上面 MappingRegistry 的 register 方法,已经分析过了
到这里我们已经分析完了 AbstractHandlerMethodMapping 的初始化工作,部分细节在子类中实现
大致逻辑:扫描有 @Controller 或者 @RequestMapping 注解的类下面的方法,如果方法上面有 @RequestMapping 注解,则会为该方法创建对应的 RequestMappingInfo 对象
将所有的 RequestMappingInfo 对象和 Method 以及方法所在类,往 MappingRegistry 进行注册,会生成 HandlerMethod 处理器(Method 所有信息)对象
这样一来,当 Spring MVC 的 DispatcherServlet 处理请求的时候,获取到对应的 HandlerMethod 处理器,就可以通过反射执行对应的方法了
到这里,思路是不是越来越清晰了,我们继续往下分析
【重点】getHandlerInternal
由于上面初始化涉及到内容有点多,先回到本文上面的回顾这一小节,通过 AbstractHandlerMapping 的 getHandler(HttpServletRequest request) 方法获取 HandlerExecutionChain 处理器执行链时,需要调用 getHandlerInternal 抽象方法获取处理器,这个方法由子类去实现,就到这里了
getHandlerInternal(ServerWebExchange exchange)方法,获得请求对应的 HandlerMethod 处理器对象,方法如下:
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {// <1> 获得请求的路径String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);// <2> 获得读锁this.mappingRegistry.acquireReadLock();try {// <3> 获得 HandlerMethod 对象HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);// <4> 进一步,获得一个新的 HandlerMethod 对象return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);}finally {// <5> 释放读锁this.mappingRegistry.releaseReadLock();}
}
获得请求路径
获得读锁
调用 lookupHandlerMethod(ServerWebExchange exchange) 方法,获得请求对应的 HandlerMethod 处理器对象,详情见下文
如果获得到 HandlerMethod 对象,则调用 HandlerMethod#createWithResolvedBean() 方法,进一步,获得 HandlerMethod 对象,如下:
public HandlerMethod createWithResolvedBean() {Object handler = this.bean;// 如果是 bean 是 String 类型,则获取对应的 Bean,因为创建该对象时 bean 可能是对应的 beanNameif (this.bean instanceof String) {Assert.state(this.beanFactory != null, "Cannot resolve bean name without BeanFactory");String beanName = (String) this.bean;handler = this.beanFactory.getBean(beanName);}return new HandlerMethod(this, handler);
}
释放读锁
lookupHandlerMethod 获取处理器方法
lookupHandlerMethod(ServerWebExchange exchange)方法,获得请求对应的 HandlerMethod 处理器对象,方法如下:
@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {// <1> Match 数组,存储匹配上当前请求的结果(Mapping + HandlerMethod)List<Match> matches = new ArrayList<>();// <1.1> 优先,基于直接 URL (就是固定死的路径,而非多个)的 Mapping 们,进行匹配List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);if (directPathMatches != null) {addMatchingMappings(directPathMatches, matches, request);}// <1.2> 其次,扫描注册表的 Mapping 们,进行匹配if (matches.isEmpty()) {// No choice but to go through all mappings...addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);}// <2> 如果匹配到,则获取最佳匹配的 Match 结果的 `HandlerMethod`属性if (!matches.isEmpty()) {// <2.1> 创建 MatchComparator 对象,排序 matches 结果,排序器Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));matches.sort(comparator);// <2.2> 获得首个 Match 对象,也就是最匹配的Match bestMatch = matches.get(0);// <2.3> 处理存在多个 Match 对象的情况!!if (matches.size() > 1) {if (logger.isTraceEnabled()) {logger.trace(matches.size() + " matching mappings: " + matches);}if (CorsUtils.isPreFlightRequest(request)) {return PREFLIGHT_AMBIGUOUS_MATCH;}// 比较 bestMatch 和 secondBestMatch ,如果相等,说明有问题,抛出 IllegalStateException 异常// 因为,两个优先级一样高,说明无法判断谁更优先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);// <2.4> 处理首个 Match 对象handleMatch(bestMatch.mapping, lookupPath, request);// <2.5> 返回首个 Match 对象的 handlerMethod 属性return bestMatch.handlerMethod;}// <3> 如果匹配不到,则处理不匹配的情况else {return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);}
}
定义 Match 数组 matches,存储匹配上当前请求的结果(Mapping + HandlerMethod)
优先,基于直接 URL (就是固定死的路径,而非多个)的 Mapping 们,进行匹配
其次,扫描注册表的 Mapping 们,进行匹配
上述的1.1和1.2,都会调用addMatchingMappings(Collection<T> mappings, List<Match> matches, ServerWebExchange exchange) 方法
将当前请求和注册表中的 Mapping 进行匹配,匹配成功则生成匹配结果 Match,添加到 matches 中,方法如下:
private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {// 遍历 Mapping 数组for (T mapping : mappings) {// <1> 执行匹配,抽象方法,交由子类实现T match = getMatchingMapping(mapping, request);if (match != null) {// <2> 如果匹配,则创建 Match 对象,添加到 matches 中matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping)));}}
}
如果匹配到,则获取最佳匹配的 Match 结果的 HandlerMethod 属性
创建 MatchComparator 对象,排序 matches 的结果,排序器
获得首个 Match 结果,也就是最匹配的
处理存在多个 Match 的情况,则判断第二匹配的和最匹配的是否“相同”,是的话就抛出异常
处理最匹配的 Match,设置请求路径 lookupPath 到请求属性
返回最匹配的 Match 的 HandlerMethod 处理器对象
如果匹配不到,则处理不匹配的情况,调用handleNoMatch(Set<T> mappings, String lookupPath, HttpServletRequest request)方法,这里返回null
到这里 AbstractHandlerMethodMapping 抽象类差不多全部分析完了,其中有几个抽象方法交由子类去实现
protected abstract boolean isHandler(Class<?> beanType);
protected abstract T getMappingForMethod(Method method, Class<?> handlerType);
protected abstract Set<String> getMappingPathPatterns(T mapping);
protected abstract T getMatchingMapping(T mapping, HttpServletRequest request);
protected abstract Comparator<T> getMappingComparator(HttpServletRequest request);
RequestMappingInfoHandlerMapping
org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping,继承 AbstractHandlerMethodMapping 抽象类,定义了使用的泛型 <T> 为 org.springframework.web.servlet.mvc.method.RequestMappingInfo 类,即 Mapping 类型就是 RequestMappingInfo 对象
这样有什么好处呢?
RequestMappingInfoHandlerMapping 定义了使用 RequestMappingInfo 对象,而其子类 RequestMappingHandlerMapping 将使用了 @RequestMapping 注解的方法,解析生成 RequestMappingInfo 对象。这样,如果未来我们自己定义注解,或者其他方式来生成 RequestMappingHandlerMapping 对象,未尝不可。
构造方法
public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMethodMapping<RequestMappingInfo> {protected RequestMappingInfoHandlerMapping() {// 设置父类的 namingStrategy 属性 Mapping 命名策略对象,为 RequestMappingInfoHandlerMethodMappingNamingStrategy 对象setHandlerMethodMappingNamingStrategy(new RequestMappingInfoHandlerMethodMappingNamingStrategy());}
}
<T> 泛型,为 RequestMappingInfo 类型
设置父类 AbstractHandlerMethodMapping 的 namingStrategy 属性为 RequestMappingInfoHandlerMethodMappingNamingStrategy 对象
是否还记得这个为 Mapping 生成名称的类?在 AbstractHandlerMethodMapping 中进行分析过了
RequestMappingInfo 对象
org.springframework.web.servlet.mvc.method.RequestMappingInfo,实现 RequestCondition 接口,每个方法的定义的请求信息,也就是 @RequestMapping 等注解的信息
关于 org.springframework.web.servlet.mvc.condition.RequestCondition,条件接口,定义了三个方法,分别是:
combine(T other),合并方法
getMatchingCondition(HttpServletRequest request),匹配方法
compareTo(T other, HttpServletRequest request),比较方法
构造方法
public final class RequestMappingInfo implements RequestCondition<RequestMappingInfo> {/*** 名字*/@Nullableprivate final String name;/*** 请求路径的条件*/private final PatternsRequestCondition patternsCondition;/*** 请求方法的条件*/private final RequestMethodsRequestCondition methodsCondition;/*** 请求参数的条件*/private final ParamsRequestCondition paramsCondition;/*** 请求头的条件*/private final HeadersRequestCondition headersCondition;/*** 可消费的 Content-Type 的条件*/private final ConsumesRequestCondition consumesCondition;/*** 可生产的 Content-Type 的条件*/private final ProducesRequestCondition producesCondition;/*** 自定义的条件*/private final RequestConditionHolder customConditionHolder;
}
可以看到属性中有各种条件。实际上,和 @RequestMapping 注解是一一对应的。所以,每个属性的详细解释,相信你经常使用到
实际上,我们日常使用最多的还是 patternsCondition 请求路径条件,和 methodsCondition 请求方法条件
RequestCondition 接口体系结构如下:

getMatchingCondition
getMatchingCondition(HttpServletRequest request)方法,从当前 RequestMappingInfo 获得匹配的条件。如果匹配,则基于其匹配的条件,创建新的 RequestMappingInfo 对象,如果不匹配,则返回 null ,代码如下:
@Override
@Nullable
public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {// 匹配 methodsCondition、paramsCondition、headersCondition、consumesCondition、producesCondition// 如果任一为空,则返回 null ,表示匹配失败RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(request);if (methods == null) {return null;}ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(request);if (params == null) {return null;}HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(request);if (headers == null) {return null;}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;}/** 创建匹配的 RequestMappingInfo 对象* 为什么要创建 RequestMappingInfo 对象呢?** 因为当前 RequestMappingInfo 对象,一个 methodsCondition 可以配置 GET、POST、DELETE 等等条件,* 但是实际就匹配一个请求类型,此时 methods 只代表其匹配的那个。*/return new RequestMappingInfo(this.name, patterns,methods, params, headers, consumes, produces, custom.getCondition());
}
虽然代码非常长,实际都是调用每个属性对应的 getMatchingCondition(HttpServletRequest request) 方法,获得其匹配的真正的条件
可能你会疑惑,如果一个 @RequestMapping(value = "user/login") 注解,并未写 RequestMethod 的条件,岂不是会报空?
实际上不会。在这种情况下,会创建一个 RequestMethodsRequestCondition 对象,并且在匹配时,直接返回自身,代码如下:
@Override
@Nullable
public RequestMethodsRequestCondition getMatchingCondition(HttpServletRequest request) {if (CorsUtils.isPreFlightRequest(request)) {return matchPreFlight(request);}// 空的情况下,就返回自身if (getMethods().isEmpty()) {if (RequestMethod.OPTIONS.name().equals(request.getMethod()) &&!DispatcherType.ERROR.equals(request.getDispatcherType())) {return null; // No implicit match for OPTIONS (we handle it)}return this;}// 非空,逐个匹配return matchRequestMethod(request.getMethod());
}
也就是说,没有 RequestMethod 的条件,则一定匹配成功,且结果就是自身 RequestMethodsRequestCondition 对象
总结:就是根据配置的 @RequestMapping 注解,如果所有条件都满足,则创建一个 RequestMappingInfo 对象返回,如果某个条件不满足则直接返回 null,表示不匹配
compareTo
compareTo(RequestMappingInfo other, HttpServletRequest request)方法,比较优先级,方法如下:
@Override
public int compareTo(RequestMappingInfo other, HttpServletRequest request) {int result;// Automatic vs explicit HTTP HEAD mapping// 针对 HEAD 请求方法,特殊处理if (HttpMethod.HEAD.matches(request.getMethod())) {result = this.methodsCondition.compareTo(other.getMethodsCondition(), request);if (result != 0) {return result;}}/** 依次比较 patternsCondition、paramsCondition、headersCondition、consumesCondition、* producesCondition、methodsCondition、customConditionHolder* 如果有一个不相等,则直接返回比较结果*/result = this.patternsCondition.compareTo(other.getPatternsCondition(), request);if (result != 0) {return result;}result = this.paramsCondition.compareTo(other.getParamsCondition(), request);if (result != 0) {return result;}result = this.headersCondition.compareTo(other.getHeadersCondition(), request);if (result != 0) {return result;}result = this.consumesCondition.compareTo(other.getConsumesCondition(), request);if (result != 0) {return result;}result = this.producesCondition.compareTo(other.getProducesCondition(), request);if (result != 0) {return result;}// Implicit (no method) vs explicit HTTP method mappingsresult = this.methodsCondition.compareTo(other.getMethodsCondition(), request);if (result != 0) {return result;}result = this.customConditionHolder.compareTo(other.customConditionHolder, request);if (result != 0) {return result;}return 0;
}
关于各种 RequestCondition 请求条件就不一一分析了
getMappingPathPatterns
getMappingPathPatterns(RequestMappingInfo info)方法,获得 RequestMappingInfo 对应的请求路径集合,代码如下:
@Override
protected Set<String> getMappingPathPatterns(RequestMappingInfo info) {return info.getPatternsCondition().getPatterns();
}
在 MappingRegistry 注册表的 register 方法中的第 3 步会调用,将所有符合的请求路径与该 RequestMappingInfo 对象进行映射保存
getMatchingMapping
getMatchingMapping(RequestMappingInfo info, HttpServletRequest request) 方法,判断请求是否匹配入参 RequestMappingInfo 对象,代码如下:
@Override
protected RequestMappingInfo getMatchingMapping(RequestMappingInfo info, HttpServletRequest request) {return info.getMatchingCondition(request);
}
在 AbstractHandlerMethodMapping 的 lookupHandlerMethod 获取处理器方法的<1.1>和<1.2>会调用,遍历所有的 Mapping 对象,获取到该请求所匹配的 RequestMappingInfo 对象
handleMatch
handleMatch(RequestMappingInfo info, String lookupPath, HttpServletRequest request)方法,覆写父类的方法,设置更多的属性到请求中,代码如下:
@Override
protected void handleMatch(RequestMappingInfo info, String lookupPath, HttpServletRequest request) {super.handleMatch(info, lookupPath, request);// 获得 bestPattern 和 uriVariablesString bestPattern; // 最佳路径Map<String, String> uriVariables; // 路径上的变量集合Set<String> patterns = info.getPatternsCondition().getPatterns();if (patterns.isEmpty()) {bestPattern = lookupPath;uriVariables = Collections.emptyMap();}else {bestPattern = patterns.iterator().next();uriVariables = getPathMatcher().extractUriTemplateVariables(bestPattern, lookupPath);}request.setAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE, bestPattern);// 设置 MATRIX_VARIABLES_ATTRIBUTE 属性,到请求中if (isMatrixVariableContentAvailable()) {Map<String, MultiValueMap<String, String>> matrixVars = extractMatrixVariables(request, uriVariables);request.setAttribute(HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE, matrixVars);}// 设置 URI_TEMPLATE_VARIABLES_ATTRIBUTE 属性,到请求中Map<String, String> decodedUriVariables = getUrlPathHelper().decodePathVariables(request, uriVariables);request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, decodedUriVariables);// 设置 PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE 属性,到请求中if (!info.getProducesCondition().getProducibleMediaTypes().isEmpty()) {Set<MediaType> mediaTypes = info.getProducesCondition().getProducibleMediaTypes();request.setAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, mediaTypes);}
}
handleNoMatch
handleNoMatch(Set<RequestMappingInfo> infos, String lookupPath, HttpServletRequest request) 方法,覆写父类方法,处理无匹配 Mapping 的情况
主要用途是,给出为什么找不到 Mapping 的原因,代码如下:
@Override
protected HandlerMethod handleNoMatch(Set<RequestMappingInfo> infos, String lookupPath, HttpServletRequest request) throws ServletException {// <1> 创建 PartialMatchHelper 对象,解析可能的错误PartialMatchHelper helper = new PartialMatchHelper(infos, request);if (helper.isEmpty()) {return null;}// <2> 方法错误if (helper.hasMethodsMismatch()) {Set<String> methods = helper.getAllowedMethods();if (HttpMethod.OPTIONS.matches(request.getMethod())) {HttpOptionsHandler handler = new HttpOptionsHandler(methods);return new HandlerMethod(handler, HTTP_OPTIONS_HANDLE_METHOD);}throw new HttpRequestMethodNotSupportedException(request.getMethod(), methods);}// <3> 可消费的 Content-Type 错误if (helper.hasConsumesMismatch()) {Set<MediaType> mediaTypes = helper.getConsumableMediaTypes();MediaType contentType = null;if (StringUtils.hasLength(request.getContentType())) {try {contentType = MediaType.parseMediaType(request.getContentType());}catch (InvalidMediaTypeException ex) {throw new HttpMediaTypeNotSupportedException(ex.getMessage());}}throw new HttpMediaTypeNotSupportedException(contentType, new ArrayList<>(mediaTypes));}// <4> 可生产的 Content-Type 错误if (helper.hasProducesMismatch()) {Set<MediaType> mediaTypes = helper.getProducibleMediaTypes();throw new HttpMediaTypeNotAcceptableException(new ArrayList<>(mediaTypes));}// <5> 参数错误if (helper.hasParamsMismatch()) {List<String[]> conditions = helper.getParamConditions();throw new UnsatisfiedServletRequestParameterException(conditions, request.getParameterMap());}return null;
}
核心代码在 PartialMatchHelper 中实现,暂时忽略
方法错误。这是一个非常常见的错误,例如说 POST user/login 存在,但是我们请求了 GET user/login
可消费的 Content-Type 错误
可生产的 Content-Type 错误
参数错误
RequestMappingHandlerMapping
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,实现 MatchableHandlerMapping、EmbeddedValueResolverAware 接口,继承 RequestMappingInfoHandlerMapping 抽象类,基于@RequestMapping 注解来构建 RequestMappingInfo 对象
写到这里有那么一点点感动,终于到最底层的实现类了
public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappingimplements MatchableHandlerMapping, EmbeddedValueResolverAware {private boolean useSuffixPatternMatch = true;private boolean useRegisteredSuffixPatternMatch = false;private boolean useTrailingSlashMatch = true;private Map<String, Predicate<Class<?>>> pathPrefixes = new LinkedHashMap<>();private ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager();@Nullableprivate StringValueResolver embeddedValueResolver;/*** RequestMappingInfo 的构建器*/private RequestMappingInfo.BuilderConfiguration config = new RequestMappingInfo.BuilderConfiguration();
}
afterPropertiesSet
因为父类 AbstractHandlerMethodMapping 实现了 InitializingBean 接口,在 Sping 初始化该 Bean 的时候,会调用该方法,完成一些初始化工作,方法如下:
@Override
public void afterPropertiesSet() {// 构建 RequestMappingInfo.BuilderConfiguration 对象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();
}
isHandler
是否还记得 AbstractHandlerMethodMapping 的这个抽象方法?在它的 processCandidateBean 方法中,扫描 Spring 中所有 Bean 时会调用,判断是否需要扫描这个 Bean 中的方法,方法如下:
@Override
protected boolean isHandler(Class<?> beanType) {// 判断是否有 @Controller 或者 @RequestMapping 的注解return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
有 @Controller 或者 @RequestMapping 的注解的类才需要进行扫描,是不是很熟悉
getMappingForMethod
是否还记得 AbstractHandlerMethodMapping 的这个抽象方法?在它的 detectHandlerMethods 方法中,用于获取 Method 方法对应的 Mapping 对象,方法如下:
@Override
@Nullable
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {// <1> 基于方法上的 @RequestMapping 注解,创建 RequestMappingInfo 对象RequestMappingInfo info = createRequestMappingInfo(method);if (info != null) {// <2> 基于类上的 @RequestMapping 注解,合并进去RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);if (typeInfo != null) {info = typeInfo.combine(info);}// <3> 如果有前缀,则设置到 info 中String prefix = getPathPrefix(handlerType);if (prefix != null) {info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);}}return info;
}
1、调用 createRequestMappingInfo(AnnotatedElement element) 方法,基于方法上的 @RequestMapping 注解,创建 RequestMappingInfo 对象
@Nullable
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {// <1> 获得 @RequestMapping 注解RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);// <2> 获得自定义的条件。目前都是空方法,可以无视RequestCondition<?> condition = (element instanceof Class ? getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));// <3> 基于 @RequestMapping 注解,创建 RequestMappingInfo 对象return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}protected RequestMappingInfo createRequestMappingInfo(RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) {// 创建 RequestMappingInfo.Builder 对象,设置对应属性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);}// 创建 RequestMappingInfo 对象return builder.options(this.config).build();
}
2、基于类上的 @RequestMapping 注解,合并进去
3、如果有前缀,则设置到 info 中
match
match(HttpServletRequest request, String pattern) 方法,执行匹配,代码如下:
@Override
public RequestMatchResult match(HttpServletRequest request, String pattern) {// <1> 为 `pattern` 创建一个 RequestMappingInfo 对象RequestMappingInfo info = RequestMappingInfo.paths(pattern).options(this.config).build();// <2> 获得请求对应的 RequestMappingInfo 对象RequestMappingInfo matchingInfo = info.getMatchingCondition(request);if (matchingInfo == null) { // <3> 没有匹配的 RequestMappingInfo 对象返回空return null;}// <4> 获得请求匹配到的路径Set<String> patterns = matchingInfo.getPatternsCondition().getPatterns();// <5> 获取请求路径String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);// <6> 创建 RequestMatchResult 结果return new RequestMatchResult(patterns.iterator().next(), lookupPath, getPathMatcher());
}
总结
在 Spring MVC 处理请求的过程中,需要通过 HandlerMapping 组件会为请求找到合适的 HandlerExecutionChain 处理器执行链,包含处理器(handler)和拦截器们(interceptors),该组件体系结构如下:

本文就红色框中的内容进行了分析,基于 Method 进行匹配。例如,我们所熟知的 @RequestMapping 等注解的方式
在将红色框中的类注入到 Spring 上下文时,会进行一些初始化工作,扫描 @Controller 或者 @RequestMapping 注解标注的 Bean 对象,会将带有 @RequestMapping 注解(包括其子注解)解析成 RequestMappingInfo 对象。接下来,会将 RequestMappingInfo、该方法对象、该方法所在类对象 往 MappingRegistry 注册表进行注册,其中会生成 HandlerMethod 处理器(方法的所有信息)对象保存起来。当处理某个请求时,HandlerMapping 找到该请求对应的 HandlerMethod 处理器对象后,就可以通过反射调用相应的方法了
这部分内容包含了我们常用到 @Controller 和 @RequestMapping 注解,算是 HandlerMapping 组件的核心内容,看完之后有种茅塞顿开的感觉
回到以前的 Servlet 时代,我们需要编写许多的 Servlet 去处理请求,然后在 web.xml 中进行配置,而 Spring MVC 让你通过只要在类和方法上面添加 @Controller 或者 @RequestMapping 注解这种方式,就可以处理请求,因为所有的请求都交给了 DispatcherServlet 去处理。这样是不是简化了你的工作量,让你专注于业务开发。
相关文章:

Spring MVC 源码 - HandlerMapping 组件(三)之 AbstractHandlerMethodMapping
HandlerMapping 组件HandlerMapping 组件,请求的处理器匹配器,负责为请求找到合适的 HandlerExecutionChain 处理器执行链,包含处理器(handler)和拦截器们(interceptors)handler 处理器是 Objec…...

超店有数,为什么商家要使用tiktok达人进行营销推广呢?
近几年互联网发展萌生出更多的短视频平台,而tittok这个平台在海外也越来越火爆。与此同时,很多商家也开始用tiktok进行营销推广。商家使用较多的方式就是达人营销,这种方法很常见且转化效果不错。那为什么现在这么多商家喜欢用tiktok达人进行…...

【分享】订阅万里牛集简云连接器同步企业采购审批至万里牛系统
方案场景 面临着数字化转型的到来,不少公司希望实现业务自动化需求,公司内部将钉钉作为办公系统,万里牛作为ERP系统,两个系统之前的数据都储存在各自的后台,导致数据割裂,数据互不相通,人工手动…...

C++类和对象_02----对象模型和this指针
目录C对象模型和this指针1、成员变量和成员函数分开存储1.1、空类大小1.2、非空类大小1.3、结论2、this指针概念2.1、解决名称冲突2.2、在类的非静态成员函数中返回对象本身,可使用return *this2.3、拷贝构造函数返回值为引用的时候,可进行链式编程3、空…...
瑞芯微RK3568开发:烧录过程
进入rk3568这款芯片的烧录模式共有3种方式,先讲需要准备的环境要求。 一、软硬件环境 1、配套sdk版本的驱动DriverAssitant_vx.x.x和RKDevTool_Release_vx.x,版本不对应可能无法烧录,建议直接在sdk压缩包里获取; 2、如果正确安…...

【数据结构】——树和二叉树的概念
目录 1.树概念及结构 1.1树的概念 1.2 树的相关性质 1.3 树的表示 1.4 树在实际中的运用(表示文件系统的目录树结构) 2.二叉树概念及结构 2.1二叉树概念 2.2 特殊的二叉树 2.3 二叉树的性质 1.树概念及结构 1.1树的概念 树是一种非线性的数据结构…...

Meta分析在生态环境领域里的应用
Meta分析(Meta Analysis)是当今比较流行的综合具有同一主题的多个独立研究的统计学方法,是较高一级逻辑形式上的定量文献综述。20世纪90年代后,Meta分析被引入生态环境领域的研究,并得到高度的重视和长足的发展&#x…...

PrivateLoader PPI服务发现RisePro恶意软件窃取分发信息
称为PrivateLoader的按安装付费(PPI)软件下载器服务正用于恶意软件RisePro的信息窃取。Flashpoint 于 2022 年 12月13日发现了新的窃取者,此前发现了在名为Russian Market的非法网络犯罪市场上使用该恶意软件泄露的“几组日志”。RisePro是一…...
SQL93 返回购买 prod_id 为 BR01 的产品的所有顾客的电子邮件(一)
描述你想知道订购 BR01 产品的日期,有表OrderItems代表订单商品信息表,prod_id为产品id;Orders表代表订单表有cust_id代表顾客id和订单日期order_date;Customers表含有cust_email 顾客邮件和cust_id顾客idOrderItems表prod_idorde…...

Git ---- 概述
Git ---- 概述1. 何为版本控制2. 为什么需要版本控制3. 版本控制的工具集中式版本控制工具分布式版本控制工具4. Git 简史5. Git 工作机制6. Git 和代码托管中心Git 是一个免费的、开源的分布式版本控制系统,可以快速高效地处理从小型到大型的各种项目。 Git 易于学…...

用 tensorflow.js 做了一个动漫分类的功能(二)
前言:前面已经通过采集拿到了图片,并且也手动对图片做了标注。接下来就要通过 Tensorflow.js 基于 mobileNet 训练模型,最后就可以实现在采集中对图片进行自动分类了。这种功能在应用场景里就比较多了,比如图标素材站点࿰…...

小林coding
一、图解网络 问大家,为什么要有TCP/Ip网络模型? 对于同一台设备上的进程通信,有很多种方式,比如有管道、消息队列、共享内存、信号等方式,对于不同设备上的进程通信,就需要有网络通信,而设备是…...

操作系统真相还原_第6章:完善内核
文章目录6.1 函数调用约定简介6.2 汇编语言和C语言混合编程汇编调用CC调用汇编6.3 实现打印函数流程程序编译并写入硬盘执行6.4 内联汇编简介汇编语言AT&T语法基本内联汇编扩展内联汇编6.1 函数调用约定简介 调用约定: calling conventions 调用函数时的一套约…...

SmoothNLP新词发现算法的改进实现
SmoothNLP新词发现算法的改进实现 背景介绍 新词发现也叫未登录词提取,依据 《统计自然语言处理》(宗成庆),中文分词有98%的错误来自"未登录词"。即便早就火遍大江南北的Bert也不能解决"未登录词"的Encoding问题,便索性…...

实时渲染为什么快,能不能局域网部署点量云
提到渲染很多有相关从业经验的人员可能会想起,自己曾经在电脑上渲染一个模型半天或者更长的 时间才能完成的经历。尤其是在项目比较着急的时候,这种煎熬更是难受。但现在随着实时渲染和云渲染行业的发展,通过很多方式可以提升渲染的时间和效率…...
网络游戏该如何防护ddos/cc攻击
现在做网络游戏的企业都知道服务器的安全对于我们来说很重要!互联网上面的 DDoS 攻击和 CC 攻击等等无处不在,而游戏服务器对服务器的防御能力和处理能力要求更高,普通的服务器则是比较注重各方面能力的均衡。随着游戏行业的壮大,…...
项目管理体系1-4练习题1-10答案
题目1 每周一次的项目会议上,一位团队成员表示在修订一项可交付成果时,一名销售经理对客户服务过程想出一项变更讨论,影响到整个项目,项目经理对销售参与到项目可交付成果感到吃惊,经理事先应该怎么做去阻止这些情况&…...

sHMIctrl智能屏幕使用记录
手上有个案子,“按压机器人”,功能是恒定一个力按下一定时间。 屏幕选型使用“sHMIctrl”,一下记录使用过程中遇到的问题以及解决方法。 目录 问题1:按键控件做定时触发,模拟运行时触发不了。 问题2:厂家…...

2.20 crm day01 配置路由router less使用 axios二次封装
需求: 目录 1.配置路由 2.less使用 vue2使用以下版本 3.axios二次封装 1.配置路由 1.1.1 官方链接:安装 | Vue Router npm i vue-router3.6.5 注意:vue2项目不能用vue-router四版本以上 1.2.1.创建router/index.js 在该文件中 //1.引…...

【LeetCode】剑指 Offer 10- I. 斐波那契数列 p74 -- Java Version
题目链接: 1. 题目介绍() 写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。斐波那契数列的定义如下: F(0) 0, F(1) 1F(N) F(N - 1) F…...

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型
摘要 拍照搜题系统采用“三层管道(多模态 OCR → 语义检索 → 答案渲染)、两级检索(倒排 BM25 向量 HNSW)并以大语言模型兜底”的整体框架: 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后,分别用…...

使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...

Zustand 状态管理库:极简而强大的解决方案
Zustand 是一个轻量级、快速和可扩展的状态管理库,特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...

AI Agent与Agentic AI:原理、应用、挑战与未来展望
文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例:使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例:使用OpenAI GPT-3进…...

PL0语法,分析器实现!
简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...

【分享】推荐一些办公小工具
1、PDF 在线转换 https://smallpdf.com/cn/pdf-tools 推荐理由:大部分的转换软件需要收费,要么功能不齐全,而开会员又用不了几次浪费钱,借用别人的又不安全。 这个网站它不需要登录或下载安装。而且提供的免费功能就能满足日常…...

【JVM面试篇】高频八股汇总——类加载和类加载器
目录 1. 讲一下类加载过程? 2. Java创建对象的过程? 3. 对象的生命周期? 4. 类加载器有哪些? 5. 双亲委派模型的作用(好处)? 6. 讲一下类的加载和双亲委派原则? 7. 双亲委派模…...
【Nginx】使用 Nginx+Lua 实现基于 IP 的访问频率限制
使用 NginxLua 实现基于 IP 的访问频率限制 在高并发场景下,限制某个 IP 的访问频率是非常重要的,可以有效防止恶意攻击或错误配置导致的服务宕机。以下是一个详细的实现方案,使用 Nginx 和 Lua 脚本结合 Redis 来实现基于 IP 的访问频率限制…...

并发编程 - go版
1.并发编程基础概念 进程和线程 A. 进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位。B. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。C.一个进程可以创建和撤销多个线程;同一个进程中…...

【java面试】微服务篇
【java面试】微服务篇 一、总体框架二、Springcloud(一)Springcloud五大组件(二)服务注册和发现1、Eureka2、Nacos (三)负载均衡1、Ribbon负载均衡流程2、Ribbon负载均衡策略3、自定义负载均衡策略4、总结 …...