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篇
文章目录
前言
对于Spring一直都是既熟悉又陌生,说对它熟悉吧,平时用用没啥问题,但面试的时候被问的一脸懵逼,就很尴尬,都不好意思在简历上写着熟悉Spring了
所以决定花点时间研究研究Spring的源码。主要参考的书籍是:《Spring源码深度解析(第2版)》、《Spring揭秘》、《Spring技术内幕:深入解析Spring架构与设计原理(第2版)》
书接上回,在上篇 第三十六章 Spring之假如让你来写MVC——拦截器篇 中,A君 按照 老大 的要求,实现了 拦截器 部分的功能了。接下来看看 A君 会有什么骚操作吧
尝试动手写IOC容器
出场人物:A君(苦逼的开发)、老大(项目经理)
背景:老大 要求 A君在一周内开发个简单的 IOC容器
前情提要:A君 按照 老大 的要求,实现了 拦截器 部分的功能了 。。。
第三十三版 控制器
“这个 拦截器 实现的不错。接下来,我们可以来聊一下新组件了!” 老大 笑着说道
“新组件?这次要加什么?” A君 疑惑道
“虽然现在注解已经成为主流,但是作为一个成熟的框架,我们也不应该抛弃老技术。对于那些之前使用 Servlet 想迁移到新技术上的用户,我们不应该把他们拒之门外,所以,我们得对其进行兼容。所以需要有多个 控制器 ,不能仅仅只有注解!” 老大 说道
“明白了!” A君 回道,默默的开始思考后续的工作内容
从 Spring 的发展角度来看,Controller
接口应该是先于注解出现的,在没有注解的情况下,Spring 对 Servlet 进行了封装,提供抽象类,方面用户继承实现,还有就是让用户可以顺利从 Servlet 过渡到 Spring。这应该是 Spring 在没有注解时候的设计,自从注解出现后,这些Controller
存在的意义可能就是为了兼容老系统的迁移了。这里直接说成兼容老系统是考虑到文章的通顺,还有就是想尽量还原 Spring 的源码
兼容 Servlet?那还不容易,直接做个简单的适配即可。不过,考虑到 老大 提到可能有多个 控制器,以及之前反复强调:接口->抽象类->实现类。A君 决定还是先定义一个Controller
接口。代码如下:
/**
* 控制器接口
*/
@FunctionalInterface
public interface Controller {
/**
* 处理请求
*
* @param request
* @param response
* @return
* @throws Exception
*/
String handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;
}
接下来,就应该轮到抽象类了。在写抽象类的时候,A君 一阵头大。想要给用户好点的体验,就必须实现一堆东西,例如:是否兼容HTTP1.0、缓存、缓存过期时间、是否需要Session等。这工作量基本等同于支持完整的HTTP协议。这里限于篇幅,并且也不是重点,所以 A君 就看个简单的例子就行了。这里 A君 以响应头Allow
为例。HTTP协议对其定义如下:
简单的说,就是只允许客户端发起Allow
规定的请求,如果不在其规定之内,则返回405
。这里 A君 只允许GET
、POST
、HEAD
这三个请求。对于这些操作响应头的工作,A君 统一放在WebContentGenerator
类中。代码如下:
/**
* 响应请求头处理
*/
public abstract class WebContentGenerator {
public static final String METHOD_GET = "GET";
public static final String METHOD_HEAD = "HEAD";
public static final String METHOD_POST = "POST";
public WebContentGenerator(boolean restrictDefaultSupportedMethods) {
if (restrictDefaultSupportedMethods) {
this.supportedMethods = new LinkedHashSet<>(4);
this.supportedMethods.add(METHOD_GET);
this.supportedMethods.add(METHOD_HEAD);
this.supportedMethods.add(METHOD_POST);
}
initAllowHeader();
}
/**
* 检查请求头
*
* @param request
* @throws ServletException
*/
protected final void checkRequest(HttpServletRequest request) throws ServletException {
String method = request.getMethod();
if (this.supportedMethods != null && !this.supportedMethods.contains(method)) {
throw new RuntimeException("this method [" + method + "] is not contains:" + this.supportedMethods);
}
if (this.requireSession && request.getSession(false) == null) {
throw new RuntimeException("Pre-existing session required but none found");
}
}
//省略其他代码
好了,把响应头相关操作封装好之后,接下来就可以去实现抽象类了。抽象类主要就检查一下请求头,以及保证线程安全就行了。A君 新增AbstractController
类,代码如下:
public abstract class AbstractController extends WebContentGenerator implements Controller {
private boolean synchronizeOnSession = false;
@Override
public String handleRequest(HttpServletRequest request, HttpServletResponse response)
throws Exception {
if (HttpMethod.OPTIONS.matches(request.getMethod())) {
response.setHeader("Allow", getAllowHeader());
return null;
}
checkRequest(request);
prepareResponse(response);
/**
* session安全,必须同步
*/
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
return handleRequestInternal(request, response);
}
}
}
return handleRequestInternal(request, response);
}
protected abstract String handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
throws Exception;
}
抽象类也搞定了,接下来就是要对 Servlet 进行适配。适配其实也没什么东西,就是把 Servlet 相关的东西,包装一下就行了。A君 新增ServletWrappingController
类,代码如下:
public class ServletWrappingController extends AbstractController
implements BeanNameAware, InitializingBean {
private Class<? extends Servlet> servletClass;
private String servletName;
private Properties initParameters = new Properties();
private String beanName;
private Servlet servletInstance;
@Override
public void afterPropertiesSet() throws Exception {
if (this.servletClass == null) {
throw new IllegalArgumentException("'servletClass' is required");
}
if (this.servletName == null) {
this.servletName = this.beanName;
}
this.servletInstance = ReflectionUtils.accessibleConstructor(this.servletClass).newInstance();
this.servletInstance.init(new DelegatingServletConfig());
}
@Override
protected String handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
throws Exception {
this.servletInstance.service(request, response);
return null;
}
//省略其他代码
}
添加对应的映射器
好了,这个类足以兼容 Servlet,不过,还有需要加个 映射器,理论上一个 控制器 要对应一个 映射器,之前那个RequestMappingHandlerMapping
,是用来处理注解的。现在是Controller
,所以得新增一个 映射器。A君 新增SimpleUrlHandlerMapping
类,逻辑很简单,维护个Map
就行了。代码如下:
public class SimpleUrlHandlerMapping extends AbstractUrlHandlerMapping {
private final Map<String, Object> urlMap = new LinkedHashMap<>();
/**
* 注册所有处理器
*
* @param urlMap
*/
protected void registerHandlers(Map<String, Object> urlMap) {
if (!urlMap.isEmpty()) {
urlMap.forEach((url, handler) -> {
if (!url.startsWith("/")) {
url = "/" + url;
}
// Remove whitespace from handler bean name.
if (handler instanceof String) {
handler = ((String) handler).trim();
}
registerHandler(url, handler);
});
}
}
}
改造DispatcherServlet
好了,现在还差点东西,之前在实现DispatcherServlet
时,只有一个孤零零的RequestMappingHandlerMapping
,是时候他变成集合了。改动如下:
init
方法:
doDispatch
方法:
测试
这回是彻底完事了,简直白给嘛(如果把那些万恶的响应头去掉)。好了,不管这么样,A君 也实现了。接来下就是测试环节了。A君 新增个HelloServlet
类,代码如下:
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println(this);
request.setAttribute("message", "Servlet");
RequestDispatcher dispatcher = request.getRequestDispatcher("/views/hello.jsp");
dispatcher.forward(request, response);
}
}
其余部分不需要改动,测试代码如下:
@Test
public void v33() throws Throwable {
System.out.println("############# 第三十三版: 控制器篇 #############");
Tomcat tomcat = new Tomcat();
//设置端口
tomcat.setPort(8082);
//设置静态资源路径
String webApp = new File("src/main/resources/v33").getAbsolutePath();
Context context = tomcat.addWebapp("/test/", webApp);
tomcat.start();
//挂起
tomcat.getServer().await();
}
测试结果如下:
好了,今天的活也完工了,A君 收拾包裹准备回家咯
总结
正所谓树欲静而风不止,欲知后事如何,请看下回分解(✪ω✪)