从源码看RequestMappingHandlerMapping的注册与发现
1.问题的产生
日常开发中,大多数的API层中@Controller注解和@RequestMapping注解都会被使用在其中,但是为什么标注了@Controller和@RequestMapping注解之后,从外部访问你设置的请求路径,springmvc就能捕获到请求并将请求交给指定的类和方法来执行呢?这其实就是RequestMappingHandlerMapping的核心功能了,注册&发现。
2.RequestMappingHandlerMapping之注册
@Controller和@RequestMapping是如何生效的呢,@Controller与其他的@Service,@Component又有什么区别呢,那么就来看下RequestMappingHandlerMapping是如何工作的。
RequestMappingHandlerMapping会在Spring容器初始化时实例化Bean注入到Spring容器中,RequestMappingHandlerMapping继承于RequestMappingInfoHandlerMapping,
RequestMappingInfoHandlerMapping又继承自AbstractHandlerMethodMapping,AbstractHandlerMethodMapping又实现了InitializingBean接口,InitializingBean接口我们都知道,
在Bean的生命周期中,在实例化Bean对象后,开始注入bean依赖的属性,如果实现了BeanNameAware接口,那么紧接着会执行该接口的setBeanName(String name)进行beanName的回调,
之后如果实现了InitializingBean接口则会调用afterPropertiesSet()方法进行一些其他属性的设置。
public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping
implements MatchableHandlerMapping, EmbeddedValueResolverAware {}
public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMethodMapping<RequestMappingInfo> {
}
public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {}
public interface InitializingBean {
void afterPropertiesSet() throws Exception;
}
核心就在AbstractHandlerMethodMapping实现的afterPropertiesSet()中,可以看到afterPropertiesSet()中调用了initHandlerMethods()方法
@Override
public void afterPropertiesSet() {
initHandlerMethods();
}
protected void initHandlerMethods() {
//遍历spring容器中所有beanName
for (String beanName : getCandidateBeanNames()) {
//如果beanName不是以scopedTarget.开头则对该bean进行处理
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
processCandidateBean(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}
protected void processCandidateBean(String beanName) {
Class<?> beanType = null;
try {
//获取该bean的类型
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);
}
}
//如果类型!=null&&类型属于handler则进行检测
if (beanType != null && isHandler(beanType)) {
detectHandlerMethods(beanName);
}
}
//RequestMappingHandlerMapping实现的isHandler方法
@Override
protected boolean isHandler(Class<?> beanType) {
//判断该类上是否有@Controller注解或者@RequestMapping注解
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
//检测handler的方法
protected void detectHandlerMethods(Object handler) {
//获取bean类型
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass());
if (handlerType != null) {
//获取该类的用户自定义类,该ClassUtils是Spring提供的,通常情况下会返回该类本身的class,如果是cglib代理类则会去返回被代理类本身的class
Class<?> userType = ClassUtils.getUserClass(handlerType);
//将该类的每个方法和对应的url路径(类上@RequestMapping的信息 + 方法上的@RequestMapping的信息进行拼装)封装到map中
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.isTraceEnabled()) {
logger.trace(formatMappings(userType, methods));
}
//遍历这个map,将所有方法和对应的mapping信息进行注册
methods.forEach((method, mapping) -> {
//通过AopUtils处理获取到反射可以调用的方法
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
//将该方法以HandlerMethod的形式进行注册,在SpringMvc的视角下,controller相当于handler,下属方法相当于handlerMethod
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}
getMappingForMethod(),通过方法获取详细的Mapping信息(类+方法)
@Override
@Nullable
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
//先获取方法上@RequestMapping的路径
RequestMappingInfo info = createRequestMappingInfo(method);
if (info != null) {
//获取类上的@RequestMapping的路径
RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
//如果类上存在@RequestMapping则将二者合并,所有路径由类的在前,后面拼接方法上的
if (typeInfo != null) {
info = typeInfo.combine(info);
}
String prefix = getPathPrefix(handlerType);
if (prefix != null) {
info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
}
}
return info;
}
selectInvocableMethod()通过反射获取实际调用的方法
public static Method selectInvocableMethod(Method method, @Nullable Class<?> targetType) {
if (targetType == null) {
return method;
} else {
//通过反射获取到方法
Method methodToUse = MethodIntrospector.selectInvocableMethod(method, targetType);
//判断是否为私有方法/非静态方法/SpringProxy的子类,是则抛出异常,这里有个疑问,反射应该是不能调用静态方法,这里不知道为什么不限制静态方法?
if (Modifier.isPrivate(methodToUse.getModifiers()) && !Modifier.isStatic(methodToUse.getModifiers()) && SpringProxy.class.isAssignableFrom(targetType)) {
throw new IllegalStateException(String.format("Need to invoke method '%s' found on proxy for target class '%s' but cannot be delegated to target bean. Switch its visibility to package or protected.", method.getName(), method.getDeclaringClass().getSimpleName()));
} else {
return methodToUse;
}
}
}
将方法以handlerMethod的形式进行注册
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
this.mappingRegistry.register(mapping, handler, method);
} public void register(T mapping, Object handler, Method method) {
public void register(T mapping, Object handler, Method method) {
//开启写锁,防止多线程注册多个相同的handleMehtod
this.readWriteLock.writeLock().lock();
try {
//构造handlerMethod
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
//校验该handlerMehtod是否可以注册进来
validateMethodMapping(handlerMethod, mapping);
//获取该方法的直接路径
Set<String> directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping);
//将该方法的所有直接路径添加mappingRegister中的一个容器pathLookup,主要用来储存直接路径和mapping信息的关系
for (String path : directPaths) {
this.pathLookup.add(path, mapping);
}
String name = null;
if (getNamingStrategy() != null) {
//获取该方法的名称,如果@RequestMapping上的name有值则取该name,没有则根据类的类型名转换成大写的后+'#'+方法的名称 拼接而成
name = getNamingStrategy().getName(handlerMethod, mapping);
//将该handlerMehod添加进mappingRegister中的一个容器nameLookup,用来存存储name和handleMethod的关系
addMappingName(name, handlerMethod);
}
//跨越相关配置
CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
if (corsConfig != null) {
corsConfig.validateAllowCredentials();
this.corsLookup.put(handlerMethod, corsConfig);
}
//将handleMehod注册进容器
this.registry.put(mapping,
new MappingRegistration<>(mapping, handlerMethod, directPaths, name, corsConfig != null));
}
finally {
this.readWriteLock.writeLock().unlock();
}
}
validateMethodMapping校验handlerMethod是否合法
private void validateMethodMapping(HandlerMethod handlerMethod, T mapping) {
// Assert that the supplied mapping is unique.
//根据mapping路径获取HandlerMethod
HandlerMethod existingHandlerMethod = this.mappingLookup.get(mapping);
//如果存在并且跟当前的HandlerMethod并且不是同一个则抛错
if (existingHandlerMethod != null && !existingHandlerMethod.equals(handlerMethod)) {
throw new IllegalStateException(
"Ambiguous mapping. Cannot map '" + handlerMethod.getBean() + "' method \n" +
handlerMethod + "\nto " + mapping + ": There is already '" +
existingHandlerMethod.getBean() + "' bean method\n" + existingHandlerMethod + " mapped.");
}
}
3.RequestMappingHandlerMapping之发现
通过上面部分的源码我们大概知道了@Controller和@RequestMapping注解是如何生效的,RequestMappingHandlerMapping是如何去处理他们的,那我们说完了注册,就该来说发现,当客户端的请求发送到后台服务器时,SpringMvc是如何根据请求url给他找到指定的方法上呢?我们都知道SpringMVC处理请求的核心在于DisPatcherServlet,也就是所谓的前端控制器,DispatchServlet会拿到请求进行解析分发,那具体是如何操作的,来看下图:

