浅谈SpringMVC源码的DispatcherServlet组件执行流程





一、基础概念

在阅读该篇博客前,你一定一定要了解前端控制器的作用以及对应的处理流程。

如下图:

用户前台发起请求,请求发送给前端控制器(DispatcherServlet),前端控制器会获取对应的处理器映射器(HandlerMapping),然后返回给前端控制器;前端控制器根据返回的处理器映射器,选择处理器适配器(HandlerAdapter)完成对应的功能逻辑后,再次返回到前端控制器;最后再调用视图解析器(ViewResolver),进行相关的处理。最后的最后再进行视图渲染,最终返回给我们的前台用户。(我相信在看这篇博客的小伙伴对于这个概念肯定是烂熟于心了。本文主要内容就是带你跟你一下相关细节的源码,即SpringMVC的DispatcherServlet组件的源码)


1、初始化变量数据

建议在看该篇博客之前,可以简单浏览一下我之前的博客:浅谈SpringMVC源码之SpringServletContainerInitializer的完整加载流程。如果你不看,很有可能你就不知道我们的数据是在什么地方加载以及获取的,你只能根据方法的名字去猜测它的功能是什么。

需要用到前文的知识点为:SpringMVC会在容器启动阶段,初始化对应的数据变量


这里就会初始化我们后面需要用到的最终要的三个map集合:处理器映射器、处理器适配器、视图解析器



2、urlLookup集合

除了上面说的那几个变量,你还需要了解这两个集合。

urlLookup作用:用来存放键值对,映射url(key)-RequestMappingInfo对象(value)。RequestMappingInfo可以理解为为我们添加的@RequestMapping注解的信息。

补充:RequestMappingInfo对象内部维护的各种xxxCondition变量,对应为@RequestMapping注解上我们能够自己配置的参数


AbstractHandlerMethodMapping类实现了InitializingBean接口,那么我么就可以联想到Spring中Bean的生命周期的初始化步骤,即执行afterPropertiesSet方法。

我们可以发现这个类是抽象类,在Spring中抽象类是无法实例化为Bean的,所以该抽象类有具体的实现,在将子类实例化为Bean的时候,调用对应的初始化步骤,完成数据的填充。


对应方法的调用逻辑,如下图,




3、策略设计模式

这个设计模式在SpringMVC中十分常见,我认为SpringMVC的大的骨架都是基于策略设计模式进行设计的。


特别是三个核心组件,全部是基于策略模式进行配置。


不熟悉策略模式的小伙伴,可以就把策略模式理解为switch case。当为1,使用A方法,当为2,使用B方法。使用策略模式,可以让我们的代码整理上看起来更加的更加的美观和优雅,更符合代码设计的开闭原则。

下面是我自己搭建的一个小Demo,仅供参考:

1)定义一个顶层接口

2)编写三个不同的策略实现类(对应不同的数据类型)

3)测试类

这就是一个策略模式的简单小实现,根据我么传入参数的不同类型,调用不同的业务实现类。




4、前期的基本流程

DispatcherServlet类的继承体系图示

顺着Servlet的doGet方法的调用逻辑,这里的代码使用的是模板方法设计模式。


以Get请求为例,当我们发起一次请求后

1、首先会根据请求的类型选择调用不同的方法,此处会调用doGet


2、doGet方法在FrameworkServlet抽象类中给出了实现,会调用当前类的processRequest方法


3、该类会处理一些基本的数据信息,然后又调用子类的doService方法(doService方法是一个模板方法)


4、此时就会调用到DispatcherServlet类中的doService方法,然后调用当前类的doDispatch方法






二、doDispatch

我将该方法中需要执行的逻辑分为三类:文件相关、拦截器相关、主体流程相关。



1、文件相关

开始

选择合适的文件解析器资源,完成对应的解析操作

1)选择使用何种策略,满足条件返回true

2)根据返回的策略,调用具体的resolveMultipart方法,完成处理逻辑



回想SpringMVC阶段,我们是如何配置文件下载的?是不是就是配置这么一个Bean,换句话说,是不是就是指定对应的解析器?



结束

清除生成的临时文件




2、拦截器

让我们先来观察对应的拦截器接口,是不是像极了Bean的生命周期的处理方法?



前置拦截

如果方法返回false,则直接结束该方法,后面的逻辑就不再执行。回想前置处理器的作用是不是就是这样?


方法内部调用逻辑:

1)获取对应处理数组(数据在构建处理器映射器的时候添加,后文会提及)

2)遍历执行

3)只要有一个拦截器返回为false,就就执行对应的处理完成后拦截,也就是afterCompletion方法。最终返回false,然后方法调用结束



后置拦截

这里的处理逻辑大同小异,就不再赘述






三、getHandler(重点)

为方便记忆理解,我将我们的处理器映射器对象的处理方法称为handlerMethod对象(父类是HandlerMethod类型),但是其实它是一个Object类(子类是Object类型),即这种说话在某种程度上来说不准确,但是却方便记忆。


1、HandlerMapping

1)handlerMappings肯定不为空

2)遍历对应的mappings集合

3)根据当前的request请求,调用对应的getHandler方法

4)如果返回的结果不为null,则说明是与我们当前请求匹配的handlerMapping


所以对应的获取逻辑在最后面得hm.gethandler(request)方法中

该代码片段来自AbstractHandlerMapping类,该类主要执行两件事情

1)根据路径找到合适得handlerMethod

