Spring5源码分析系列(八)SpringMVC设计原理及实现

SpringMVC请求处理流程引用SpringinAction上的一张图来讲明了SpringMVC的核心组件和请求处理流程:前端

①:DispatcherServlet是SpringMVC中的前端控制器(FrontController),负责接收Request并将Request转发给对应的处理组件.设计模式

②:HanlerMapping是SpringMVC中完成url到Controller映射的组件.DispatcherServlet接收Request,而后从HandlerMapping查找处理Request的Controller.缓存

③:Cntroller处理Request,并返回ModelAndView对象,Controller是SpringMVC中负责处理Request的组件(相似于Struts2中的Action),ModelAndView是封装结果视图的组件.安全

④⑤⑥:视图解析器解析ModelAndView对象并返回对应的视图给客户端.app

SpringMVC的工做机制

在容器初始化时会创建全部url和Controller的对应关系,保存到Map<url,Controller>中.Tomcat启动时会通知Spring初始化容器(加载Bean的定义信息和初始化全部单例Bean),而后SpringMVC会遍历容器中的Bean,获取每个Controller中的全部方法访问的url,而后将url和Controller保存到一个Map中;框架

这样就能够根据Request快速定位到Controller,由于最终处理Request的是Controller中的方法,Map中只保留了url和Controller中的对应关系,因此要根据Request的url进一步确认Controller中的Method,这一步工做的原理就是拼接Controller的url(Controller上@RequestMapping的值)和方法的url(Method上@RequestMapping的值),与request的url进行匹配,找到匹配的那个方法;源码分析

肯定处理请求的Method后,接下来的任务就是参数绑定,把Request中参数绑定到方法的形式参数上,这一步是整个请求处理过程当中最复杂的一个步骤。SpringMVC提供了两种Request参数与方法形参的绑定方法:性能

①经过注解进行绑定,@RequestParam优化

②经过参数名称进行绑定.url

使用注解进行绑定,咱们只要在方法参数前面声明@RequestParam("a"),就能够将Request中参数a的值绑定到方法的该参数上.使用参数名称进行绑定的前提是必需要获取方法中参数的名称,Java反射只提供了获取方法的参数的类型,并无提供获取参数名称的方法.SpringMVC解决这个问题的方法是用asm框架读取字节码文件,来获取方法的参数名称.asm框架是一个字节码操做框架,关于asm更多介绍能够参考它的官网。我的建议,使用注解来完成参数绑定,这样就能够省去asm框架的读取字节码的操做。

SpringMVC源码分析

咱们根据工做机制中三部分来分析SpringMVC的源代码。

其一,ApplicationContext初始化时创建全部url和Controller类的对应关系(用Map保存);

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

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

第一步、创建Map<urls,Controller>的关系

咱们首先看第一个步骤,也就是创建Map<url,Controller>关系的部分.第一部分的入口类为ApplicationObjectSupport的setApplicationContext方法.setApplicationContext方法中核心部分就是初始化容器initApplicationContext(context),子类AbstractDetectingUrlHandlerMapping实现了该方法,因此咱们直接看子类中的初始化容器方法。

determineUrlsForHandler(StringbeanName)方法的做用是获取每一个Controller中的url,不一样的子类有不一样的实现,这是一个典型的模板设计模式.由于开发中咱们用的最多的就是用注解来配置Controller中的url,BeanNameUrlHandlerMapping是AbstractDetectingUrlHandlerMapping的子类,处理注解形式的url映射.因此咱们这里以BeanNameUrlHandlerMapping来进行分析.咱们看BeanNameUrlHandlerMapping是如何查beanName上全部映射的url.

到这里HandlerMapping组件就已经创建全部url和Controller的对应关系。

第二步、根据访问url找到对应的Controller中处理请求的方法

下面咱们开始分析第二个步骤,第二个步骤是由请求触发的,因此入口为DispatcherServlet的核心方法为doService(),doService()中的核心逻辑由doDispatch()实现,咱们查看doDispatch()的源代码.

