用一张图分析MVC设计原理:
①: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<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
②通过参数名称进行绑定 ----
使用注解进行绑定,我们只要在方法参数前面声明@RequestParam("a"),就可以将Request中参数a的值绑定到方法的该参数上.使用参数名称进行绑定的前提是必须要获取方法中参数的名称,Java反射只提供了获取方法的参数的类型,并没有提供获取参数名称的方法.SpringMVC解决这个问题的方法是用asm框架读取字节码文件,来获取方法的参数名称.asm框架是一个字节码操作框架,关于asm更多介绍可以参考它的官网。个人建议,使用注解来完成参数绑定,这样就可以省去asm框架的读取字节码的操作。
源码分析
①、建立Map<urls,Controller>关系映射
子类AbstractDetectingUrlHandlerMapping实现了该方法setApplicationContext,determineUrlsForHandler(StringbeanName)方法实现对应bean的url映射,采用判断是否有“/”前缀的bean,然后加入集合中。
②、根据访问url找到对应的Controller中处理请求的方法
该步骤是建立在请求上的,所以会走前端控制器的doservice方法,具体实现是doDispatch()。先检查是否为文件上传请求在获取对应的hander处理器链,对request进行适配处理,通过调用gethandler(request)去第一步里面找映射关系,通过url找到对应的bean,但是现在还没找到对应方法。
③、反射调用处理请求的方法,返回结果视图
这个部分用于处理获取对应方法,使用java反射获取方法上的注解及注解属性,通过字符串拼接将bean对应的url与方法注解属性url拼接去对比request请求url,找到对应的方法后进行参数注入请求处理,之后将处理完毕的数据给视图解析器,给对应模板渲染视图。
注意,我们在写controller代码的时候当request请求带参数需要绑定到方法形参时,一般我们会选择注解@requestParam绑定,有人会问我不写该注解也有用,写该注解不使用value好像也行得通。那是因为MVC框架还支持一种参数绑定,因为在使用到反射的时候只提供了获取方法的参数的类型,并没有提供获取参数名称的方法,这个时候要用到asm框架去获取字节码文件然后再去获取参数名称。所以开发时这个注解参数绑定要写,可以省去大量读字节码的时间。