现在tomcat源码量非常大,想读懂那么多人写的代码很耗时间,这里记下我学习tomcat的一点笔记
Tomcat是Web服务器
几个月前我本科没毕业的时候,还不知道web服务器和应用服务器的区别
用我自己的话描述,自己的程序里带jar文件来支持JavaEE组件的是web服务器
从socket开始
我觉得很多中间件源码读起来很困难,因为我们只看到了最终的产品,中间发展的过程以及增加的功能我们并不清楚.
Tomcat现在的源码量相当的大,即使读一些解读的文章再调试也不容易理解透彻,而且在业务团队里也没必要死抠每个技术细节,所以我觉得应该循序渐进
我先写一个最简单的Socket,启动时只要用浏览器访问 localhost:8080 就可以看到socket 传来的信息,并能看返回当前系统时间收到的消息
返回的结果
Socket改进
一个socket虽然很简单,但我们可以明白HTTP协议的原理
如果我们把它改进一下,input封装到Request里,output封装到response里,就好理解多了
我在github上找到一个挺有意思的东东
我稍微改动了一下这个代码,让它变得更直观些
它就是把我上面写的代码改成线程池管理,并把socket通过handler传给线程,分为Request和Response,支持了本地文件的读写
RequestHandler处理过程
执行效果
虽然和上面的代码相比不过就是封装了一下罢了
但线程池把socket监听请求以及io流的处理给分开了,其实这个过程就是tomcat的connector和container通过HttpProcessor传递socket的过程的简化,Main里管线程池的代码我们可以封装到一个叫Connector里面,有请求就从池子里取个线程,让它合成Request和Response,把它上交给.....容器
Classloader原理
其实做到上一步,基本可以把这坨代码当webServer跑了,但它不支持servlet,也没有webapps目录和Context额,而且我们都是打war包的,war包里的那些类tomcat是怎么调用的?
因为tomcat的classloader是封装到Context里面的,这个加载过程不好解释,我就写一个简单的例子说明一下:
这个目录结构相当简单,folder下有个编译好的B类,我们启动tomcat然后启动服务的过程可以视为Main类跑时把B类也加载进去
为了更有说服力,我们可以先ps -ef|grep java 确定 folder不在我们的classpath里面
然后VM参数加上-verbose:class 亲眼看看B这个class文件是怎么被加载进去的
在学校的时候配java环境变量,总是要把rt.jar 这堆玩意加到classpath里,时间久了基本背下来要加哪些包了
Java的classloader分为 bootstrap classloader,extension classloader和system classloader,其实rt.jar里的那些java核心类就是bootstrap classloader给加进去的,我们可以执行以下这个代码看看bootstrap classloader都加载了什么
Tomcat结构
写了这么多却一直没提tomcat有些三纸无驴的感觉,但其实写到这里,我们自己就能写个非常粗陋的支持servlet的webServer类似物了
我学习tomcat源码不仅是因为好奇,为了方便定位问题,也是为了了解一点它的设计思想
我画了个草图,并按自己立即标注了一下这些组件是干啥的,大致说明一下tomcat的结构
当然,这里面还有一些Loader, Pipeline ,Valve ,Repository 这类的小东西我没标,不然图就太乱了
由一个Socket突然变得这么复杂稍微有点过度不自然,也没关系,我在万能的github上又找到一个过度自然的项目
这个项目麻雀虽小五脏俱全,虽然没Server.xml配置文件,但tomcat该有的它基本都有,只是容器只有Context和Wrapper,但看懂这个基本就明白tomcat的设计思想了
这个项目下面有个Mushroom的项目,主项目跑起来,会把Mushroom放到Context容器里,并通过Loader加载里面编译好的class
这里没有filter,所以如果访问的uri直接能找到文件就直接返回文件内容,找不到就会从ContextValve的map里找加载好的Servlet然后invoke调用
这里面还有个LifeCycle接口,就是咱们常说的生命周期,每个容器都实现了这个接口里的方法
我读源码的办法
我是先debug,边debug边看涉及到的类结构,因为这是多线程的,单纯debug不是很好理解
我把主要的类抠出来,只保留类里重要的域,再debug一遍就能明白类之间的调用关系