图片来自百度,这张图大家应该都很熟悉了,DispatcherServlet的工作流程,可以看到DispatcherServlet在接收到客户端发送的request时会将请求委托给HandlerMapping去处理,HandlerMapping将根据url寻找一个合适的handler返回给DispatcherServlet,RequestMappingHandlerMapping实现自HandlerMapping接口,他是SpringMVC的众多HandlerMapping中的一个,它们都在Spring容器初始化时一同创建并注入到容器中。而我们的RequestMappingHandlerMapping的发现功能就在此时派上了用场。
当请求进来时,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);
// Determine handler for the current request.
// 可以看到在此时就要通过handlerMapping去获取HandlerExecutionChain执行链,其中包括了handler和对应的拦截器Inteceptor等等。
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
getHandler方法如下:
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//如果handlerMapping集合不为null,则遍历每个handlerMapping看是否可以找到对应的handler
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
这里我们主要来看RequestMappingHandlerMapping实现的getHandler()方法,查看源码发现RequestMappingHandlerMapping并没有重写该方法,而是由其祖宗类AbstractHandlerMapping重写了该方法
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//获取handler又是通过getHandlerInternal()方法
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
// Ensure presence of cached lookupPath for interceptors and others
if (!ServletRequestPathUtils.hasCachedPath(request)) {
initLookupPath(request);
}
//根据handler和request获取执行链
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
if (logger.isTraceEnabled()) {
logger.trace("Mapped to " + handler);
}
else if (logger.isDebugEnabled() && !DispatcherType.ASYNC.equals(request.getDispatcherType())) {
logger.debug("Mapped to " + executionChain.getHandler());
}
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;
}
getHandlerInternal(request)方法又是由AbstractHandlerMapping的子类AbstractHandlerMethodMapping重写的,
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = initLookupPath(request);
//对mappingRegister加读锁,防止读取的时候被篡改数据
this.mappingRegistry.acquireReadLock();
try {
//根据request获取handlerMehtod,即找到了实际要执行的方法
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
//释放读锁
this.mappingRegistry.releaseReadLock();
}
}
4. 其他的一些补充
在阅读源码中我们也是在不断的在学习,其中的一些方法可能也是第一次见,这里也做解析让自己学习一下
ClassUtils.getUserClass(Class<?> clazz)方法我们在源码中看到有使用来根据calss获取用户自定义类,注意该ClassUtils为Spring包下的
public static Class<?> getUserClass(Class<?> clazz) {
//判断类名称是否包含cglib的分隔符 CGLIB_CLASS_SEPARATOR = "$$";
if (clazz.getName().contains(CGLIB_CLASS_SEPARATOR)) {
//是则取获取当前cglib类的父类,因为cglib代理类是增强类,并不是被代理类本身
Class<?> superclass = clazz.getSuperclass();
if (superclass != null && superclass != Object.class) {
return superclass;
}
}
return clazz;
}

