美文网首页
2018-08-22

2018-08-22

作者: 吥破 | 来源:发表于2018-12-10 19:00 被阅读0次

    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

    相关文章

      网友评论

          本文标题:2018-08-22

          本文链接:https://www.haomeiwen.com/subject/tsdyiftx.html