getHandler(processedRequest)方法实际上就是从HandlerMapping中找到url和Controller的对应关系.这也就是第一个步骤:创建Map<url,Controller>的意义.咱们知道,最终处理Request的是Controller中的方法,咱们如今只是知道了Controller,还要进一步确认Controller中处理Request的方法.因为下面的步骤和第三个步骤关系更加紧密,直接转到第三个步骤.

第三步、反射调用处理请求的方法,返回结果视图

上面的方法中,第2步其实就是从第一个步骤中的Map<urls,beanName>中取得Controller,而后通过拦截器的预处理方法,到最核心的部分--第5步调用Controller的方法处理请求.在第2步中咱们能够知道处理Request的Controller,第5步就是要根据url肯定Controller中处理请求的方法,而后经过反射获取该方法上的注解和参数,解析方法和参数上的注解,最后反射调用方法获取ModelAndView结果视图。由于上面采用注解url形式说明的.第5步调用的就是RequestMappingHandlerAdapter的handle()中的核心逻辑由handleInternal(request,response,handler)实现。

这一部分的核心就在2和4了.先看第2步,经过Request找Controller的处理方法.实际上就是拼接Controller的url和方法的url,与Request的url进行匹配,找到匹配的方法.

经过上面的代码,已经能够找处处理Request的Controller中的方法了,如今看如何解析该方法上的参数,并调用该方法。也就是执行方法这一步。执行方法这一步最重要的就是获取方法的参数,而后咱们就能够反射调用方法了。

invocableMethod.invokeAndHandle最终要实现的目的就是:完成Request中的参数和方法参数上数据的绑定。

SpringMVC中提供两种Request参数到方法中参数的绑定方式:

①经过注解进行绑定,@RequestParam

②经过参数名称进行绑定.

使用注解进行绑定,咱们只要在方法参数前面声明@RequestParam("a"),就能够将request中参数a的值绑定到方法的该参数上.使用参数名称进行绑定的前提是必需要获取方法中参数的名称,Java反射只提供了获取方法的参数的类型,并无提供获取参数名称的方法.SpringMVC解决这个问题的方法是用asm框架读取字节码文件,来获取方法的参数名称.asm框架是一个字节码操做框架,关于asm更多介绍能够参考它的官网.我的建议,使用注解来完成参数绑定,这样就能够省去asm框架的读取字节码的操做.

关于asm框架获取方法参数的部分,这里就再也不进行分析了.感兴趣的话本身去就能看到这个过程.

到这里,方法的参数值列表也获取到了,就能够直接进行方法的调用了.整个请求过程当中最复杂的一步就是在这里了.ok,到这里整个请求处理过程的关键步骤都分析完了.理解了SpringMVC中的请求处理流程,整个代码仍是比较清晰的.

谈谈SpringMVC的优化

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

1.Controller若是能保持单例,尽可能使用单例,这样能够减小建立对象和回收对象的开销.也就是说,若是Controller的类变量和实例变量能够以方法形参声明的尽可能以方法的形参声明,不要以类变量和实例变量声明,这样能够避免线程安全问题.

2.处理Request的方法中的形参务必加上@RequestParam注解,这样能够避免SpringMVC使用asm框架读取class文件获取方法参数名的过程.即使SpringMVC对读取出的方法参数名进行了缓存,若是不要读取class文件固然是更加好.

3.阅读源码的过程当中,发现SpringMVC并无对处理url的方法进行缓存,也就是说每次都要根据请求url去匹配Controller中的方法url,若是把url和Method的关系缓存起来,会不会带来性能上的提高呢?有点恶心的是,负责解析url和Method对应关系的ServletHandlerMethodResolver是一个private的内部类,不能直接继承该类加强代码,必需要该代码后从新编译.固然,若是缓存起来,必需要考虑缓存的线程安全问题。