2)将找到得handlerMethod对象,封装成HandlerExecutionChain类对象,返回



2、getHandlerInternal

根据请求路径,找到对应的HandlerMethod类对象,根据已有得request请求,拿到我们的访问路径,这不难吧?

底层就是各种getAttribute(xxx),然后辅助各种判断,感兴趣的小伙伴可以自己去跟代码


lookupHandlerMethod方法

在根据路径找HandlerMethod类对象时,会调用这个方法。在这个方法中,就会去根据我们的映射url路径,找到对应的HandlerMethod类对象。这里就会联系到我们在第一节中提及的urlLookup集合,忘记的小伙伴,记得往前看

当我们的请求路径无法直接匹配映射路径,但是却满足一些通过通配符的路径,如*、?。此时就会使用到另一个集合mappingLookup,该集合的数据与urlLookup集合的数据处理相同。复杂的地方就是此时SpringMVC会初始化一个比较器,然后根据指定的比较规则,选择优先级最高的映射,进行对应的逻辑处理。



该代码片段来自AbstractHandlerMethodMapping类




3、getHandlerExecutionChain

看名字,这里显然使用了一个责任链的设计模式

第3步,已经返回了对应的handlerMethod对象,这一步是进行拦截器的封装。

前面我们在doDispatch方法中有提及,会调用对应的拦截器方法执行,可是拦截器的数据来自哪里?就是在这里

1)获取当前的映射路径

2)找到我们容器中配置的所有的拦截器

3)如果拦截器类继承自MappedInterceptor类,调用拦截器中的matches方法,如果路径匹配就添加到对应的拦截器链条中

4)其他常规的拦截器就都添加到拦截器链条中

5)最终返回封装好的带有拦截器链条和handlerMethod对象的HandlerExecutionChain






四、getHandlerAdapter(重点)

截至到目前,我们已经做好的工作为(仅考虑主体逻辑),找到对应的handlerMethod对象,即处理的逻辑,封装好了对应的拦截器链

1、getHandlerAdapter方法

传入前面封装好的chain对象的handlerMethod属性。这里就会根据对应的handlerMethod类型,选择合适的处理器适配器(HandlerAdapter)

判断supports逻辑比较简单,这里就不再说明



2、ha.handle()

前面流程是选择合适的handlerAdapter,这一步就是根据具体的adapter处理器对象,执行我们方法里面的具体逻辑

我们以使用注解的方式为例,其使用的HandlerAdapter是RequestMappingHandlerAdapter。

注意:一定要是org.springframework.web.servlet.mvc.method.annotation路径下的HandlerAdapter。因为在org.springframework.web.reactive.result.method.annotation路径下也有一个HandlerAdapter。

点进handle方法,就会执行下面的方法逻辑

该代码片段来自RequestMappingHandlerAdapter类



3、invokeHandlerMethod

上面方法的核心方法就是invokeHandlerMethod,我们熟悉的解析@ModelAttribute注解,就是在该方法中。具体的方法功能已在注释中给出,这里就不再赘述。

该代码片段来自RequestMappingHandlerAdapter类


4、invokeAndHandle

该方法会调用我们的业务逻辑(invokeForRequest),并且完成返回内容的解析(handleReturnValue)。具体功能参考代码注释

该代码片段来自ServletInvocableHandlerMethod类


5、invokeForRequest

我们最想看的还是在什么位置调用了我们controller层里面的方法,这个位置就是在invokeForRequest方法中。

该代码片段来自InvocableHandlerMethod类中



6、handleReturnValue

我们都知道如果我们添加了@ResponseBody注解,那么返回值就是JSON字符串,这个处理逻辑,就是在这个方法中进行处理的。

1)根据返回的值和返回的类型,找到合适的返回值处理器

2)使用具体的返回值处理器处理我们的返回值

该代码片段来自HandlerMethodReturnValueHandlerComposite类


这个selectHandler方法,会调用supportsReturnType方法,即调用具体返回值处理器支持的类型方法,如下图。是否有ResponseBody注解



该方法的调用逻辑过长(writeWithMessageConverters),仅展示关键部分。即在添加了@RequestBody注解的基础上,会根据具体的json实现类完成将结果转换成json字符串的业务逻辑

该代码片段来自AbstractMessageConverterMethodProcessor类






五、processDispatchResult(重点)

该方法就是我们的视图解析器。根据方法内容很容易就能看出来,它会调用render方法


1、render

该方法主要处理三件事情

1)根据我们的视图名称 解析成为我们真正的物理视图,通过视图解析器对象(resolveViewName)

2)渲染模型视图(view.render)



2、resolveViewName

就是完成的映射路径的前缀和后缀的拼接

获取我们配置的视图解析器。


执行方法后,完成对应的url拼接



3、view.render

根据我们配置的视图解析器,执行对应的处理逻辑

具体的处理逻辑,请参考注释。也就是在这一步,完成我们请求的转发,完成页面的逻辑处理






六、核心流程图

根据上面的代码,再回忆一下这个执行流程,看看自己能理清楚吗?


至此,前端控制器这个大的组件的基本骨架算是梳理完成,感兴趣的小伙伴可以自己去跟源码,完成其他细节的处理。整理了接近4个小时,如果对你有帮助,期待你的收藏。

好了,不说了,现在是下午两点,吃饭去!!!

猜你喜欢

转载自blog.csdn.net/qq_44377709/article/details/120933345