[7]菜鸡写Tomcat之Container
上学期买了本How Tomcat Works然后一直丢在书柜里没看,之前有一天闲,翻出来看了几页,觉得挺有趣的,所以就跟着书上的思路和一些tomcat源码,自己写了一个简单的应用服务器—Tiny Server
感觉在这个过程中学到了不少东西,所以想把自己的思路和想法写出来分享一下
Ps:因为是自己的理解,所以如有不对请各位大佬指出,感激不尽
我写的Tiny Server的源码在这里:https://github.com/Lehr130/TinyServer
整个项目并没有完全按照tomcat的写法来,只是按照自己的理解去实现的,而且有些功能并没有完全实现,还有待各位指教
文章目录
相关内容
[7]菜鸡写Tomcat之Container
[6]菜鸡写Tomcat之Cookie与Session
[5]菜鸡写Tomcat之WebClassloader
[4]菜鸡写Tomcat之生命周期控制
[3]菜鸡写Tomcat之Filter
[2]菜鸡写Tomcat之Context
[1]菜鸡写Tomcat之Wrapper
什么是Servlet容器
Tomcat的结构
Tomcat大体可以分为两个部分:Connector和Container
Tomcat中一个完整的Service服务是由Connector和Container组成的,首先,Connector接受请求并封装,然后按照请求,找到对应的Container容器,执行这个容器里的任务。
仔细观察可以发现,Connector和Container的对应关系是多对一,一个Service中只能有一个Container
在Connector部分,我们只需要去关心怎么样处理请求,接受Http请求或者AJP请求,然后,我们把封装成为ServletRequest的请求统一交给Container处理
Container的职能
一个JavaWeb项目是由若干个Servlet小程序组成的,项目的每个部分都需要被放到容器里
tomcat中用了四个容器:Wrapper、Context、Host和Engine来管理不同层级的资源
其中:
-
Wrapper容器就管理了一个Servlet小程序和他所有的相关信息,除了Servlet以外不能有其他子容器了
-
Context容器就管理了一个JavaWeb项目和他的各种配置文件参数,有多个Wrapper作为子容器
-
Host容器就代表了一个虚拟主机,可以配置多个Context作为子容器
-
Engine则代表了整个Tomcat引擎,名为Catalina,可以有多个Host子容器,但是Engine本身有且只有一个
部署的时候,我们至少需要有一个Wrapper容器,就能让一个Servlet程序正常工作了
但是如果需要同时调用多个Servlet,则需要用Context…需要匹配多个主机,则准备多个Host…
Container接口
这几个容器通过关联
的方式(类似于Classloader)组成了父子关系:
无论是哪个层级的容器,他们在管理的过程中都会有类似的操作,比如添加子容器,准备管道任务等等,同时,每个容器还会有自己的一些特有的组件,比如类加载器,安全管理器等等,所以,他们需要统一地实现一个叫做Container的接口
接口需要做的事情大概有这样几件事:
- 能够执行自己对应的任务
- 支持对子容器的管理
在Container中,执行自己层级的任务时,会调用到invoke接口,invoke接口则会把请求发送给容器内部一个叫做管道任务的组件来执行
在具体谈Container之前,我们需要了解Tomcat中管道任务的设计思路
管道任务
Tomcat在设计每个容器invoke时要执行的任务的时候,采用了责任链的设计模式,也就是说,一个容器内维持着一条管道,管道由多个阀组成,其中每一个阀就是一个任务,如果我们需要执行这个容器的invoke方法,那么任务就会像水流过管道一样,在每个阀中触发对应的任务执行相应的操作,不了解的读者可以用FilterChain来类比
管道(Pipline)和阀(Valve)
管道其实就类似一个任务表,如果容器在被触发的时候,需要先后按照某种顺序完成一些特定的动作,例如先进行日志记录,然后再进行安全检查,然后才触发Servlet的service方法执行业务逻辑,则我们可以把这一系列的任务抽象封装成一个叫做阀(Valve)的组件,按照你希望的执行顺序,依次放入管道中排列好,一旦触发请求,则管道里排放的任务就能够一个接着一个执行了
管道的实现代码简化后类似于这样:
public class TommyPipeline {
private List<TommyValve> valveList = new ArrayList<>();
private TommyValve basicValve;
public void addValve(TommyValve valve)
{
valveList.add(valve);
}
public void invoke(MyRequest req, MyResponse res)
{
//逐个invoke
valveList.forEach(v->v.invoke(req,res));
//执行基础阀
basicValve.invoke(req,res);
}
public void setBasicValve(TommyValve basic)
{
this.basicValve = basic;
}
逻辑很简单,内部维护了一个代表任务的valve的list(在原版Tomcat中是用数组来实现的)
提供了valve阀的添加方法和一个叫做基础阀
的东西的设置方法
在执行invoke触发操作的时候,管道会逐个触发Valve阀中的任务,最后触发基础阀
中的任务
这里需要注意一下的就是,管道任务只会顺序执行一次,而不会像FilterChain一样又反着执行回来
现在来看一下Valve阀门这个东西的代码:
public interface TommyValve {
/**
* 每个阀的执行方法,必须把方法返回结果放到res里面去
* @param req
* @param res
*/
void invoke(MyRequest req, MyResponse res);
}
“阀”实际上就是一个接口,如果想要放入到管道里去执行的话必须就要实现这个invoke方法,在invoke方法中写入你想执行的业务逻辑(这倒是有点策略模式的感觉…)
上文说到的基础阀就是这个接口的一个实现类
阀的使用方法也很简单粗暴,比如说,我们想在容器执行任务的时候先输出一句"Hello",那么我们就写一个叫做HelloValve的类去实现这个Valve接口,然后在invoke方法里写一句sout(“Hello”),然后把这个阀的实现类通过管道pipline的add方法加入进去就好了
基础阀(Basic Valve)
基础阀,就是整个容器的任务的核心逻辑了
实现类
比如对于管理Servlet级别的Wrapper容器,他被调用的时候本质就是要触发Servlet的service方法,所以,我们会把service方法的调用过程单独放到一个实现了Valve接口的类里去,然后把这个对象放入到当前容器所包含的最后一环去,当前面杂七杂八的什么日志任务,安全检查任务完成后,就会正式触发service方法,servlet程序就开始执行他自己的工作然后把响应发回去了
在Tomcat中,他是为每一个容器单独写了一个对应的基础阀的类的,我们以Wrapper为例:
在Wrapper中,他的管道任务的最后一个环节基础阀放入的对象是一个叫做WrapperValve的类,这个类的代码大致如下:
public class WrapperValve implements TommyValve {
.....各种set get之类的
void invoke(MyRequest req, MyResponse res)
{
servlet = allocate();
servlet.service();
};
}
对于高级容器
我们以Context容器为例,Context容器最核心的业务逻辑就是:通过uri去找到对应的Wrapper,然后触发这个wrapper的invoke方法,封装到一个阀里以后:
public class ContextValve implements TommyValve {
.....各种set get之类的
void invoke(MyRequest req, MyResponse res)
{
Wrapper wrapper = req.getWrapper();
wrapper.invoke(req,res);
};
}
这倒是没什么,不过我们从整体结构上看,当有多层容器的时候,执行任务的流程大概是这样:
好了我尽力了,有嘴讲不清,,,以后再来修改,,,,
我的TinyServer里的实现代码
TinyServer是我个人在读tomcat源码的时候模仿着写的一个非常简单的玩具,所以很多设计结构上都比tomcat简陋很多,由于上文是用到了自己的代码来做的分享,所以这里附上我对于Wrapper级容器的实现的源码,完整项目见https://github.com/Lehr130/TinyServer
我直接把原版中Container接口和ContainerBase类rua到一起,写成了一个抽象类,然后把每个容器对应的基础阀直接改造成了内部类来处理,代码如下:
package tiny.lehr.tomcat.container;
import tiny.lehr.bean.MyRequest;
import tiny.lehr.bean.MyResponse;
import tiny.lehr.tomcat.TommyPipeline;
import tiny.lehr.tomcat.lifecircle.*;
import tiny.lehr.tomcat.valve.TommyValve;
import java.util.List;
/**
* @author Lehr
* @create 2020-01-16
* 模仿Tomcat的那个Container设计
*
* <p>
* 是个标识性接口
*/
public abstract class TommyContainer implements TommyLifecycle {
//一个监听器管理器???
private TommyLifecycleSupport lifecycle = new TommyLifecycleSupport(this);
private TommyPipeline pipeline = new TommyPipeline();
protected TommyLifecycleSupport getLifecycle() {
return lifecycle;
}
/**
*
*
* @param req
* @param res
*/
public void invoke(MyRequest req, MyResponse res) {
addBasicValve();
pipeline.invoke(req, res);
}
/**
*
*
* @param valve
*/
public void addValve(TommyValve valve) {
pipeline.addValve(valve);
}
public void addBasicValve() {
pipeline.setBasicValve(new BasicValve());
}
/**
* 定义了基础阀的工作过程
*
* @param req
* @param res
*/
protected abstract void basicValveInvoke(MyRequest req, MyResponse res);
/**
*
*/
class BasicValve implements TommyValve {
@Override
public void invoke(MyRequest req, MyResponse res) {
basicValveInvoke(req, res);
}
}
@Override
public void addLifecycleListener(TommyLifecycleListener listener) {
lifecycle.addLifecycleListener(listener);
}
@Override
public List<TommyLifecycleListener> findLifecycleListeners() {
return lifecycle.findLifecycleListeners();
}
@Override
public void removeLifecycleListener(TommyLifecycleListener listener) {
lifecycle.removeLifecycleListener(listener);
}
private Boolean started = false;
//子类具体要做的
protected abstract void doStart() throws Exception;
protected void beforeStart(){
if(this instanceof TommyWrapper)
{
addLifecycleListener(new TommyWrapperLifecycleListener());
}
if(this instanceof TommyContext)
{
addLifecycleListener(new TommyContextLifecycleListener());
}
if(this instanceof TommyHost)
{
addLifecycleListener(new TommyHostLifecycleListener());
}
if(this instanceof TommyEngine)
{
addLifecycleListener(new TommyEngineLifecycleListener());
}
}
//因为接口不能用synchronized 所以要具体实现过来
@Override
public synchronized void start(){
if (started) {
System.out.println("md都开始了还搞锤子");
return;
}
//我设计这个是为了一开始好添加监听器的
beforeStart();
lifecycle.fireLifecycleEvent(BEFORE_START_EVENT,null);
started = true;
//我这里又抽象了一次哈
try {
doStart();
} catch (Exception e) {
e.printStackTrace();
}
//启动阀门
pipeline.start();
//通知所有监听器
lifecycle.fireLifecycleEvent(START_EVENT, null);
lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);
}
protected abstract void doStop();
@Override
public synchronized void stop(){
if (!started) {
System.out.println("md都结束了还搞锤子");
}
lifecycle.fireLifecycleEvent(BEFORE_STOP_EVENT,null);
lifecycle.fireLifecycleEvent(STOP_EVENT,null);
started = false;
pipeline.stop();
//停止子容器 但是我不知道为什么之而立是先stop event再去停止
doStop();
//TODO: 这里还有报错需要改进
lifecycle.fireLifecycleEvent(AFTER_STOP_EVENT, null);
}
public Boolean isStarted()
{
return started;
}
}
下一篇: IDEA配置GIT