spring mvc 原理深度解析(一)

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的形式做的视图解析

最后附上一张整体的流程图

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容