2021-05-27 23:43  阅读(227)
文章分类:Spring 源码解析 文章标签:SpringSpring 源码
©  原文作者:Java学习部落 原文地址:https://www.zhihu.com/people/tobetopjavaer/posts

首先祝贺大家能看到这儿,说明都是人才,然后想说的就是,这一章过后,可能就会停止晕车了,但是这是在前面的文章都认真阅读理解的前提上,相信大家肯定有很大的收获,这也是我坚持下来的最大动力。

进入正题

先上一张图 【Spring 源码解析图】【高清图可以关注我找我要哦】

202105272343177721.png

接下来 进入SpringMVC源码分析

根据上一篇文章分析的 Spring MVC 工作机制,我们将从三个部分来分析 Spring MVC 的源代码。

其一,ApplicationContext 初始化时用 Map 保存所有 url 和 Controller 类的对应关系;

其二,根据请求 url 找到对应的 Controller,并从 Controller 中找到处理请求的方法;

其三,Request 参数绑定到方法的形参,执行方法处理请求,并返回结果视图。

初始化阶段

我们首先找到 DispatcherServlet 这个类,必然是寻找 init()方法。

然后,我们发现其 init 方法其实在父类 HttpServletBean 中,其源码如下:

202105272343179923.png202105272343183855.png

我 们 看 到 在 这 段 代 码 中 , 又 调 用 了 一 个 重 要 的 initServletBean() 方 法 。

进 入 initServletBean()方法看到以下源码:

202105272343184877.png

这段代码中最主要的逻辑就是初始化 IOC 容器,最终会调用 refresh()方法,前面的章节中对 IOC 容器的初始化细节我们已经详细掌握,在此我们不再赘述。

我们看到上面的代码中,IOC 容器初始化之后,最后有调用了 onRefresh()方法。这个方法最终是在DisptcherServlet 中实现,来看源码:

202105272343186949.png

到这一步就完成了Spring MVC的九大组件的初始化。

接下来,我们来看url和Controller的 关 系 是 如 何 建 立 的 呢 ?

HandlerMapping的子 类AbstractDetectingUrlHandlerMapping 实现了 initApplicationContext()方法,所以我们直接看子类中的初始化容器方法。

2021052723431925811.png2021052723431932013.png

determineUrlsForHandler(String beanName)方法的作用是获取每个 Controller 中的url,不同的子类有不同的实现,这是一个典型的模板设计模式。因为开发中我们用的最多的就是 用 注解 来 配 置 Controller 中 的 url ,BeanNameUrlHandlerMapping 是 AbstractDetectingUrlHandlerMapping 的子类,处理注解形式的 url 映射。

所以我们这里以BeanNameUrlHandlerMapping来进行分析。

我们看BeanNameUrlHandlerMapping 是如何查 beanName 上所有映射的 url。

2021052723431964115.png

到这里 HandlerMapping 组件就已经建立所有 url 和 Controller 的对应关系。

运行调用阶段

这一步步是由请求触发的,所以入口为 DispatcherServlet 的核心方法为 doService(), doService()中的核心逻辑由 doDispatch()实现,源代码如下:

2021052723432001217.png2021052723432384819.png2021052723432431721.png

getHandler(processedRequest)方法实际上就是从 HandlerMapping 中找到 url 和 Controller 的对应关系。也就是 Map<url,Controller>。

我们知道,最终处理 Request 的是 Controller 中的方法,我们现在只是知道了 Controller,我们如何确认 Controller中处理 Request 的方法呢?继续往下看。

从 Map<urls,beanName>中取得 Controller 后,经过拦截器的预处理方法,再通过反射获取该方法上的注解和参数,解析方法和参数上的注解,然后反射调用方法获取ModelAndView 结果视图。

最后,调用的就是 RequestMappingHandlerAdapter 的handle()中的核心逻辑由 handleInternal(request, response, handler)实现。

2021052723432489823.png

整个处理过程中最核心的逻辑其实就是拼接 Controller 的 url 和方法的 url,与 Request 的 url 进行匹配,找到匹配的方法。

2021052723432526225.png

通过上面的代码分析,已经可以找到处理 Request 的 Controller 中的方法了,现在看如何解析该方法上的参数,并反射调用该方法。

2021052723432577027.png2021052723432614629.png

invocableMethod.invokeAndHandle()最终要实现的目的就是:完成 Request 中的参数和方法参数上数据的绑定。Spring MVC 中提供两种 Request 参数到方法中参数的绑定方式:

1、通过注解进行绑定,@RequestParam。

2、通过参数名称进行绑定。

使用注解进行绑定,我们只要在方法参数前面声明@RequestParam("name"),就可以将 request 中参数 name 的值绑定到方法的该参数上。

使用参数名称进行绑定的前提是必须要获取方法中参数的名称,Java 反射只提供了获取方法的参数的类型,并没有提供获取参数名称的方法。

SpringMVC 解决这个问题的方法是用 asm 框架读取字节码文件,来获取方法的参数名称。

asm 框架是一个字节码操作框架,关于 asm 更多介绍可以参考其官网。个人建议,使用注解来完成参数绑定,这样就可以省去 asm 框架的读取字节码的操作。

2021052723432673331.png2021052723432713733.png

关于 asm 框架获取方法参数的部分,这里就不再进行分析了。

感兴趣的小伙伴可以继续深入了解这个处理过程。

到这里,方法的参数值列表也获取到了,就可以直接进行方法的调用了。

整个请求过程中最复杂的一步就是在这里了。

到这里整个请求处理过程的关键步骤都已了解。理解了Spring MVC 中的请求处理流程,整个代码还是比较清晰的。最后我们再来梳理一下Spring MVC 核心组件的关联关系(如下图):

2021052723432762635.png

最后上SpringMVC的时序图(需要高清大图依然可以找我

2021052723432797137.png

Spring MVC 使用优化建议

上面我们已经对 SpringMVC 的工作原理和源码进行了分析,在这个过程发现了几个优化点:

1、Controller 如果能保持单例,尽量使用单例

这样可以减少创建对象和回收对象的开销。也就是说,如果 Controller 的类变量和实例变量可以以方法形参声明的尽量以方法的形参声明,不要以类变量和实例变量声明,这样可以避免线程安全问题。

2、处理 Request 的方法中的形参务必加上@RequestParam 注解

这样可以避免 Spring MVC 使用 asm 框架读取 class 文件获取方法参数名的过程。

即便 Spring MVC 对读取出的方法参数名进行了缓存,如果不要读取 class 文件当然是更好。

3、缓存 URL

阅读源码的过程中,我们发现 Spring MVC 并没有对处理 url 的方法进行缓存,也就是说每次都要根据请求 url 去匹配 Controller 中的方法 url,如果把 url 和 Method 的关系缓存起来,会不会带来性能上的提升呢?

有点恶心的是,负责解析 url 和 Method 对应关系的 ServletHandlerMethodResolver 是一个 private 的内部类,不能直接继承该类增强代码,必须要该代码后重新编译。当然,如果缓存起来,必须要考虑缓存的线程安全问题。

点赞(0)
版权归原创作者所有,任何形式转载请联系作者; Java 技术驿站 >> 《Spring源码解析(十一)》从源码深处体验Spring核心技术--SpringMVC源码解析
上一篇
《Spring源码解析(十)》从源码深处体验Spring核心技术--SpringMVC流程和九大组件
下一篇
《Spring源码解析(十二)》深入理解Spring事务原理,告别面试一问三不知的尴尬