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

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

Spring5源码分析系列(八)SpringMVC设计原理及实现_第1张图片

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

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

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

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

SpringMVC的工作机制

在容器初始化时会建立所有url和Controller的对应关系,保存到Map.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

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

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

SpringMVC源码分析

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

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

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

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

第一步、建立Map的关系

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

Spring5源码分析系列(八)SpringMVC设计原理及实现_第2张图片

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

Spring5源码分析系列(八)SpringMVC设计原理及实现_第3张图片

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

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

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

Spring5源码分析系列(八)SpringMVC设计原理及实现_第4张图片

Spring5源码分析系列(八)SpringMVC设计原理及实现_第5张图片

Spring5源码分析系列(八)SpringMVC设计原理及实现_第6张图片

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

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

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

Spring5源码分析系列(八)SpringMVC设计原理及实现_第7张图片

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

Spring5源码分析系列(八)SpringMVC设计原理及实现_第8张图片

通过上面的代码,已经可以找到处理Request的Controller中的方法了,现在看如何解析该方法上的参数,并调用该方法。也就是执行方法这一步。执行方法这一步最重要的就是获取方法的参数,然后我们就可以反射调用方法了。

Spring5源码分析系列(八)SpringMVC设计原理及实现_第9张图片invocableMethod.invokeAndHandle最终要实现的目的就是:完成Request中的参数和方法参数上数据的绑定。

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

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

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

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

Spring5源码分析系列(八)SpringMVC设计原理及实现_第10张图片

关于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的内部类,不能直接继承该类增强代码,必须要该代码后重新编译.当然,如果缓存起来,必须要考虑缓存的线程安全问题。

你可能感兴趣的:(java开发,Spring)