Tomcat启动分析(七) - 容器组件

本文及后续几篇文章开始分析容器组件,它们都与请求处理息息相关,首先介绍容器组件共同的父类Container接口及ContainerBase抽象类。

容器组件

容器组件处理客户端请求并返回响应,Tomcat中的容器组件按从高到低的顺序有以下四个层次:

  • Engine:表示整个Catalina servlet引擎,可以包含一个或多个子容器,如Host或者Context等;
  • Host:表示虚拟主机,包含Context;
  • Context:表示ServletContext,包含一个或多个Wrapper;
  • Wrapper:表示单独的servlet定义。

需要注意的是,Catalina不需要包括所有上述容器组件,所以容器组件的实现必须被设计成在没有父容器的情况下依然可以正确运行。

Container接口

Container接口继承了Lifecycle接口,表示Servlet容器相关组件的抽象,类层次结构如下图所示:

Container类层次结构.png

public interface Container extends Lifecycle {
    // ----------------------------------------------------- Manifest Constants
    public static final String ADD_CHILD_EVENT = "addChild";
    public static final String ADD_VALVE_EVENT = "addValve";
    public static final String REMOVE_CHILD_EVENT = "removeChild";
    public static final String REMOVE_VALVE_EVENT = "removeValve";
    // ------------------------------------------------------------- Properties
    public Log getLogger();
    public String getLogName();
    public ObjectName getObjectName();
    public String getDomain();
    public String getMBeanKeyProperties();
    public Pipeline getPipeline();
    public Cluster getCluster();
    public void setCluster(Cluster cluster);
    public int getBackgroundProcessorDelay();
    public void setBackgroundProcessorDelay(int delay);
    public String getName();
    public void setName(String name);
    public Container getParent();
    public void setParent(Container container);
    public ClassLoader getParentClassLoader();
    public void setParentClassLoader(ClassLoader parent);
    public Realm getRealm();
    public void setRealm(Realm realm);
    // --------------------------------------------------------- Public Methods
    public void backgroundProcess();
    public void addChild(Container child);
    public void addContainerListener(ContainerListener listener);
    public void addPropertyChangeListener(PropertyChangeListener listener);
    public Container findChild(String name);
    public Container[] findChildren();
    public ContainerListener[] findContainerListeners();
    public void removeChild(Container child);
    public void removeContainerListener(ContainerListener listener);
    public void removePropertyChangeListener(PropertyChangeListener listener);
    public void fireContainerEvent(String type, Object data);
    public void logAccess(Request request, Response response, long time, boolean useDefault);
    public AccessLog getAccessLog();
    public int getStartStopThreads();
    public void setStartStopThreads(int startStopThreads);
    public File getCatalinaBase();
    public File getCatalinaHome();
}
  • 一个容器可以包含其他容器,可以通过addChild、findChild、findChildren、removeChild和setParent等接口方法进行操作;
  • 通过addContainerListener、findContainerListeners和removeContainerListener等接口方法可以向容器组件添加容器事件监听器ContainerListener,还可以通过fireContainerEvent方法发布容器事件;
  • 通过addPropertyChangeListener和removePropertyChangeListener可以向容器组件添加属性值改变监听器PropertyChangeListener;
  • 注意Container接口继承了Lifecycle接口,Lifecycle接口添加的是生命周期事件监听器LifecycleListener,因此容器组件包含三种监听器:生命周期事件监听器LifecycleListener、容器事件监听器ContainerListener和属性值改变监听器PropertyChangeListener。

ContainerBase抽象类

ContainerBase抽象类是Container接口的默认实现,它继承了LifecycleBase类,同时也是其他容器组件如StandardEngine、StandardHost和StandardContext等的父类,其部分成员变量如下:

public abstract class ContainerBase extends LifecycleMBeanBase
        implements Container {
    // 省略一些代码
    /**
     * The child Containers belonging to this Container, keyed by name.
     */
    protected final HashMap<String, Container> children = new HashMap<>();
    protected final List<ContainerListener> listeners = new CopyOnWriteArrayList<>();
    protected String name = null;
    protected Container parent = null;
    protected ClassLoader parentClassLoader = null;
    protected final Pipeline pipeline = new StandardPipeline(this);
    /**
     * The number of threads available to process start and stop events for any
     * children associated with this container.
     */
    private int startStopThreads = 1;
    protected ThreadPoolExecutor startStopExecutor;

    @Override
    public int getStartStopThreads() {
        return startStopThreads;
    }

    /**
     * Handles the special values.
     */
    private int getStartStopThreadsInternal() {
        int result = getStartStopThreads();
        // Positive values are unchanged
        if (result > 0) {
            return result;
        }
        // Zero == Runtime.getRuntime().availableProcessors()
        // -ve  == Runtime.getRuntime().availableProcessors() + value
        // These two are the same
        result = Runtime.getRuntime().availableProcessors() + result;
        if (result < 1) {
            result = 1;
        }
        return result;
    }

    @Override
    public void setStartStopThreads(int startStopThreads) {
        this.startStopThreads = startStopThreads;
        // Use local copies to ensure thread safety
        ThreadPoolExecutor executor = startStopExecutor;
        if (executor != null) {
            int newThreads = getStartStopThreadsInternal();
            executor.setMaximumPoolSize(newThreads);
            executor.setCorePoolSize(newThreads);
        }
    }
}
  • children变量是一个以子容器组件名为键、子容器组件自身为值的Map;
  • listeners变量保存了添加到该容器组件的容器事件监听器,使用CopyOnWriteArrayList的原因请参见《Effective Java》第三版第79条;
  • name变量是该容器组件的名字;
  • parent变量引用该容器组件的父容器组件;
  • pipeline变量引用一个StandardPipeline,为什么要有一个Pipeline呢?从ContainerBase的类注释可以得到答案:子类需要将自己对请求的处理过程封装成一个Valve并通过Pipeline的setBasic方法安装到Pipeline;
  • startStopExecutor表示启动和停止子容器组件用的线程池,startStopThreads表示这个线程池的线程数。
  • Use local copies to ensure thread safety 这句注释没看明白,即使用了局部变量也是同一个引用。

