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对象并返回对应的视图给客户端.
在容器初始化时会建立所有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的源代码。
其一,ApplicationContext初始化时建立所有url和Controller类的对应关系(用Map保存);
其二,根据请求url找到对应的Controller,并从Controller中找到处理请求的方法;
其三,request参数绑定到方法的形参,执行方法处理请求,并返回结果视图.
第一步、建立Map
我们首先看第一个步骤,也就是建立Map
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
第三步、反射调用处理请求的方法,返回结果视图
上面的方法中,第2步其实就是从第一个步骤中的Map
这一部分的核心就在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的工作原理和源码进行了分析,在这个过程发现了几个优化点:
1.Controller如果能保持单例,尽量使用单例,这样可以减少创建对象和回收对象的开销.也就是说,如果Controller的类变量和实例变量可以以方法形参声明的尽量以方法的形参声明,不要以类变量和实例变量声明,这样可以避免线程安全问题.
2.处理Request的方法中的形参务必加上@RequestParam注解,这样可以避免SpringMVC使用asm框架读取class文件获取方法参数名的过程.即便SpringMVC对读取出的方法参数名进行了缓存,如果不要读取class文件当然是更加好.
3.阅读源码的过程中,发现SpringMVC并没有对处理url的方法进行缓存,也就是说每次都要根据请求url去匹配Controller中的方法url,如果把url和Method的关系缓存起来,会不会带来性能上的提升呢?有点恶心的是,负责解析url和Method对应关系的ServletHandlerMethodResolver是一个private的内部类,不能直接继承该类增强代码,必须要该代码后重新编译.当然,如果缓存起来,必须要考虑缓存的线程安全问题。