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开始到


2018-08-22_第1张图片

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


2018-08-22_第2张图片

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

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


2018-08-22_第3张图片

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


2018-08-22_第4张图片


2018-08-22_第5张图片

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

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

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

initStrategies(context);


2018-08-22_第6张图片

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


2018-08-22_第7张图片

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

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


2018-08-22_第8张图片

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


2018-08-22_第9张图片

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


2018-08-22_第10张图片

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


2018-08-22_第11张图片

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


2018-08-22_第12张图片

继续进入handle方法


2018-08-22_第13张图片

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


2018-08-22_第14张图片

获取到ServletInvocableHandlerMethod invocableMethod 并进入执行


2018-08-22_第15张图片

继续往下进入 ServletInvocableHandlerMethod.invokeAndHandle


2018-08-22_第16张图片

invokeForRequest()方法-》InvocableHandlerMethod.invokeForRequest

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

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

对应的处理方法


2018-08-22_第17张图片

此处个人时进行了一个日志的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>) 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 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> 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

你可能感兴趣的:(2018-08-22)