2018-08-22

SpringMvc初始化流程源码解析及请求加载流程解析

及常见Mvc三剑客在spring-boot中的配置和加载原理解析

SpringMvc初始化流程源码解析及请求加载流程解析

及常见Mvc三剑客配置和加载原理解析

一、Dispatcher初始化流程源码解析

1、mvc上下文初始化流程解析

二、 HttpServletRequest请求在springmvc的解析过程

三、mvc常见三剑客配置

1、新瓶装旧酒  MessageConverter

2、三剑客之二 文件上传配置multipartResolver

3、三剑客之3 Interceptor

自定义mvc配置参考

源码地址

https://github.com/cc-ohayou/cc-boot-demo


强烈建议:

简书这个文章图片和格式的处理实在麻烦 还是直接看有道笔记原型吧

https://note.youdao.com/share/?id=52762cfc92051e3ffeff5d3f02f82758&type=note#/


一、Dispatcher初始化流程源码解析

1、mvc上下文初始化流程解析

首先看下类谱图 可见最初加载从Servlet开始到


接下来进入代码看下方法加载顺序


如果是使用tomcat容器或插件启动的从tomcat内部类开始加载 启动 然后调用到spring-webmvc相关的内部初始化实现

直接看源码 注释很清晰initServletBean()这个抽象方法让子类去实现任何他们想实现的东西


此处spring-webmvc通过FrameworkServlet这个类来实现自己内部的初始化



createWebApplicationContext()这个方法一层层点进去其实最后就可以看到调用的是

AbstractApplicationContext.refresh()方法  跟spring IOC容器初始化是殊途同归的

而onRefresh()方法通过DispatcherServlet来实现自己的任务 其实主要也就一个方法

initStrategies(context);


看注释很明了可以发现 就是初始化servlet用到的策略对象 至此mvc初始化work完成


二、 HttpServletRequest请求在springmvc的解析过程

主要是通过DispatcherServlet.doService()方法完成spring自己的分发处理 从类继承图谱可以看出 方法调用路径  光标放在doService方法上然后ctrl+alt+u可以发现 idea很智能的显示出了该方法的实现路径 感谢强大ide工具吧 (真幸福的时代啊)


进入源码查看发现doService方法主要调用的是doDispatch方法


首先获取调用映射的处理对象 找不到直接抛异常


然后查找处理器适配器 找不到同样直接抛异常


接下来继续往下走可以发现 是进行拦截器的前置处理 执行方法 和拦截器的后置处理


继续进入handle方法


继续往下走 可以看到进入了熟悉有名的RequestMappingHandlerAdapter 类


获取到ServletInvocableHandlerMethod invocableMethod 并进入执行


继续往下进入 ServletInvocableHandlerMethod.invokeAndHandle


invokeForRequest()方法-》InvocableHandlerMethod.invokeForRequest

继续往下走 可以看到下图中 利用反射真正的进入了方法Method的invoke阶段

如果方法关联的Bean在初始化时被aop注入了其他的植入逻辑此处则会进入到

对应的处理方法


此处个人时进行了一个日志的aop织入记录

调用方法执行完毕后进入返回值的处理

注意此处 如果自己配置了 一些拦截器对返回结果进行了统一的转换处理 最好指定自定义的

HtppMessageConverter

进入HandlerMethodReturnValueHandlerComposite.handleReturnValue

此处根据返回类型和值获取到返回值处理器

此处调用

主要是得到对应的MediaType 并从而选择MessageConverter

此处代码很重要 直接把源码版过来一步步分析

//上面获取到的MediaType 不为空进入逻辑判断

if (selectedMediaType != null) {

  selectedMediaType = selectedMediaType.removeQualityValue();

//遍历所有的messageConverters  看下面给出的截图1可以看到springmvc默认加载的所有HttpMessageConverter

  for (HttpMessageConverter<?> converter : this.messageConverters) {


      GenericHttpMessageConverter genericConverter =

            (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);

//如果转化的GenericHttpMessageConverter类型 不为空

调用canWrite方法判断是否可以对该返回值进行写入

为空则直接使用对应converter的canWrite方法进行判断

可以看到这里是if里面使用了三目运算符 不推荐此种写法 不直观明了

简而言之就是找到判断通过的converter然后进行 写入

if (genericConverter != null ?

            ((GenericHttpMessageConverter) converter).canWrite(declaredType, valueType, selectedMediaType) :

            converter.canWrite(valueType, selectedMediaType)) {

//值得注意的是这里 获取了advice进行返回体真正返回前的操作 我们可以借此实现自定义的ResponseBodyAdvice来进行返回格式的统一  参见下面截图2  截图3 、4 指明ResponseBodyAdvice匹配的流程和规则

        outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,

              (Class<? extends HttpMessageConverter<?>>) converter.getClass(),

              inputMessage, outputMessage);

        if (outputValue != null) {

            addContentDispositionHeader(inputMessage, outputMessage);

            if (genericConverter != null) {

              genericConverter.write(outputValue, declaredType, selectedMediaType, outputMessage);

            }

            else {

              ((HttpMessageConverter) converter).write(outputValue, selectedMediaType, outputMessage);

            }

            if (logger.isDebugEnabled()) {

              logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType +

                    "\" using [" + converter + "]");

            }

        }

        return;

      }

  }

}

截图1

截图2

截图3

