测试环境搭建: 本次搭建是基于springboot来实现的,代码在码云的链接:https://gitee.com/yangxioahui/thymeleaf.git

项目结构代码如下:

一: controller的三种实现方式:

1. 第一种是大家非常熟悉的,使用@Controller和@RequestMapping

浏览器测试第一个方法结果:

  

2.第二种: 实现Controller 接口,并在beanName里写上url;
  

浏览器测试结果:

    3. 第三种:实现HttpRequestHandler接口,并在beanName里写上url;

浏览器测试结果:

总结:  第一种的请求参数是不确定的,随便我们自己定义 第二和第三种,请求参数是固定的,并且他们的url都是定义在请求路径上的,只是返回值不一样

二: 思考

要执行一个方法,第一步:先找到该方法所在的类的对象,第二步: 调用该对象的指定方法:

如何找到一个类的实例对象:

我们在浏览器输入一个url,对应的controller就会被执行,那么肯定会在某个地方存在一个map集合,map集合的key就是url,value值又是什么呢? 对于第一种实现controller的方式(@RequestMapping 方式),value值至少要包含:

@RequestMapping注解标注的方法对象 和 该方法所属的controller对象,一个controller可以有多个带有@RequestMapping 注解的方法;对于第二和第三种实现controller的方式,由于他们的对象只会有一个方法处理请求,url是beanName

那么他们的map的value值就是HttpRequestHandler或Controller 接口的实现类的对象即可;因此 三种实现controller的方式,最终会有2种map对象进行存储,这2种对象存在不同的映射器中,我们暂且都叫它们做处理器映射器,也叫HandlerMapping;

Handler 就是我们所说的controller;我们分别给这两种映射器起一个名字: @RequestMapping 方式的就叫做RequestMappingHandlerMapping,而实现HttpRequestHandler或Controller 接口方式的控制器,因为url就是他们的beanName,所以起个名称:
  BeanNameUrlHandlerMapping;既然2种方式都是通过url查找一个controller,那么我们将他们抽象出来,提供一个父接口,HandlerMapping,要提供一个查找Handler的方法吧:参数有HttpServletRequest request即可,那么返回值呢?

两种方式的返回值都不一样,用谁都不好,用Object 可以吧,当然可以,但如果我要在我的方法调用前后加上拦截器,这样Object就不合理了,那么就起个公共的名字吧,叫做处理器执行链条,这样可以加入很多的拦截器了: HandlerExecutionChain,于是接口的方法如下:

HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception,那么问题来了? 一个请求过来,我怎么知道要用那种HandlerMapping呢? 到底是RequestMappingHandlerMapping还是BeanNameUrlHandlerMapping,没法确定,既然这样,那就用个集合存起2种HandlerMapiing:

List<HandlerMapping> handlerMappings; 一个请求过来,先问第一个HandlerMapping,看看它的map集合中有没有对应的Handler,没有继续找第二个,都没有叫404 响应

过程如图:

源码分析BeanNameUrlHandlerMapping 的map集合创建过程:

先看看其继承体系:

我们在其父类中看到了对应的map集合:

现在,要关注的是,第二和第三种实现controller的方式,是什么时候将他们的url和对应的controller对象存到HandlerMap中的,我们在继承体系中发现,BeanNameUrlHandlerMapping实现了ApplicationContextAware接口,

spring在创建BeanNameUrlHandlerMapping会调用对应的接口实现方法setApplicationContext(@Nullable ApplicationContext context),debug 断点调试打在该方法上:然后启动代码:

小结: 上面的方法是从spring容器中取出所有的beanName,然后看看是不是以url开头,是的话就创建对应的bean ,并且存到handlerMap中,我们看看判断beanName是不是url开头的方法:

String[] urls = determineUrlsForHandler(beanName),为何会是数组,因为一个bean可以有多个别名:

接着我们看看是如何将bean 跟其url存到map中的:

registerHandler(urls, beanName);

源码分析RequestMappingHandlerMapping中的map集合是如何将RequestMapping注解对应的方法存进去的,先看继承体系:

父类AbstractHandlerMethodMapping有个属性:

