您的位置 首页 java

Java互联网架构-spring mvc 原理深度解析

欢迎关注头条号:java小马哥

周一至周日早九点半!下午三点半!精品技术文章准时送上!!!

精品学习资料获取通道,参见文末

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

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

封面图源网络,侵权删除)

私信头条号,发送:“资料”,获取更多“秘制” 精品学习资料

如有收获,请帮忙转发,您的鼓励是作者最大的动力,谢谢!

一大波微服务、分布式、高并发、高可用的原创系列文章正在路上,

欢迎关注头条号:java小马哥

周一至周日早九点半!下午三点半!精品技术文章准时送上!!!

十余年BAT架构经验倾囊相授

文章来源:智云一二三科技

文章标题:Java互联网架构-spring mvc 原理深度解析

文章地址:https://www.zhihuclub.com/186526.shtml

关于作者: 智云科技

热门文章

网站地图