1、回顾servlet 与jsp 执行过程
2、Spring MVC请求处理流程
3,mvc 体系结构详解
URL映射
表单参数映射
调用目标Control
数据模型映射
视图解析
异常处理
DispatcherServlet
DispatcherServlet它的作用有点儿类似于网关,DispatcherServlet负责将请求转发到不同的Controller上去。
配置DispatcherServlet
/ 后面不能写成/*否则容易出问题。
编写Controller,继承controller类。实现handlerRequest接口。指定ModelAndView。这是一种方式。
第二种方式 继承 HttpRequestHandler类。那么DispatcherServlet是如何去匹配不同的controller的呢?这就需要了解SpringMVC的体系结构。整个体系结构都搞懂才行。下面就从一个全局的视角去看Spring MVC的体系结构。
SpringMVC的体系结构图
DispatcherServlet是通过HandlerMapping的一组映射关系去找到我们的目标Controller
通过上面我们知道Controller存在的形式是多种多样的。可以通过Controller接口的形式,也可以通过@Controller注解的形式存在。等等等。。。有这么多不同的Controller形式DispatcherServlet是如何去执行的呢?这里面就涉及到一个HandlerAdapter控制器适配器。找到数据模型的一个映射。然后试图解析是通过ViewResolver这个东东。他支持各种各样的试图解析。view用于具体的试图解析。HandlerExceptionResolver异常拦截解析器。
下面就围绕这张图去逐一进行源码分析。
一,HandlerMapping源码分析
HandlerMapping是一个接口,他有两个实现类,MatchableHandlerMapping和AbstractHandlerMapping。HandlerMapping在getHandler里面并没有返回handler(我们的目标执行器,也就是我们的controller)而是返回一个执行链条。HandlerExecutionChain。
根据下图可知HandlerExecutionChain里面有一个getHandler方法,这里面返回了Handler。但是具体的是哪个一个Handler是不确定的,因为他是Object类型的。所以通过动态代理的方式是不能实现的。只能通过这个执行链条去返回目标Handler。后续的文章我们会对执行链条做深入的讲解。
然后看上面的类继承关系图。左面继承了AbstractUrlHandlerMapping其中AbstractDetectingUrlHandlerMapping起到自动发现的作用,他是根据Bean的名称,而且是必须以/开头。比如这样
<bean name="/hello.do" class="com.tuling.control.SimpleControl"/>
然后SimpleUrlHandlerMapping。如果不需要自动发现功能,则使用这个。SimpleUrlHandlerMapping是自己配置URL和controller之间的一个关系。所以他们之间的区别就是一个自动发现,一个手动配置。AbstractHandlerMethodMapping是对方法进行映射RequestMappingInfoHandlerMapping这个大家比较熟悉了,就是通过@RequestMapping进行映射的。如果配置了SimpleUrlHandlerMapping或者BeanNameUrlHandlerMapping那么默认的就会失效。SimpleUrlHandlerMapping和BeanNameUrlHandlerMapping可以同时配置,都可以映射到我们的Controller控制器。
SimpleUrlHandlerMapping的初始化执行流程
对initApplicationContext()方法进行调用。其中super.initApplicationContext();这里是初始化一些拦截器。比如自动发现拦截器啊等等等。this.registerHandlers(this.urlMap);开始进行Url的匹配点进去看一下可知如果urlMap不为空才进行匹配。第一步是!url.startsWith("/")如果不是/开头的。就会把你的URL加上/然后获取Handler,就是我们的控制器。然后判断handler是不是String类型的,。如果是进行去除空格的处理。然后执行this.registerHandler(url, handler);点进去发现,将handler转化为Object类型。然后判断是否是懒加载并且是String类型。然后将Handler转化成String的字符串。然后获取一个ApplicationContext对象。判断handler是都是单例。如果是通过ApplicationContext.getBean(handlerName)获取到一个Object对象。然后获取handlerMap,判断是否是空,如果不为空说明你配置了两个name属性此时会抛出异常。接着判断你的url是否等于/如果是设置一个根目录的Handler通过localhost:8080/即可访问我们的handler。然后判断是否等于/*如果是就设置一个默认的和Handler。也就是说你永远都不会担心找不到这个url的异常。
最后如果以上情况都不是,则通过this.handlerMap.put(urlPath, resolvedHandler);将url和handler绑定在一起。如果说handler是懒加载那么此时map中存储的value就是beanName。这就是SimpleUrlHandlerMapping的初始化流程。
SimpleUrlHandlerMapping的访问执行流程
我们知道最终执行调用的一定是DispatcherServlet。所以我们从这里开始入手。找到getHandler
首先通过Iterator var2 =this.handlerMappings.iterator();或取到所有的Mapping然后遍历。遍历一次取出一个handlerMapping然后调用handlerMapping。点进去看看。第一行getHandlerInternal这个方法很重要。点进去看看,在这个方法里第一行通过getUrlPathHelper().getLookupPathForRequest(request);去解析我们地址栏上的URL。比如或取到的是/hello.do。第二行通过Object handler = lookupHandler(lookupPath, request);去查找对应的handler。点进去发现就是从上面put进去的handlerMap找到对应的handler。在最后return一个buildPathExposingHandler。点进去发现HandlerExecutionChain这里面配置了一个拦截器链,那么返回的并不是我们最终的handler。
那么getHandlerInternal方法的第二行也执行完了。最终拿到得是一个拦截器链,如果说拦截器链为空则判断第一行的url解析器拿到得/hello.do是否等于/如果是Object rawHandler=getRootHandler()设置为根节点的handler,然后判断rawHandler是否等于空,如果是则调用默认的handler。如果不等于空呢,则直接通过beanName从Spring容器找到Handler。 再调用buildPathExposingHandler。还是获取一个拦截器链。最终,将拦截器链返回。getHandlerInternal方法执行结束。返回到getHandler方法里。那么如果说,没有获取到拦截器链,则获取默认的handler。如果不为空,则直接通过beanName从容器中获取到handler。然后,去创建一个拦截器链条,又加入了一个拦截器。最后进一步获取handler。然后返回HandlerExecutionChain。最后通过HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());获取到的才是我们的handler。SimpleUrlHandlerMapping执行流程到此介绍完毕,说实话SpringMVC的代码写的跟IOC\aop差着一大块儿的水平感觉。
Controller 接口:
HttpRequestHandler 接口:
HttpServlet 接口:
@RequestMapping方法注解
可以看出 Handler 没有统一的接口,当dispatchServlet获取当对应的Handler之后如何调用呢?调用其哪个方法?这里有两种解决办法,一是用instanceof 判断Handler 类型然后调用相关方法 。二是通过引入适配器实现,每个适配器实现对指定Handler的调用。spring 采用后者。
HandlerAdapter详解
HandlerAdapter中有三个接口
1supports接口,主要的作用是传入一个handler看看是否可以被执行。如果可以返回true。
2handle处理接口,处理完返回一个ModelAndView。
3getLastModified,用作缓存处理,获取最后一次修改时间。
我们知道HandlerAdapter对应适配了4种handler关系图如下。
举个例子,如果说我们写了一个Servlet,然后继承HttpServlet此时如果不配置SimpleServletHandlerAdapter适配器,那么就会报错,报500的异常。但是如果配置了这个适配器,那么SpringMVC给我们配置的默认适配器SimpleControllerHandlerAdapter就会失效。所以需要手动的配置一下。下面看一下源码即可验证这个说法。上面讲到返回一个拦截器链条。然后下面的代码就是获取适配器如下图。
点进getHandlerAdapter方法。这个方法里做了一个do-while循环。通过support方法判断是否是可用的适配器。
在下图的方法里判断是否是我们配置的适配器,如果是则返回true。
拿到适配器以后就开始了真正的调用。
此时适配器找到了以后就开始真正的调用handler也就是我们servlet或者是controller。然后通过一个叫ViewResolver的接口类去解析。其中有一个接口叫resolveViewName。他返回一个View。然后View里面有一个render接口。通过里面的model进行处理,封装HTML。通过response进行写入。所以它是先有ViewResolver才有的View。
然后看看View有哪些实现类如下图。
第一个AbstractTemplateViewResolver里面有以下两种视图支持,比如我们常用的FreeMarkerViewReslover。
第二个BeanNameViewResolver可以通过这种方式实现自定的视图解析器,生明一个自定义View类。然后实现View接口。在render里面去做返回。然后controller里面通过ModelAndView视图解析器的构造器去加载我们的自定义View类型。
第三个就是InternalResourceViewResolver。这个就是我们最常用的,通过配置前缀,后缀等信息去实现视图解析。
下图是通过BeanName的形式做的视图解析
最后附上一张整体的流程图