从继承体系中,可以看到,RequestMappingHandlerMapping实现了InitializingBean接口,该接口是一个生命周期接口,spring在创建bean时,会调用该接口的afterPropertiesSet() 方法,那么我们debug调试看看:

我们先看是如何获取到对应的beanName的:getCandidateBeanNames()

接着看看如何处理beanName:

processCandidateBean(beanName);

现在已经找到带有@controller或者@RequestMapping注解的类了,从上面条件可以看到,类上只要有其中一个注解都可以了,所以下面搭配也是可以的:

找到对应的类,接下来要处理器里面的方法了吧:detectHandlerMethods(beanName);

跟进去看看是如何找到对应的方法的:

最后我们看看是如何将找到的方法存到map中的:registerHandlerMethod(handler, invocableMethod, mapping);

通过上图分析,一个url 过来,先从urlLookup这个集合中找到@requestMapping信息封装的对象,然后以该对象作为key,从mappingLookup中获取对应的controller和方法
  小结:我们已经解决了第一个问题:如何通过url找到一个Handler,那么第二个问题来了,找到Handler后,如何执行方法?

三种实现controller的方式,第一种执行是最复杂的,因为参数不固定:

另外2种方式,比较固定:

后2种方式的返回值不一样,前面我们说过HandlerMapping有个获取handler的方法:HandlerExecutionChain getHandler(HttpServletRequest request),所以三种方式通过url获取到的handler最终被封装成了HandlerExecutionChain

现在我们要执行里面的方法时,对于第一种controller实现方法,HandlerExecutionChain的handler属性对象其实是HandlerMethod对象,而对于第二种方式,就是Controller接口的实现类对象,第三种方式:HttpRequestHandler接口实现类对象;

这样的话,我们需要判断handler对象类型,来找到对应的处理方式,如果是实现Controller接口就用xx 方式,如果是HttpRequestHandler接口就有aa方式,对于@Controller注解的又是另外一种方式,这种找处理方式的过程就是适配过程,也可以叫做

查找适配器过程,给适配器起名叫做HandlerAdapter,至少存在3种HandlerAdappter实现类,该接口要提供一个方法,判断支不支持当前处理器,支持,就可以调用该类的另一个执行处理器的方法:

源码分析:

我们先看实现Controller接口方式的controller对应的适配器:SimpleControllerHandlerAdapter

接着我们来看看实现HttpRequestHandler接口的方式:HttpRequestHandlerAdapter

最复杂的是实现RequestMapping注解的方法,因为参数是多样化的:RequestMappingHandlerAdapter

综上所述,我们已经解决了如何通过url找到一个handler,并且如何通过hanlder找到执行器,大概流程是 : url->分别判断BeanNameUrlHandlerMapping和RequestMappingHandlerMapping找到对应hanlder
->分别判断HttpRequestHandlerAdapter,SimpleControllerHandlerAdapter,RequestMappingHandlerAdapter 找到对应的适配器进行方法调用;

我们知道,tomcat接收到请求后,会根据路径找到对应的servlet,而在springMVC中,只提供了一个servlet,那就是DispatcherSetvlet,他拦截的路径是“/”代表所有请求,下面我们来分析其核心过程:

先看继承体系:

可见,其继承的是HttpServlet:

那么,我们上面分析的HandlerMapping和HandlerAdapter是什么时候添加到DispathcerServlet的呢?

在下面的配置类中,有个内部类:

这个内部类的继承关系:

WebMvcConfigurationSupport这个类里面有很多方法:

可以看到,通过该配置类将我们要的HandlerMapping和HandlerAdapter注入到spring容器中了,那么我们回到DispatcherServlet中:

//在非springboot项目,使用的是默认策略,这怎么理解呢?我们在DispatcherServlet所在包下看到一个文件:

看看里面的内容:

回到DisapcherServlet中:

我们回到之前获取默认策略方法那里:

从上面的分析,我们已经知道:

List<HandlerMapping> handlerMappings 和 List<HandlerAdapter> handlerAdapters 是如何添加到DispacherServlet中去的了,下面我们进行调用链路调试:

//该方法是get请求,所以我们debug在DispatcherServlet父类的doGet中:

有上面的分析可以知道,我们在service层要获取HttpServletRequest,可以通过:HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