可以看到这里cglib代理类的的名称是拼接而成,其中是带着"$$"符号
5. 总结
关于RequestMappingHandlerMapping的源码解析暂时先到这了,之后可能会随时更改其中的内容,因为本人目前能力有限,文章中可能存在一些错误的地方,希望大家可以指出一起学习探讨。
从源码看RequestMappingHandlerMapping的注册与发现的更多相关文章
- 从微信小程序开发者工具源码看实现原理(一)- - 小程序架构设计
使用微信小程序开发已经很长时间了,对小程序开发已经相当熟练了:但是作为一名对技术有追求的前端开发,仅仅熟练掌握小程序的开发感觉还是不够的,我们应该更进一步的去理解其背后实现的原理以及对应的考量,这可能 ...
- 从微信小程序开发者工具源码看实现原理(四)- - 自适应布局
从前面从微信小程序开发者工具源码看实现原理(一)- - 小程序架构设计可以知道,小程序大部分是通过web技术进行渲染的,也就是最终通过浏览器的dom tree + cssom来生成渲染树:既然最终是通 ...
- .NET Core 3.0之深入源码理解Startup的注册及运行
原文:.NET Core 3.0之深入源码理解Startup的注册及运行 写在前面 开发.NET Core应用,直接映入眼帘的就是Startup类和Program类,它们是.NET Core应用程 ...
- 从linux源码看epoll
从linux源码看epoll 前言 在linux的高性能网络编程中,绕不开的就是epoll.和select.poll等系统调用相比,epoll在需要监视大量文件描述符并且其中只有少数活跃的时候,表现出 ...
- 从源码看Azkaban作业流下发过程
上一篇零散地罗列了看源码时记录的一些类的信息,这篇完整介绍一个作业流在Azkaban中的执行过程,希望可以帮助刚刚接手Azkaban相关工作的开发.测试. 一.Azkaban简介 Azkaban作为开 ...
- 解密随机数生成器(二)——从java源码看线性同余算法
Random Java中的Random类生成的是伪随机数,使用的是48-bit的种子,然后调用一个linear congruential formula线性同余方程(Donald Knuth的编程艺术 ...
- 从源码看Android中sqlite是怎么通过cursorwindow读DB的
更多内容在这里查看 https://ahangchen.gitbooks.io/windy-afternoon/content/ 执行query 执行SQLiteDatabase类中query系列函数 ...
- 从源码看Android中sqlite是怎么读DB的(转)
执行query 执行SQLiteDatabase类中query系列函数时,只会构造查询信息,不会执行查询. (query的源码追踪路径) 执行move(里面的fillwindow是真正打开文件句柄并分 ...
- 从Chrome源码看浏览器的事件机制
.aligncenter { clear: both; display: block; margin-left: auto; margin-right: auto } .crayon-line spa ...
随机推荐
- poj 3041 Asteroids(最小点覆盖)
题意: N*N的矩阵,有K个敌人,坐标分别是(C1,C1),.....,(Rk,Ck). 有一个武器,每发射一次,可消掉某行或某列上的所有的敌人. 问消灭所有敌人最少需要多少发. 思路: 二分建图:左 ...
- Centos7 升级过内核 boot分区无法挂载修
参考连接:https://www.cnblogs.com/heqiuyong/p/11186301.html 故障图 挂载系统盘,光盘启动,急救模式, chroot /mnt/sysimage 报错 ...
- CSS 脉冲和火箭动画特效
CSS脉冲和火箭动画特效 <!DOCTYPE html> <html lang="en"> <head> <meta charset=
- C++中简单使用HP-Socket
目录 简介 使用方式 实现简单线程池 实现TCP客户端 实现TCP服务端 实现Http客户端 附件 简介 HP-Socket 是一套通用的高性能 TCP/UDP /HTTP 通信 框架 ,包含服务端组 ...
- Kafka面试题总结
1.Kafka 都有哪些特点? 高吞吐量.低延迟:kafka每秒可以处理几十万条消息,它的延迟最低只有几毫秒,每个topic可以分多个partition, consumer group 对partit ...
- super和this
super注意点: 1.super调用父类的构造方法,必须在构造方法的第一个 2.super必须只能出现在子类的方法或者构造方法中 3.super和this不能同时调用构造方法 this: 代表的对象 ...
- 如何用命令行编译c++程序
作为程序员,如果仅仅只懂得如何在IDE上拖控件写程序,而不知道如何直接通过编译器编译程序的话.虽然说也没啥大不了的,但是如果掌握了手动编译的技能,那肯定会是一种炫技般的存在.从客观的角度来讲,一方面, ...
- selet 语句详解
SELECT 语句的基本格式为: SELECT 要查询的列名 FROM 表名字 WHERE 限制条件; 2.0 数学符号条件 SELECT 语句常常会 ...
- [loj3342]制作菜品
当$n-1\le m$,不妨令$d_{1}\le d_{2}\le...\le d_{n}$,则$(n-1)k\le mk=\sum_{i=1}^{n}d_{i}\le d_{1}+(n-1)d_{n ...
- Java设计模式之(九)——门面模式
1.什么是门面模式? Provide a unified interface to a set of interfaces in a subsystem.Facade defines a higher ...