Spring源码阅读目录
第一部分——IOC篇
第一章 Spring之最熟悉的陌生人——IOC
第二章 Spring之假如让你来写IOC容器——加载资源篇
第三章 Spring之假如让你来写IOC容器——解析配置文件篇
第四章 Spring之假如让你来写IOC容器——XML配置文件篇
第五章 Spring之假如让你来写IOC容器——BeanFactory和FactoryBean
第六章 Spring之假如让你来写IOC容器——Scope和属性填充
第七章 Spring之假如让你来写IOC容器——属性填充特别篇:SpEL表达式
第八章 Spring之假如让你来写IOC容器——拓展篇
第九章 Spring之源码阅读——环境搭建篇
第十章 Spring之源码阅读——IOC篇
第二部分——AOP篇
第十一章 Spring之不太熟的熟人——AOP
第十二章 Spring之不得不了解的内容——概念篇
第十三章 Spring之假如让你来写AOP——AOP联盟篇
第十四章 Spring之假如让你来写AOP——雏形篇
第十五章 Spring之假如让你来写AOP——Joinpoint(连接点)篇
第十六章 Spring之假如让你来写AOP——Pointcut(切点)篇
第十七章 Spring之假如让你来写AOP——Advice(通知)上篇
第十八章 Spring之假如让你来写AOP——Advice(通知)下篇
第十九章 Spring之假如让你来写AOP——番外篇:Spring早期设计
第二十章 Spring之假如让你来写AOP——Aspect(切面)篇
第二十一章 Spring之假如让你来写AOP——Weaver(织入器)篇
第二十二章 Spring之假如让你来写AOP——Target Object(目标对象)篇
第二十三章 Spring之假如让你来写AOP——融入IOC容器篇
第二十四章 Spring之源码阅读——AOP篇
第三部分——事务篇
第二十五章 Spring之曾经的老朋友——事务
第二十六章 Spring之假如让你来写事务——初稿篇
第二十七章 Spring之假如让你来写事务——铁三角篇
第二十八章 Spring之假如让你来写事务——属性篇
第二十九章 Spring之假如让你来写事务——状态篇
第三十章 Spring之假如让你来写事务——管理篇
第三十一章 Spring之假如让你来写事务——融入IOC容器篇
第三十二章 Spring之源码阅读——事务篇
第四部分——MVC篇
第三十三章 Spring之梦开始的地方——MVC
第三十四章 Spring之假如让你来写MVC——草图篇
第三十五章 Spring之假如让你来写MVC——映射器篇
第三十六章 Spring之假如让你来写MVC——拦截器篇
第三十七章 Spring之假如让你来写MVC——控制器篇
第三十八章 Spring之假如让你来写MVC——适配器篇
第三十九章 Spring之假如让你来写MVC——番外篇:类型转换
第四十章 Spring之假如让你来写MVC——ModelAndView篇
第四十一章 Spring之假如让你来写MVC——番外篇:数据绑定
第四十二章 Spring之假如让你来写MVC——视图篇
第四十三章 Spring之假如让你来写MVC——上传文件篇
第四十四章 Spring之假如让你来写MVC——异常处理器篇
第四十五章 Spring之假如让你来写MVC——国际化篇
第四十六章 Spring之假如让你来写MVC——主题解析器篇
第四十七章 Spring之假如让你来写MVC——闪存管理器篇
第四十八章 Spring之假如让你来写MVC——请求映射视图篇
第四十九章 Spring之假如让你来写MVC——番外篇:属性操作
第五十章 Spring之假如让你来写MVC——融入IOC容器篇
第五十一章 Spring之源码阅读——MVC篇
第五部分——Boot篇
第五十二章 Spring之再进一步——Boot
第五十三章 Spring之假如让你来写Boot——环境篇
第五十四章 Spring之假如让你来写Boot——注解篇(上)
第五十五章 Spring之假如让你来写Boot——注解篇(下)
第五十六章 Spring之假如让你来写Boot——SPI篇
第五十七章 Spring之假如让你来写Boot——配置文件篇(上)
第五十八章 Spring之假如让你来写Boot——配置文件篇(下)
第五十九章 Spring之假如让你来写Boot——番外篇:再谈Bean定义
第六十章 Spring之假如让你来写Boot——自动装配篇
第六十一章 Spring之假如让你来写Boot——番外篇:杂谈Starter
第六十二章 Spring之假如让你来写Boot——番外篇:重构BeanFactory
第六十三章 Spring之假如让你来写Boot——番外篇:再谈ApplicationContext
第六十四章 Spring之假如让你来写Boot——内嵌Web容器篇
第六十五章 Spring之假如让你来写Boot——Main方法启动篇
第六十六章 Spring之最终章——结语篇
文章目录
前言
对于Spring一直都是既熟悉又陌生,说对它熟悉吧,平时用用没啥问题,但面试的时候被问的一脸懵逼,就很尴尬,都不好意思在简历上写着熟悉Spring了
所以决定花点时间研究研究Spring的源码。主要参考的书籍是:《Spring源码深度解析(第2版)》、《Spring揭秘》、《Spring技术内幕:深入解析Spring架构与设计原理(第2版)》
书接上回,在上篇 第三十八章 Spring之假如让你来写MVC——适配器篇 中,A君 已经完成了 适配器 部分的功能了。接下来看看 A君 会有什么骚操作吧
尝试动手写IOC容器
出场人物:A君(苦逼的开发)、老大(项目经理)
背景:老大 要求 A君在一周内开发个简单的 IOC容器
前情提要:A君 已经完成了 适配器 部分的功能了 。。。
第三十五版 ModelAndView
“A君,适配器 完成之后,接下来要跟你谈新的功能了。” 老大 坐在工位上,幽幽说道
“还要实现哪个功能?” 生活不易,A君 叹气,怎么还没完没了了
“你看现在还需要用户自己调用request
参数,视图返回之后,还要单独处理。可以考虑把这部分内容统一放到一个对象中去,方便管理。” 老大 缓缓说到 “具体实现,你自己琢磨下把!”
“好吧!” 老大 话都说到这份上了,A君 也就没什么好说的了,默默的回去准备开始干活了
ModelMap
A君 坐在位置上,稍微想了下,心中就有主意了。对于request
设置值,无疑Map
是个好方式。先把这部分内容分装成一个对象。A君 新增ModelMap
对象,和平时用的Map
并无什么区别,只是需要提供更贴合Web环境的API。代码如下:
public class ModelMap extends LinkedHashMap<String, Object> {
public ModelMap addAttribute(Object attributeValue) {
if (attributeValue instanceof Collection && ((Collection<?>) attributeValue).isEmpty()) {
return this;
}
return addAttribute(Conventions.getVariableName(attributeValue), attributeValue);
}
public Object getAttribute(String attributeName) {
return get(attributeName);
}
//省略其他代码
}
ModelAndView
想要统一管理的,不仅仅是request
设置的值,还有视图、状态等信息,这时候还需要额外的一个类进行包装。于是,A君 定义ModelAndView
类,代码如下:
public class ModelAndView {
/**
* 视图
*/
private Object view;
/**
* 数据
*/
private ModelMap model;
/**
* 响应状态
*/
private HttpStatus status;
/**
* 是否清楚实例
*/
private boolean cleared = false;
}
新的Model
这样就完事了,NO,NO,NO。A君 刚才随手写了一个,真要这么干,会被 老大 砍死的,线程线程不安全,接口接口没有,这让 A君 如何交差
此处模拟Spring发展历程,实际上ModelMap
是Spring早期的产物,正如上面所说的原因一样,后边被重新设计了
A君 一阵无奈,只能按照 老大 的要求重新设计,至于ModelMap
就扔着吧,A君 也懒得弄了,后期做个兼容就行了,新增Model
接口,代码如下:
public interface Model {
Model addAttribute(String attributeName, Object attributeValue);
Model addAttribute(Object attributeValue);
Model addAllAttributes(Collection<?> attributeValues);
Model addAllAttributes(Map<String, ?> attributes);
/**
* 合并参数
*
* @param attributes
* @return
*/
Model mergeAttributes(Map<String, ?> attributes);
boolean containsAttribute(String attributeName);
Object getAttribute(String attributeName);
Map<String, Object> asMap();
}
线程安全的Model
由于前面的问题,A君 汲取ModelMap
的教训,为什么ModelMap
线程不安全?是因为直接继承了LinkedHashMap
,那改成线程安全的ConcurrentHashMap
就行了。于是,A君 新增ConcurrentModel
类,代码如下:
public class ConcurrentModel extends ConcurrentHashMap<String, Object> implements Model {
@Override
public ConcurrentModel addAttribute(String attributeName, Object attributeValue) {
put(attributeName, attributeValue);
return this;
}
}
整体代码和ModelMap
差不多,这里就不过多赘述了
兼容ModelMap
要想实现兼容,其实也简单,无非就是分别继承ModelMap
和实现Model
就可以了,也没什么难度。A君 定义ExtendedModelMap
类,代码如下:
public class ExtendedModelMap extends ModelMap implements Model {
@Override
public ExtendedModelMap addAttribute(String attributeName, Object attributeValue) {
super.addAttribute(attributeName, attributeValue);
return this;
}
//省略其他方法
}
咦?这不还是线程不安全吗?原因很简单,A君 只负责提供对应的实现,不替用户做决定,选择权交给用户
重定向Model
之前 A君 开发Web的时候,知道服务器有 forward(转发)、redirect(重定向) 两种响应方式。这两种差异还挺大的,简单的说:
-
forward(转发) 相当于服务器内部处理,比如说:A君 去餐厅吃饭,要什么菜跟服务员说就行了,服务员会去找厨师进行处理,最终服务员会把菜端上来,至于其中的过程 A君 不用关心
-
redirect(重定向) 相当于给客户端返回一个地址,让客户端重新请求,比如说:A君 平时要处理事情,但是不知道联系方式,这时候 A君 就会拨通114热线去询问对应的联系方式,然后在自己去联系对应的负责人
通过上面的对比,可以清楚看出关键所在,对于 forward(转发) 来说,是同一个request
,内容可以往下传递,而对于 redirect(重定向),客户端需要重新发起请求,相当于新的request
了,自然而然,对应的数据都会丢失。这就衍生出一个新的场景, 用户如果想在 redirect(重定向) 的时候传递参数,这时候要怎么办?解决方法其他也有:要么通过URL传递,要么通过共享区域——Session。解决方法有了,那就可以定义接口了,A君 新增RedirectAttributes
类,代码如下:
public interface RedirectAttributes extends Model {
@Override
RedirectAttributes addAttribute(String attributeName, Object attributeValue);
@Override
RedirectAttributes addAttribute(Object attributeValue);
@Override
RedirectAttributes addAllAttributes(Collection<?> attributeValues);
@Override
RedirectAttributes mergeAttributes(Map<String, ?> attributes);
RedirectAttributes addFlashAttribute(String attributeName, Object attributeValue);
RedirectAttributes addFlashAttribute(Object attributeValue);
Map<String, ?> getFlashAttributes();
}
实现类基本类似,A君 这里就不在赘述了
此处还有涉及到参数绑定、数据校验、异常处理等问题,后面会有专门的章节进行讲解
ModelAndView容器
在 控制器 执行的过程中,会经过很多处理,在此过程中,数据、状态是不断发生变化的,还有可能是 redirect(重定向) 操作,这些数据都需要额外的存储,那直接用ModelAndView
不行吗?可以,但是ModelAndView
只负责提供渲染数据,执行过程中的变化它可不管,这时候就需要个类,帮ModelAndView
收集请求链中执行的数据、状态等信息,它就是ModelAndViewContainer
。代码如下:
/**
* ModelAndView容器,负责帮ModelAndView采集数据
*/
public class ModelAndViewContainer {
/**
* 默认model
*/
private final ModelMap defaultModel = new BindingAwareModelMap();
private final Set<String> noBinding = new HashSet<>(4);
private final Set<String> bindingDisabled = new HashSet<>(4);
/**
* Session状态
*/
private final SessionStatus sessionStatus = new SimpleSessionStatus();
/**
* 忽略默认重定向
*/
private boolean ignoreDefaultModelOnRedirect = false;
/**
* 视图
*/
private Object view;
/**
* 重定向数据
*/
private ModelMap redirectModel;
private boolean redirectModelScenario = false;
/**
* 响应状态
*/
private HttpStatus status;
private boolean requestHandled = false;
}
由于在执行过程中需要收集数据,所以各个调用链都必须带上ModelAndViewContainer
,改动地方比较多,这里主要集中在 注解适配器 上,例如:
在这里创建后,依次往下传递即可
返回值解析器
前面 A君 只处理了 控制器 方法入参的问题,返回值在当时的场景下,只处理了字符串。现在就不一样了,返回值可以是String
、ModelAndView
等类型了,就不能简单的一个instanceof
能解决的了,处理方式和参数解析器类似。A君 定义HandlerMethodReturnValueHandler
接口,代码如下:
/**
* 返回值处理器
*/
public interface HandlerMethodReturnValueHandler {
boolean supportsReturnType(MethodParameter returnType);
void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;
}
不管这么样,A君 得先对字符串提供支持,于是,新增ViewNameMethodReturnValueHandler
类,代码如下:
public class ViewNameMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
@Override
public boolean supportsReturnType(MethodParameter returnType) {
Class<?> paramType = returnType.getParameterType();
return (void.class == paramType || CharSequence.class.isAssignableFrom(paramType));
}
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
if (returnValue instanceof CharSequence) {
String viewName = returnValue.toString();
mavContainer.setViewName(viewName);
if (isRedirectViewName(viewName)) {
mavContainer.setRedirectModelScenario(true);
}
} else if (returnValue != null) {
throw new UnsupportedOperationException("Unexpected return type: " +
returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
}
}
//省略其他代码
}
和参数解析器一样,返回值解析器一样会有很多个,这时候就需要一个统一处理的入口,A君 定义HandlerMethodReturnValueHandlerComposite
类,代码如下:
public class HandlerMethodReturnValueHandlerComposite implements HandlerMethodReturnValueHandler {
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return getReturnValueHandler(returnType) != null;
}
private HandlerMethodReturnValueHandler getReturnValueHandler(MethodParameter returnType) {
for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
if (handler.supportsReturnType(returnType)) {
return handler;
}
}
return null;
}
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType,
NativeWebRequest webRequest) throws Exception {
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
}
handler.handleReturnValue(returnValue, returnType, webRequest);
}
private HandlerMethodReturnValueHandler selectHandler(Object value, MethodParameter returnType) {
for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
if (handler.supportsReturnType(returnType)) {
return handler;
}
}
return null;
}
//省略其他代码
}
新增参数解析器
那这就完事了?显然还没有,之前在解析 控制器 参数时,只支持了基本类型,并没有对 Model 进行对应的支持。这回可得添加上了,不然没有实现这一功能。不过处理 Model 类型的处理器有点特殊,不管入参还是返回值都能处理。A君 新增ModelMethodProcessor
类,代码如下:
public class ModelMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return Model.class.isAssignableFrom(parameter.getParameterType());
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest) throws Exception {
return mavContainer.getModel();
}
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return Model.class.isAssignableFrom(returnType.getParameterType());
}
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
if (returnValue == null) {
return;
} else if (returnValue instanceof Model) {
mavContainer.addAllAttributes(((Model) returnValue).asMap());
} else {
// should not happen
throw new UnsupportedOperationException("Unexpected return type [" +
returnType.getParameterType().getName() + "] in method: " + returnType.getMethod());
}
}
}
修改适配器
现在参数解析器有了,返回值解析器也有了,该对 适配器 入手了,HandlerAdapter
可以正式返回ModelAndView
进行统一管理,A君 先对HandlerAdapter
接口进行改造,改动如下:
其他 适配器 改动不大,主要改动在 注解适配器,RequestMappingHandlerAdapter
新增getModelAndView
方法,代码如下:
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
NativeWebRequest webRequest) throws Exception {
if (mavContainer.isRequestHandled()) {
return null;
}
ModelMap model = mavContainer.getModel();
return new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
}
接着,还需要对invokeHandlerMethod
方法进行改动,主要新增对返回值的处理,改动如下:
测试
折腾了半天,总算完事了,A君 开始准备测试了。只需修改 控制器 参数为 Model
即可,代码如下:
@Controller
public class HelloController {
@RequestMapping("/hello")
public String sayHello(HttpServletRequest req, HttpServletResponse resp, Model model) {
String key = "message";
String message = (String) req.getAttribute(key);
if (message == null) {
message = "";
}
model.addAttribute(key, message + " V35 HandleMapping!");
return "hello";
}
}
其余部分不变,测试代码如下:
@Test
public void v35() throws Throwable {
System.out.println("############# 第三十五版: ModelAndView篇 #############");
Tomcat tomcat = new Tomcat();
//设置端口
tomcat.setPort(8082);
//设置静态资源路径
String webApp = new File("src/main/resources/v35").getAbsolutePath();
Context context = tomcat.addWebapp("/test/", webApp);
tomcat.start();
//挂起
tomcat.getServer().await();
}
测试结果如下:
OK,现在 ModelAndView 也完成了,整体来说涉及到的内容比较多,弄的比较乱些。主要是涉及到的内容盘根错杂,不能一一拆开说明。Model 部分简单,相信理解起来没有什么问题,主要改动的点其实就是 适配器 部分,涉及到一大堆的参数、返回值适配问题,所以会显得比较多,只要弄清楚这部分内容,其实也好理解了。现在就先到这里了,A君 只能说一句:MVC 我爱你。下班!
总结
正所谓树欲静而风不止,欲知后事如何,请看下回分解(✪ω✪)