继续跟进:

该方法是核心代码:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;//我们之前分析的HanderMapping的getHandler方法,返回值就是HandlerExecutionChain 了
boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try {
ModelAndView mv = null;
Exception dispatchException = null; try {
processedRequest = checkMultipart(request); //校验请求是不是文件上传类型,主要的逻辑:StringUtils.startsWithIgnoreCase(request.getContentType(), "multipart/");
multipartRequestParsed = (processedRequest != request); // Determine handler for the current request.
mappedHandler = getHandler(processedRequest);//通过请求对象的url,遍历各个HandlerMapping,找到对应的Hanler
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);//找不到就是404
return;
} // Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); //通过找到的Handler,遍历HandlerAdapter,看看哪个可以执行该Handler的方法 // Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { //如果是get或者head请求,判断上次请求和这次请求的最后修改时间,如果没变化,前端就用它们之前缓存的内容即可
return;
}
} if (!mappedHandler.applyPreHandle(processedRequest, response)) { //执行拦截器链条的所有前置拦截处理
return;
} // Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); //执行目标方法,得到ModleAndView对象 if (asyncManager.isConcurrentHandlingStarted()) {
return;
} applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);//执行拦截器的后置处理方法
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); //处理返回结果,如进行视图渲染
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}

下面具体跟进去看看:

因为本次调试用的是RequestMapping方式的controller,因此会用到RequestMappingHandlerMapping:

跟进去:

在这个Map里有我们要找的Handler

至此,一个请求过来,通过请求url,找到了对应的Handler:我们回到主流程:

前面分析过RequestMappingHandlerMapping的map集合的Handler对象是HandlerMethod,因此RequestMappingHandlerAdapter 只要判断hanler是不是HandlerMethod

至此,HandlerAdapter已经找到了,接着就是处理handler的目标方法了:

执行方法前会调用拦截器链路:

下面是真正执行目标方法了:

@RequestMapping注解的方式,执行方法逻辑会非常复杂:

我们这里会用到下面的一个参数解析器,因为我们的路径是:product/info/{id}

继续跟进:

最后的结果返回封装成ModleAndView对象

HandlerAdapter执行后,获得了ModleAndView

我本次使用的是Thymeleaf 模板技术,其有个ThymeleafProperties类,指定了视图的前缀和后缀

所以,我们得到的ModelAndView  中的view 会被拼接上前缀和后缀,这样视图的路径就变成: classpath:/templates/product.html ,而我在该路径下已经有了该文件

继续前面调试:

中间省略n个步骤,最终到达这里:

所以我们跟进下面方法:

进入里面,发现它是先从缓存取模板,没有取到再重新去解析:

显然,我第一次调用,缓存是没有的:

继续跟进,省略多个步骤后:

继续跟进到达:

跟进里面可以看到

数据填充过程就不分析了,有兴趣自己调试

问题: 三种controller实现方式,它们的 HandlerAdapter 返回的对象是ModelAndView,但有些时候是不用进行试图解析的,那么是怎么判断需不需要进行试图解析的呢

我们调试第三种实现方式:

我们看看wasCleared()这个方法:

最后看看MV的结构

最后总结:

问题?我们如何自定义HandlerMapping和HandlerAdapter还有参数解析器,响应值解析器,下编博客将会讲解




												

