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
mediaTypes.add(MediaType.APPLICATION_JSON_UTF8);//设定json格式且编码为UTF-8
fastJsonHttpMessageConverter.setSupportedMediaTypes(mediaTypes);
//规则赋予转换对象
fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig);
return fastJsonHttpMessageConverter;
}
@Override
public void extendMessageConverters(List
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