自定义的advice

到了最终的转化步骤

其实一路仔细看过来 稍微思考一下就知道没戏的 我们此处待转换的类型是自定义对象

关键使用的是StringHttpMessageConverter  这个转换不了对象为String类型的

最终走完发现果然抛了异常 那怎么办呢 不要急 其实这里涉及到了我们很熟悉但有容易忽略的一个地方

springmvc配置常见三剑客之二的MessageConverter

三、mvc常见三剑客配置

1、新瓶装旧酒  MessageConverter

其实这个是我在搭建新spring-boot项目时遇到的一个问题

原先使用spring-mvc时有配置这么一个东西

我们指定使用FastJsonHttpMessageConverter就ok了

在我们自定义的webMvcConfig的实现类中通过覆写父类方法添加一下

这样配置好以后我们重新启动项目 再次来到刚才截图1的位置 发现果然按我们新加的

FastJsonHttpMessageConverter来到了messageConverters这个list的最上方

(额外添加的嘛 list的规则后来者居上)

最终使用postMan请求一下 终于成功了 真不容易啊

MessageConverters的加载流程

不过到这里新的疑问又来了 有些好奇宝宝问了 messageConverter到底在那里加载的啊 隐藏的这么深 害我们折腾了这么久 太可恶了

所以满足你们的好奇心呐 下面就讲下messageConverters是如何加载的吧

其实主要是通过这个bean 名称叫requestMappingHandlerAdapter 

怎么找到他的呢 直接在我们的自定义配置类MyWebMvcConfig处打上断点

debug模式下调用链路其实很清晰  我们只需要一个个点一下看看每一步干了什么即可

很容易就发现 调用主要是开始自这个bean的获取 创建 那就看看怎么创建的吧

可以看到是在这个类WebMvcAutoConfiguration里面 完成的bean的注入

这个bean初始化的时候有个getMessageConverters()方法 ,很明显找到老窝了

那还等什么进去一看究竟吧

可以看到有三处嫌疑点

我们WebMvcConfig中实现的是extendMessageConverters方法

所以直接看这个

2、三剑客之二 文件上传配置multipartResolver

既然讲到了mvc的Interceptor 和messageConverter 不讲一下multipartResolver这个难兄难弟也说不过去

下面就讲一下常用的文件上传的bean的配置 multipartResolver 在普通spring项目和spring-boot中的转换吧

通过MultipartFilter获取 multipartResolver

所以换到spring-boot中只需要注入以下即可  不在此处 在任意能扫描到的位置也是可以的

不可为了统一最好放在一处

3、三剑客之3 Interceptor

作为我们最熟悉的座上宾 这个就不过多废话了 请求流程解析里也提到了拦截器生效的地方

上个参考配置

自定义mvc配置参考

最后贴出mvcConfig的整体配置代码 做个参考

建议大家不急的话l还是多敲敲键盘打出来而不是复制粘贴完事儿

(这样做的后果通常是 过后就忘 以后再犯 - -)

做个脚踏实地的聪明人

@Configuration

public class MyWebMvcConfig implements WebMvcConfigurer {

    @Override

    public void addInterceptors(InterceptorRegistry registry) {

        registry.addInterceptor(getExceptionInterceptor()).addPathPatterns("/**");

    }

    @Bean

    public ExceptionInterceptor getExceptionInterceptor(){

        return new ExceptionInterceptor();

    }

    /**

    * 配置自定义的HttpMessageConverter

    *注:

    *1.configureMessageConverters:

      * 重载会覆盖掉spring mvc默认注册的 多个HttpMessageConverter。

    *2.extendsMessageConverter:仅添加一个自定义 的HttpMessageConverter,

      * 不覆盖默认注册 的HttpMessageConverter.

    **/

    //使用extendsMessageConverter 添加一个自定义的HttpMessageConverter

    @Bean

    public HttpMessageConverter fastJsonHttpMessageConverter(){

        //创建FastJson信息转换对象

        FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();

        //创建Fastjosn对象并设定序列化规则

        FastJsonConfig fastJsonConfig = new FastJsonConfig();

        fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);

        // 中文乱码解决方案

        List<MediaType> mediaTypes = new ArrayList<>();

        mediaTypes.add(MediaType.APPLICATION_JSON_UTF8);//设定json格式且编码为UTF-8

        fastJsonHttpMessageConverter.setSupportedMediaTypes(mediaTypes);

        //规则赋予转换对象

        fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig);

        return fastJsonHttpMessageConverter;

    }

    @Override

    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {

        converters.add(fastJsonHttpMessageConverter());

    }

    @Bean(name = "multipartResolver")

    public MultipartResolver multipartResolver() {

        CommonsMultipartResolver resolver = new CommonsMultipartResolver();

        resolver.setDefaultEncoding("UTF-8");

        //resolveLazily属性启用是为了推迟文件解析,以在在UploadAction中捕获文件大小异常

        resolver.setResolveLazily(true);

        resolver.setMaxInMemorySize(40960);

        //上传文件大小 50M 50*1024*1024

        resolver.setMaxUploadSize(50 * 1024 * 1024);

        return resolver;

    }

}

源码地址

https://github.com/cc-ohayou/cc-boot-demo

参考博客:

https://www.cnblogs.com/fangjian0423/p/springMVC-interceptor.html

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

推荐阅读更多精彩内容