springmvc 源码分析(二)-- DiapartcherServlet核心调用流程分析的更多相关文章

  1. springMVC源码分析--HandlerInterceptor拦截器调用过程(二)

    在上一篇博客springMVC源码分析--HandlerInterceptor拦截器(一)中我们介绍了HandlerInterceptor拦截器相关的内容,了解到了HandlerInterceptor ...

  2. 7、SpringMVC源码分析(2):分析HandlerAdapter.handle方法,了解handler方法的调用细节以及@ModelAttribute注解

    从上一篇 SpringMVC源码分析(1) 中我们了解到在DispatcherServlet.doDispatch方法中会通过 mv = ha.handle(processedRequest, res ...

  3. springMVC源码分析--容器初始化(二)DispatcherServlet

    在上一篇博客springMVC源码分析--容器初始化(一)中我们介绍了spring web初始化IOC容器的过程,springMVC作为spring项目中的子项目,其可以和spring web容器很好 ...

  4. 框架-springmvc源码分析(二)

    框架-springmvc源码分析(二) 参考: http://www.cnblogs.com/leftthen/p/5207787.html http://www.cnblogs.com/leftth ...

  5. springMVC源码分析--SimpleServletHandlerAdapter(二)

    上一篇博客springMVC源码分析--HandlerAdapter(一)中我们主要介绍了一下HandlerAdapter接口相关的内容,实现类及其在DispatcherServlet中执行的顺序,接 ...

  6. springMVC源码分析--AbstractHandlerMapping(二)

    上一篇博客springMVC源码分析--HandlerMapping(一)中我们简单的介绍了HandlerMapping,接下来我们介绍一下它的抽象实现类AbstractHandlerMapping

  7. springMVC源码分析--国际化实现Session和Cookie(二)

    上一篇博客springMVC源码分析--国际化LocaleResolver(一)中我们介绍了springMVC提供的国际化的解决方案,接下来我们根据springMVC提供的解决方案来简单的实现一个多语 ...

  8. springMVC源码分析--视图AbstractView和InternalResourceView(二)

    上一篇博客springMVC源码分析--视图View(一)中我们介绍了简单介绍了View的结构实现及运行流程,接下来我们介绍一下View的实现类做的处理操作. AbstractView实现了rende ...

  9. springMVC源码分析--HandlerMethodReturnValueHandlerComposite返回值解析器集合(二)

    在上一篇博客springMVC源码分析--HandlerMethodReturnValueHandler返回值解析器(一)我们介绍了返回值解析器HandlerMethodReturnValueHand ...

随机推荐

  1. codeforce Round #599(Div.2)

    题目传送门 A. Maximum Square 题目意思是给你n个长条,每个长条的高度是num[i](0 < i < n),每一条的宽度都是 1 :然后求这些长条可以组成的最大面积的正方形 ...

  2. HTTP/3 来了,你了解它么?

    作为我们网上冲浪最为常见,也经常被人忽视的 HTTP 已经更新换代到了 HTTP/3.本文简单明了的带你认识 HTTP/3 的作用. 最近二狗子看到自己存储女神婷婷照片所用的云服务商--又拍云推出了 ...

  3. seo增加外链的方法

    http://www.wocaoseo.com/thread-128-1-1.html 今天给大家介绍一下本人发外链的一点经验吧.好多新手都感觉,发个外链真的好难哦.其实之前我也是这样认为的,发外链好 ...

  4. elementUI table怎么实现点击上移下移

    其实炒鸡简单...   <el-table :data='tableData' > ... ...  <el-table-column label="操作" al ...

  5. python数值运算 四则运算

    数值运算 描述 获得用户输入的一个字符串,格式如下:‪‬‪‬‪‬‪‬‪‬‮‬‫‬‪‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‭‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‮‬‪‬‪‬‪‬‪‬‪‬ ...

  6. PHP - 读取EXCEL内容 存入数据库

    <?php //设置请求头 header("Content-Type:text/html;charset=utf8"); header("Access-Contro ...

  7. Cross-Site Scripting: Reflected

    首先贴解决办法吧,解决了我项目中的问题,不一定适用所有情况. //For Cross-Site Scripting: Reflected public static String filter(Str ...

  8. java初探(1)之登录终探

    上一章讲了表单验证,数据验证和加密.这一章,将研究服务器和数据库的交互过程. 后端服务器有两种主流的形式,SQL数据库和NOSQL数据库.其中MYSQL属于SQL数据库,REDIS属于非SQL数据库. ...

  9. [BUUOJ记录] [HCTF 2018]WarmUp

    BUUOJ Web的第一题,其实是很有质量的一道题,但是不知道为什么成了Solved最多的题目,也被师傅们笑称是“劝退题”,这道题的原型应该是来自于phpMyadmin的一个文件包含漏洞(CVE-20 ...

  10. js map对象处理if

    onButtonClick只有一个参数时候,map和object对象都可以 // onButtonClick1(3) onButtonClick只有一个参数时候,map和object对象都可以 con ...