组件初始化

@Override
protected void initInternal() throws LifecycleException {
    BlockingQueue<Runnable> startStopQueue = new LinkedBlockingQueue<>();
    startStopExecutor = new ThreadPoolExecutor(
            getStartStopThreadsInternal(),
            getStartStopThreadsInternal(), 10, TimeUnit.SECONDS,
            startStopQueue,
            new StartStopThreadFactory(getName() + "-startStop-"));
    startStopExecutor.allowCoreThreadTimeOut(true);
    super.initInternal();
}
  • 初始化过程为容器组件自己创建了一个线程池,该线程池用于执行子容器的启动和停止等过程;
  • startStopExecutor里的每个线程的名称是“容器组件名称-startStop-”再加上线程计数,这在Tomcat启动的日志中有所体现,如Host组件启动时日志会有如“localhost-startStop-1”字样的输出。

组件启动

@Override
protected synchronized void startInternal() throws LifecycleException {
    // 省略一些代码
    // Start our child containers, if any
    Container children[] = findChildren();
    List<Future<Void>> results = new ArrayList<>();
    for (int i = 0; i < children.length; i++) {
        results.add(startStopExecutor.submit(new StartChild(children[i])));
    }

    boolean fail = false;
    for (Future<Void> result : results) {
        try {
            result.get();
        } catch (Exception e) {
            log.error(sm.getString("containerBase.threadedStartFailed"), e);
            fail = true;
        }

    }
    if (fail) {
        throw new LifecycleException(sm.getString("containerBase.threadedStartFailed"));
    }

    // Start the Valves in our pipeline (including the basic), if any
    if (pipeline instanceof Lifecycle)
        ((Lifecycle) pipeline).start();

    setState(LifecycleState.STARTING);
    // Start our thread
    threadStart();
}
  • 启动过程利用初始化时创建的线程池启动了各个子容器,如果startStopThreads大于1那么多个子容器可以并发地启动。容器组件启动时会等待全部的子容器组件启动完毕,若有一个子容器启动失败就会抛出异常;
  • 如果有与容器组件关联的Pipeline则启动Pipeline;
  • 调用基类LifecycleBase的setState方法发布LifecycleState.STARTING事件给添加到容器组件自身的生命周期事件监听器。

Pipeline组件

Pipeline组件用于封装容器组件的请求处理过程,一般来说每个容器组件都有与其关联的一个Pipeline。
Pipeline接口的代码如下:

public interface Pipeline {
    public Valve getBasic();
    public void setBasic(Valve valve);
    public void addValve(Valve valve);
    public Valve[] getValves();
    public void removeValve(Valve valve);
    public Valve getFirst();
    public boolean isAsyncSupported();
    public Container getContainer();
    public void setContainer(Container container);
    public void findNonAsyncValves(Set<String> result);
}
  • getBasic、setBasic与基本阀(Basic Valve)的概念有关,Pipeline是由一系列阀(Valve)组成的链表,基本阀始终指向最后一个阀;
  • 基本阀封装了容器组件对请求的处理过程,总是在一个Pipeline的末尾执行;
  • 其余的方法与链表操作有关,如添加、移除阀等方法。

StandardPipeline类继承LifecycleBase类并实现了Pipeline接口,与阀有关的方法实现很简单,在此不再赘述。initInternal方法没有做任何事情,startInternal则依次启动了各个实现了Lifecycle接口的阀。

@Override
protected void initInternal() {
    // NOOP
}

@Override
protected synchronized void startInternal() throws LifecycleException {
    // Start the Valves in our pipeline (including the basic), if any
    Valve current = first;
    if (current == null) {
        current = basic;
    }
    while (current != null) {
        if (current instanceof Lifecycle)
            ((Lifecycle) current).start();
        current = current.getNext();
    }

    setState(LifecycleState.STARTING);
}
  • 在看StandardPipeline类源码的过程中需要注意basic变量永远指向最后一个阀,first指向第一个阀。当只有一个阀时,first为null但basic不会是null,这时getFirst会返回basic而不是null。

Valve组件

Valve组件是容器组件中处理请求的组件,主要用在Pipeline中,是一种责任链设计模式。Tomcat内置了很多阀的实现,如前文提到的StandardEngineValve、StandardHostValve和StandardContextValve,具体可参考阀的Valve配置文档
Valve接口的码如下:

public interface Valve {
    public Valve getNext();
    public void setNext(Valve valve);
    public void backgroundProcess();
    public void invoke(Request request, Response response)
        throws IOException, ServletException;
    public boolean isAsyncSupported();
}
  • getNext和setNext分别获取和设置后继阀;
  • invoke方法处理请求,处理规约可以参考Valve API文档
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容