上学期买了本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
概述
Tomcat是由很多个组件构成的,单以Container为例,内部就包含了有Engine、Host、Context、Wrapper,然后以Context为例,内部又包含了有Loader、Manager、Pipline、Filters这些杂七杂八的组件
所有的这些组件都有一个生命周期:他们在被创建的时候需要做什么操作,初始化自己的哪些属性,调用自己的哪些组件哪些方法,他们在被销毁的时候还需要做什么,如何按照次序关闭所有组件,更重要的就是,我们怎么统一操控所有组件,让他们在服务器启动的时候一起开始,一起结束…
在Tomcat源码中,他使用到了生命周期管理的方式来实现这些功能,这里主要涉及到了以下两种设计模式
两种设计模式简介
这里我会简单介绍这两种设计模式,然后结合Tomcat中的使用来谈谈
观察者模式
基本介绍
观察者模式,又被称为发布-订阅
模式,就是,当被观察对象(也被称为主题
)做出某个动作的时候,会发出一条消息给所有观察者,观察者就执行相应的观察操作
看这样一个例子:
被观察者:老师
观察者:10个学生
当老师做出
写板书
这个动作的时候,会喊所有同学“看黑板”
(发送消息给观察者),所有学生得到消息之后就开始记笔记
了(观察者的动作)
那么,用代码来表示大概就是这样的,大概有4个类:
抽象主题角色:添加、删除、通知某个观察者
public interface AbstractSubject {
public void addObserver(AbstractObserver observer);
public void removeObserver(AbstractObserver observer);
public void notification();
}
抽象观察者角色:其实就是给别人暴露一个激活动作的接口,等主题对象来触发这个方法
public interface AbstractObserver {
public void update();
}
具体主题角色**:就是上文中的老师,这里多了一个保存观察者们的容器List
public class ConcreteSubject implements AbstractSubject {
List<AbstractObserver> observers = new ArrayList<AbstractObserver>();
@Override
public void addObserver(AbstractObserver observer) {
observers.add(observer);
}
@Override
public void removeObserver(AbstractObserver observer) {
observers.remove(observer);
}
@Override
public void notification() {
observers.forEach(AbstractObserver::update);
}
}
具体观察者角色:就是上文中的学生,他的触发方法就是上文中的做笔记动作,被老师触发
public class ConcreteObserver implements AbstractObserver {
@Override
public void update() {
System.out.println("我在做笔记")
}
}
模拟一下主程序:
public static final void main(String[] args)
{
//一个老师
AbstractSubject teacher = new ConcreteSubject();
//他的观察者是5个学生
teacher.addObserver(new ConcreteObserver);
teacher.addObserver(new ConcreteObserver);
teacher.addObserver(new ConcreteObserver);
teacher.addObserver(new ConcreteObserver);
teacher.addObserver(new ConcreteObserver);
//通知观察者
teachaer.notification();
}
就这个意思
Tomcat中的使用
每个组件都是一个被观察者(主题),然后其他有些程序就作为观察者,当被观察的组件的生命周期进入到下一个阶段的时候,执行某些特定操作
举个例子,在Tomcat源码中搜索的结果(可能我没有找全):
在执行某些组件的某个生命周期的时候,需要执行一些其他程序
不过感觉我目前写的过程中好像没有实际遇到需要写一个观察者的情况,但是How Tomcat Works里面有一个例子,我会在后面贴上
组合模式
基本介绍
先看这样一个场景:
你有一个文件夹,里面有很多子文件夹和文件,现在需要在所有文件夹中寻找内容上带有’xxx’的文件
这种情况下,我们写Java代码来实现这个功能的话,代码大概如下:
File file = new File("path");
File[] files = file.listFiles();
files.forEach(f -> {
if (f.isDirectory()) {
//递归进入文件夹继续执行
}
if (f.isFile()) {
//检查文件
}
});
这里我们可以发现,虽然都被统称为File,但其实他包含了两种东西:文件目录(文件夹)
、 文件
当我们需要粗糙地了解文件夹里大概有什么的时候,可以都把这两类归为一种类来看,就当做File,然后画出树状图
但是当我们需要具体对某个文件本身进行读写操作的时候,我们就会通过某个标志位来判断File是属于哪一种,然后分别进行不同操作
这其实就是组合模式:
组合模式(Composite Pattern),又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。
这种模式创建了一个包含自己对象组的类。该类提供了修改相同对象组的方式。
这里再引入两个概念:容器节点和叶子节点
如果能够让容器节点(上文中的文件夹)和叶子节点(上文中的文件本身)在执行某些操作的时候被统一操作(都用File表示),这就叫组合模式
Tomcat中的使用
在Tomcat中,仅仅针对生命周期的设计来说,Tomcat使用组合模式,使得所有的容器(本身有功能而且还包含了有组件的)和组件(里面没有再包含其他东西的)都去实现了生命周期的Lifecycle,然后当最顶层的容器开始生命周期变化时,会调用子容器的方法,让子容器也发生生命周期变化…依次导致全部容器组件的生命周期都统一变化,实现了改变顶层容器状态就自动更改了所有子类容器的效果
举个例子:
Host容器调用了stop方法,生命周期进入STOP状态
Host的stop方法中会调用子容器Context的stop方法,使得所有的子容器Context也执行STOP
对于Context容器来说,被触发执行stop之后,他的stop方法也会去让所有自己管理的子组件和Wrappers执行STOP动作
Wrappers执行stop,销毁Servlet
Context其他的组件比如Loader、Manager也执行stop进行停止工作
…
就这样,我们调用了一个Host(顶层容器)的停止方法,他就改变了他里面所有子容器和组件的生命周期,让整个Host全部停止下来
涉及到的类
Lifecycle接口
这就是观察者模式里的抽象主题
接口:
package tiny.lehr.tomcat.lifecircle;
import java.util.List;
/**
* 抽象主题
*/
public interface TommyLifecycle {
String START_EVENT = "start";
String BEFORE_START_EVENT = "before_start";
String BEFORE_INIT_EVENT = "before_init";
String AFTER_INIT_EVENT = "after_init";
String AFTER_START_EVENT = "after_start";
String STOP_EVENT = "stop";
String BEFORE_STOP_EVENT = "before_stop";
String AFTER_STOP_EVENT = "after_stop";
void addLifecycleListener(TommyLifecycleListener listener);
List<TommyLifecycleListener> findLifecycleListeners();
void removeLifecycleListener(TommyLifecycleListener listener);
void start() throws Exception;
void stop() throws Exception;
}
LifecycleEvent类
这个类继承了JavaSE里的EventObject,代表的其实就是,发生的事件是什么
package tiny.lehr.tomcat.lifecircle;
import java.util.EventObject;
/**
* 生命周期事件
*/
public final class TommyLifecycleEvent extends EventObject {
public TommyLifecycleEvent(TommyLifecycle lifecycle, String type) {
this(lifecycle, type, null);
}
public TommyLifecycleEvent(TommyLifecycle lifecycle, String type, Object data) {
super(lifecycle);
this.lifecycle = lifecycle;
this.type = type;
this.data = data;
}
private Object data = null;
private TommyLifecycle lifecycle = null;
private String type = null;
public Object getData() {
return (this.data);
}
public TommyLifecycle getLifecycle() {
return (this.lifecycle);
}
public String getType() {
return (this.type);
}
}
LifecycleListenrer接口
这个就是抽象观察者接口
package tiny.lehr.tomcat.lifecircle;
/**
* 抽象观察者
*/
public interface TommyLifecycleListener {
void lifecycleEvent(TommyLifecycleEvent event);
}
LifecycleSupport类
LifecycleSupport类其实就是一个监听器管理器,内置了一个List来存放观察者,然后封装了各种remove add的时候对List的遍历操作
Container容器里会关联一个这个类,然后当Container实现Lifecycle接口的方法的时候,可以直接从这个类里面调用他封装好了的方法(具体看下文Container的代码)
package tiny.lehr.tomcat.lifecircle;
import java.util.ArrayList;
import java.util.List;
/**
* 生命周期管理的实例类
* 提供对观察者的添加,删除及通知观察者的方法。(Tomcat release 9.0.x版本中没有用到这个)
*/
public final class TommyLifecycleSupport {
private TommyLifecycle lifecycle;
//内置的监听器些 原版用的数组,我觉得怪麻烦的,所以就用list了 TODO:为啥他喜欢用数组?
private List<TommyLifecycleListener> listeners = new ArrayList<>();
public TommyLifecycleSupport(TommyLifecycle lifecycle) {
this.lifecycle = lifecycle;
}
//添加监听器,该方法中动态数组的方法值得借鉴
public void addLifecycleListener(TommyLifecycleListener listener) {
synchronized (listeners) {
listeners.add(listener);
}
}
public List<TommyLifecycleListener> findLifecycleListeners() {
return listeners;
}
//这个其实就是通知大伙
public void fireLifecycleEvent(String type, Object data) {
TommyLifecycleEvent event = new TommyLifecycleEvent(lifecycle, type, data);
synchronized (listeners) {
listeners.forEach(l->l.lifecycleEvent(event));
}
}
public void removeLifecycleListener(TommyLifecycleListener listener) {
synchronized (listeners) {
listeners.forEach(l->{
if(listener.equals(l))
{
//这个remove的方法的实现其实就是遍历然后对比
//TODO: https://www.jianshu.com/p/c1fb6a26a136 这里有个有趣的情况
listeners.remove(l);
}
});
}
}
}
最后讲一下:在Tomcat源码中还有一个叫做LifecycleException的类,用来代表错误,我没去实现,所以后文也不会写到这个
完整过程案例
Container类实现lifecycle接口
Container类是Host、Engine、Wrapper、Context的超类,所以只需要让超类实现这个接口,这些子容器就能够执行生命周期动作了
由于Host、Engine、Wrapper、Context比较重要,所以他们有可能需要配置多个监听器,所以Tomcat写了LifecycleSupport类
,作为一种类似工具类的东西,内置提供了一个监听器List,使得通过LifecycleSupport类可以直接对整个监听器List进行唤醒,也可以单个添加和删除
代码
我实现的Container类
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;
}
public void invoke(MyRequest req, MyResponse res) {
addBasicValve();
pipeline.invoke(req, res);
}
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;
}
}
给组件实现lifecycle接口
也一样的,让某个组件实现lifecycle接口,只不过如果需要准备一个监听器List则需要自己写
下面以Pipline为例:
代码
package tiny.lehr.tomcat;
import tiny.lehr.bean.MyRequest;
import tiny.lehr.bean.MyResponse;
import tiny.lehr.tomcat.lifecircle.TommyLifecycle;
import tiny.lehr.tomcat.lifecircle.TommyLifecycleListener;
import tiny.lehr.tomcat.valve.TommyValve;
import java.util.ArrayList;
import java.util.List;
/**
* @author Lehr
* @create 2020-01-16
* 这个就是里面的管道流水线工程
*/
public class TommyPipeline implements TommyLifecycle {
/**
* 内部维护了一组阀门
* 源码是用数组配一个内部类ValveContext控制来实现的
* 然而我并不知道他为什么要这样做,所以我就选择直接List了
*/
List<TommyValve> valveList = new ArrayList<>();
/**
* 基础阀任务,就是这个容器的核心任务
*/
TommyValve basicValve;
/**
* 向管道里添加一个阀
* @param valve
*/
public void addValve(TommyValve valve)
{
valveList.add(valve);
}
/**
* 启动管道任务:先启动每个普通阀,再启动基础阀
* @param req
* @param res
*/
public void invoke(MyRequest req, MyResponse res)
{
//逐个invoke
valveList.forEach(v->v.invoke(req,res));
//执行基础阀
basicValve.invoke(req,res);
}
/**
* 设置基础阀
* @param basic
*/
public void setBasicValve(TommyValve basic)
{
this.basicValve = basic;
}
//TODO:!!!!
@Override
public void addLifecycleListener(TommyLifecycleListener listener) {
}
@Override
public List<TommyLifecycleListener> findLifecycleListeners() {
return null;
}
@Override
public void removeLifecycleListener(TommyLifecycleListener listener) {
}
@Override
public void start(){
System.out.println("我开始了");
}
@Override
public void stop(){
System.out.println("我裂开了");
}
}
编写生命周期执行时的逻辑代码
主要其实就是Lifecycle接口里的start和stop方法,由于我抽象了一下,所以具体对于每个容器的主要逻辑代码是在doStart和doStop方法里的
以Context的为例,在start的时候,他会逐个加载自己的子容器和组件,然后调用他们的start方法
同理,在stop的时候,就是反过来
代码
以Context的为例:我选取了部分:
@Override
protected void doStart() throws Exception {
//FIXME:这里还有个设计:如果报错了的话,有一个叫做available的布尔值会为false然后本容器是无法调用的
isPaused = true;
//准备好session管理器
sessionManager = new TommySessionManager(this);
sessionManager.start();
//通过路径准备好类加载器,热加载默认关闭
loader = new TommyWebAppLoader(appPath, true, this);
loader.start();
//解析web.xml内容
context = new TommyContextConfig(appPath);
//获取域对象
servletContext = new TommyServletContext(context.getContextInitParameters(), appPath);
//加载所有的filter
initAllFilter(context.getFilterConfigMap());
//初始化子容器
loadWrappers();
//一个一个启动子容器
//我这里没有去做类型判断,因为我觉得这样好像没有毛病
wrappers.values().forEach(TommyWrapper::start);
//现在就是可以访问的了
isPaused = false;
//开始后台线程
threadStart();
}
@Override
protected void doStop() {
isPaused = true;
//先把后台线程停止了,这里用的是tomcat的源码的处理方法
threadStop();
//停止子容器
wrappers.values().forEach(TommyWrapper::stop);
//销毁Filter
filterPool.values().forEach(TommyFilterConfig::destory);
}