读<看透Spring MVC 源代码分析与实践>Tomcat笔记

Servlet->GenericServlet->HttpServlet


Tomcat整体结构:

Server

   Service 1

   Service 2

   ...

   Service n

      Connnector 1

      Connnector 2

      ...     

      Connnector n (负责网络连接,request/response的创建等)

      Container (具体处理Servlet)

Tomcat 里的Server由org.apache.catalina.startup.Catalina来管理 ,Catalina是整个Tomcat的管理类,它里面的三个方法load,start,stop分别用来管理整个服务器的生命周期,load方法用于根据conf/server.xml文件创建Server并调用 Server的init方法进行初始化,start 方法用于启动服务器,stop方法用于停止服务器,start和stop方法在内部分别调用了Server的start 和stop方法,load方法内部调用了Server的init方法,这三个方法都 会按容器的结构逐层调用相应的方法,比如,Server的start方法中会调用 所有的Service中的start方法,Service中的start方法又会调用所包含的connectors和container的start方法,这样整个服务器就启动了,init和stop方法也一样,这就是tomcat生命周期的管理方式。

   Catalina中还有个await方法很重要,此方法直接调用 了Server的await方法,这个方法的作用是进入一个循环,让主线程不会退出。

Tomcat的入口org.apache.catalina.startup.Bootstrap.main(), Bootstrap的作用类似一个CatalinaAdaptor,具体过程还是使用Catalina来完成,这么做的好处是可以把启动的入口和具体的管理类分开,这样可以就可以方便创建出多种启动方式 ,每种启动方式只需要写一个相应的CatalinaAdaptor就可以了。


Bootstrap是Tomcat的入口,正常情况下启动Tomcat就是调用的Bootstrap的main方法,其代码如下:
// org.apache.catalina.startup.Bootstrap
public static void main(String args[]) {
    // 先新建一个Bootstrap
    if (daemon == null) {
        Bootstrap bootstrap = new Bootstrap();
        try {
            //初始化了ClassLoader,并用ClassLoader创建了Catalina实例,赋给catalinaDaemon变量
            bootstrap.init();
        } catch (Throwable t) {

handleThrowable(t);
            t.printStackTrace();
            return;
        }
        daemon = bootstrap;
    } else {
        Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
    }
    try {
        String command = "start";
        if (args.length > 0) {
            command = args[args.length - 1];
        }
        if (command.equals("startd")) {
            args[args.length - 1] = "start";

daemon.load(args);
            daemon.start();
        } else if (command.equals("stopd")) {
            args[args.length - 1] = "stop";
            daemon.stop();
        } else if (command.equals("start")) {
            daemon.setAwait(true);
            daemon.load(args);
            daemon.start();
        } else if (command.equals("stop")) {
            daemon.stopServer(args);
        } else if (command.equals("configtest")) {
            daemon.load(args);

if (null==daemon.getServer()) {
                System.exit(1);
            }
            System.exit(0);
        } else {
            log.warn("Bootstrap: command \"" + command + "\" does not exist.");
        }
    } catch (Throwable t) {
        if (t instanceof InvocationTargetException &&
                t.getCause() != null) {
            t = t.getCause();
        }
        handleThrowable(t);
        t.printStackTrace();

System.exit(1);
    }
}
可以看到这里的main非常简单,只有两部分内容:首先新建了Bootstrap,并执行init方法初始化;然后处理main方法传入的命令,如果args参数为空,默认执行start。
在init方法里初始化了ClassLoader,并用ClassLoader创建了Catalina实例,然后赋给catalinaDaemon变量,后面对命令的操作都要使用catalinaDaemon来具体执行。
对start命令的处理调用了三个方法:setAwait(true)、load(args)和start()。这三个方法内部都调用了Catalina的相应方法进行具体执行,只不过是用反射来调用的。start方法(另外两个方法会处理一些参数,调用方法类似)的代码如下:

// org.apache.catalina.startup.Bootstrap
public void start()
    throws Exception {
    if( catalinaDaemon==null ) init();


    Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
    method.invoke(catalinaDaemon, (Object [])null);
}
这里首先判断catalinaDaemon有没有初始化,如果没有则调用init方法对其进行初始化,然后使用Method进行反射调用Catalina的start方法。Method是java.lang.reflect包里的

类,代表一个具体的方法,可以使用其中的invoke方法来执行所代表的方法,invoke方法有两个参数,第一参数是Method方法所在的实体,第二个参数是可变参数用于Method方法执行时所需要的参数,所以上面的调用相当于((Catalina)catalinaDaemon).start()。setAwait和load也用类似的方法调用了Catalina中的setAwait和load方法。
7.1.3 Catalina的启动过程
从前面的内容可以知道,Catalina的启动主要是调用setAwait、load和start方法来完成的。setAwait方法用于设置Server启动完成后是否进入等待状态的标志,如果为true则进入,否则不进入;load方法用于加载配置文件,创建并初始化Server;start方法用于启动服务器。下面分别来看一下这三个方法。

首先来看setAwait方法,代码如下:
// org.apache.catalina.startup.Catalina
public void setAwait(boolean b) {
    await = b;
}
这个方法非常简单,就是设置await属性的值,await属性会在start方法中的服务器启动完之后使用它来判断是否进入等待状态。
Catalina的load方法根据conf/server.xml创建了Server对象,并赋值给server属性(具体解析操作是通过开源项目Digester完成的),然后调用了server的init方法,代码如下:
// org.apache.catalina.startup.Catalina
public void load() {

long t1 = System.nanoTime();
    // 省略创建 server代码,创建过程使用Digester完成
    try {
        getServer().init();
    } catch (LifecycleException e) {
        if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
            throw new java.lang.Error(e);
        } else {
             log.error("Catalina.start", e);
        }
    }


    long t2 = System.nanoTime();
    if(log.isInfoEnabled()) {

//启动过程中,控制台可以看到
        log.info("Initialization processed in " + ((t2 - t1) / 1000000) + " ms"); 
    }
}
Catalina的start方法主要调用了server的start方法启动服务器,并根据await属性判断是否让程序进入了等待状态,代码如下:
//org.apache.catalina.startup.Catalina
public void start() {
    if (getServer() == null) {
        load();
    }
    long t1 = System.nanoTime();
    try {

// 调用Server的start方法启动服务器
        getServer().start();
    } catch (LifecycleException e) {
        log.fatal(sm.getString("catalina.serverStartFail"), e);
        try {
            getServer().destroy();
        } catch (LifecycleException e1) {
            log.debug("destroy() failed for failed Server ", e1);
        }
        return;
    }


    long t2 = System.nanoTime();
    if(log.isInfoEnabled()) {

log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");
    }
    // 此处省略了注册关闭钩子代码
    // 进入等待状态
    if (await) {
        await();
        stop();
    }
}
这里首先判断Server是否已经存在了,如果不存在则调用load方法来初始化Server,然后调用Server的start方法来启动服务器,最后注册了关闭钩子并根据await属性判断是否进入等待状态,之前我们已将这里的await属性设置为true了,所以需要进入等待状态。进入等待状态会调用await和stop两个方法,await方法直接调用了Server的await方法,Server的await方法内部会执行一个while循环,这样程序就停到了await方法,当await方法里的while循环退出时,就会执行stop方法,从而关闭服务器。
7.1.4 Server的启动过程
Server接口中提供addService(Service service)、removeService(Service service)来添加和删除Service,Server的init方法和start方法分别循环调用了每个Service的init方法和start方法来启动所有Service。
Server的默认实现是org.apache.catalina.core.StandardServer,StandardServer继承自Lifecycle-MBeanBase,LifecycleMBeanBase又继承自LifecycleBase,init和start方法就定义在了LifecycleBase中,LifecycleBase里的init方法和start方法又调用initInternal方法和startInternal方法,这两个方法都是模板方法,由子类具体实现,所以调用StandardServer的init和start方法时会执行StandardServer自己的initInternal和startInternal方法,这就是Tomcat生命周期的管理方式,更详细的过程见7.2节。StandardServer中的initInternal和startInternal方法分别循环调用了每一个service的start和init方法,代码如下:
//org.apache.catalina.core.StandardServer
protected void startInternal() throws LifecycleException {
    ……
    synchronized (servicesLock) {
        for (int i = 0; i < services.length; i++) {
            services[i].start();
        }
    }
}
protected void initInternal() throws LifecycleException {
    ……
    for (int i = 0; i < services.length; i++) {
        services[i].init();
    }
}
除了startInternal和initInternal方法,StandardServer中还实现了await方法,Catalina中就是调用它让服务器进入等待状态的,其核心代码如下:

//org.apache.catalina.core.StandardServer
public void await() {
    // 如果端口为-2则不进入循环,直接返回
    if( port == -2 ) {
        return;
    }
    // 如果端口为-1则进入循环,而且无法通过网络退出
    if( port==-1 ) {
        try {
            awaitThread = Thread.currentThread();
            while(!stopAwait) {
                try {
                    Thread.sleep( 10000 );
                } catch( InterruptedException ex) {
                    // continue and check the flag
                }
            }
        } finally {
            awaitThread = null;
        }
        return;
    }


    // 如果端口不是-1和-2(应该大于0),则会新建一个监听关闭命令的ServerSocket
    awaitSocket = new ServerSocket(port, 1,InetAddress.getByName(address));
    while (!stopAwait) {

ServerSocket serverSocket = awaitSocket;
    if (serverSocket == null) {
        break;
    }


    Socket socket = null;
    StringBuilder command = new StringBuilder();
    InputStream stream;
    socket = serverSocket.accept();
    socket.setSoTimeout(10 * 1000); 
    stream = socket.getInputStream();


    // 检查在指定端口接收到的命令是否和shutdown命令相匹配

boolean match = command.toString().equals(shutdown); 
    // 如果匹配则跳出循环
    if (match) {
        break;
    }
}
await方法比较长,为了便于大家理解,这里省略了一些处理异常、关闭Socket以及对接收到数据处理的代码。处理的大概逻辑是首先判断端口号port,然后根据port的值分为三种处理方法:
□port为-2,则会直接退出,不进入循环。
□port为-1,则会进入一个while(!stopAwait)的循环,并且在内部没有break跳出的语句,stopAwait标志只有调用了stop方法才会设置为true,所以port为-1时只有在外部调用stop方法才会退出循环。
□port为其他值,则也会进入一个while(!stopAwait)的循环,不过同时会在port所在端口启动一个ServerSocket来监听关闭命令,如果接收到了则会使用break跳出循环。
这里的端口port和关闭命令shutdown是在conf/server.xml文件中配置Server时设置的,默认设置如下:
<!-- server.xml -->
<Server port="8005" shutdown="SHUTDOWN">
这时会在8005端口监听"SHUTDOWN"命令,如果接收到了就会关闭Tomcat。如果不想使用网络命令来关闭服务器可以将端口设置为-1。另外await方法中从端口接收到数据后还会进行简单处理,如果接收到的数据中有ASCII码小于32的(ASCII中32以下的为控制符)则会从小于32的那个字符截断并丢弃后面的数据,为了便于大家理解这部分代码我们在上面省略掉了。
7.1.5 Service的启动过程
Service的默认实现是org.apache.catalina.core.StandardService,StandardService也继承自LifecycleMBeanBase类,所以init和start方法最终也会调用initInternal和startInternal方法,我们来看一下这两个方法的核心内容(省略了异常处理和日志打印代码)。
// org.apache.catalina.core.StandardService
protected void initInternal() throws LifecycleException {
    super.initInternal();
    if (container != null) {
        container.init();
    }


    for (Executor executor : findExecutors()) {
        if (executor instanceof JmxEnabled) {
            ((JmxEnabled) executor).setDomain(getDomain());
        }
        executor.init();
    }


    mapperListener.init();

synchronized (connectorsLock) {
        for (Connector connector : connectors) {
            connector.init();
        }
    }
}


protected void startInternal() throws LifecycleException {
    setState(LifecycleState.STARTING);
    if (container != null) {
        synchronized (container) {
            container.start();
        }

}


    synchronized (executors) {
        for (Executor executor: executors) {
            executor.start();
        }
    }


    mapperListener.start();


    synchronized (connectorsLock) {
        for (Connector connector: connectors) {
            if (connector.getState() != LifecycleState.FAILED) {
                connector.start();

}
        }
    }
}
可以看到,StandardService中的initInternal和startInternal方法主要调用container、executors、mapperListener、connectors的init和start方法。这里的container和connectors前面已经介绍过,mapperListener是Mapper的监听器,可以监听container容器的变化,executors是用在connectors中管理线程的线程池,在serverx.xml配置文件中有参考用法,不过默认是注释起来的,打开注释就可以看到其使用方法,如下所示:
<Service name="Catalina">
    <Executor name="tomcatThreadPool" namePrefix="catalina-exec-"maxThreads="150" minSpareThreads="4"/>
    <Connector executor="tomcatThreadPool"
            port="8080" protocol="HTTP/1.1"
            connectionTimeout="20000"
            redirectPort="8443" />
……
</Service>
这样Connector就配置了一个叫tomcatThreadPool的线程池,最多可以同时启动150个线程,最少要有4个可用线程。
现在整个Tomcat服务器就启动了,整个启动流程如图所示。



7.2 Tomcat的生命周期管理
7.2.1 Lifecycle接口
Tomcat通过org.apache.catalina.Lifecycle接口统一管理生命周期,所有有生命周期的组件都要实现Lifecycle接口。Lifecycle接口一共做了4件事:
□定义了13个String类型常量,用于LifecycleEvent事件的type属性中,作用是区分组件发出的LifecycleEvent事件时的状态(如初始化前、启动前、启动中等)。这种设计方式可以让多种状态都发送同一种类型的事件(LifecycleEvent)然后用其中的一个属性来区分状态而不用定义多种事件,我们要学习和借鉴这种方式。
□定义了三个管理监听器的方法addLifecycleListener、findLifecycleListeners和remove-LifecycleListener,分别用来添加、查找和删除LifecycleListener类型的监听器。
□定义了4个生命周期的方法:init、start、stop和destroy,用于执行生命周期的各个阶段的操作。
□定义了获取当前状态的两个方法getState和getStateName,用来获取当前的状态,getState的返回值LifecycleState是枚举类型,里边列举了生命周期的各个节点,getStateName方法返回String类型的状态的名字,主要用于JMX中。
Lifecycle接口定义如下:
// org.apache.catalina.Lifecycle
public interface Lifecycle {
    // 13种LifecycleEvent事件的类型

public static final String BEFORE_INIT_EVENT = "before_init";
    public static final String AFTER_INIT_EVENT = "after_init";
    public static final String START_EVENT = "start";
    public static final String BEFORE_START_EVENT = "before_start";
    public static final String AFTER_START_EVENT = "after_start";
    public static final String STOP_EVENT = "stop";
    public static final String BEFORE_STOP_EVENT = "before_stop";
    public static final String AFTER_STOP_EVENT = "after_stop";
    public static final String AFTER_DESTROY_EVENT = "after_destroy";
    public static final String BEFORE_DESTROY_EVENT = "before_destroy";
    public static final String PERIODIC_EVENT = "periodic";
    public static final String CONFIGURE_START_EVENT = "configure_start";
    public static final String CONFIGURE_STOP_EVENT = "configure_stop";
    // 3个管理监听器的方法
    public void addLifecycleListener(LifecycleListener listener);
    public LifecycleListener[] findLifecycleListeners();
    public void removeLifecycleListener(LifecycleListener listener);
    // 4个生命周期方法
    public void init() throws LifecycleException;
    public void start() throws LifecycleException;
    public void stop() throws LifecycleException;
    public void destroy() throws LifecycleException;
    // 2个获取当前状态的方法
    public LifecycleState getState();
    public String getStateName();
}

7.2.2 LifecycleBase
Lifecycle的默认实现是org.apache.catalina.util.LifecycleBase,所有实现了生命周期的组件都直接或间接地继承自LifecycleBase,LifecycleBase为Lifecycle里的接口方法提供了默认实现:监听器管理是专门使用了一个LifecycleSupport类来完成的,LifecycleSupport中定义了一个LifecycleListener数组类型的属性来保存所有的监听器,然后并定义了添加、删除、查找和执行监听器的方法;生命周期方法中设置了相应的状态并调用了相应的模板方法,init、start、stop和destroy所对应的模板方法分别是initInternal、startInternal、stopInternal和destroyInternal方法,这四个方法由子类具体实现,所以对于子类来说,执行生命周期处理的方法就是initInternal、startInternal、stopInternal和destroyInternal;组件当前的状态在生命周期的四个方法中已经设置好了,所以这时直接返回去就可以了。下面分别来看一下实现的过程。
三个管理监听器的方法
管理监听器的添加、查找和删除的方法是使用LifecycleSupport来管理的,代码如下:
// org.apache.catalina.util.LifecycleBase
private final LifecycleSupport lifecycle = new LifecycleSupport(this);
@Override
public void addLifecycleListener(LifecycleListener listener) {

lifecycle.addLifecycleListener(listener);
}
public LifecycleListener[] findLifecycleListeners() {
    return lifecycle.findLifecycleListeners();
}
@Override
public void removeLifecycleListener(LifecycleListener listener) {
    lifecycle.removeLifecycleListener(listener);
}
LifecycleBase中的addLifecycleListener、findLifecycleListeners和removeLifecycleListener方法分别调用了LifecycleSupport中的同名方法,LifecycleSupport监听器是通过一个数组属性listeners来保存的,代码如下:
//org.apache.catalina.util.LifecycleSupport
// 用于保存监听器
private LifecycleListener listeners[] = new LifecycleListener[0]; 
// 当监听器发生变化时进行同步的同步锁
private final Object listenersLock = new Object();
// 添加一个监听器
public void addLifecycleListener(LifecycleListener listener) {
    synchronized (listenersLock) {
        LifecycleListener results[] = new LifecycleListener[listeners.length + 1];
        for (int i = 0; i < listeners.length; i++)
            results[i] = listeners[i];
        results[listeners.length] = listener;
        listeners = results;
    }
}
// 获取所有监听器
public LifecycleListener[] findLifecycleListeners() {
    return listeners;
}
// 删除一个监听器
public void removeLifecycleListener(LifecycleListener listener) {
    synchronized (listenersLock) {
        int n = -1;
        for (int i = 0; i < listeners.length; i++) {
            if (listeners[i] == listener) {
                n = i;
                break;
            }
        }
        if (n < 0)
            return;
        LifecycleListener results[] =
          new LifecycleListener[listeners.length - 1];
        int j = 0;

for (int i = 0; i < listeners.length; i++) {
            if (i != n)
                results[j++] = listeners[i];
        }
        listeners = results;
    }
}
这三个方法的实现非常简单,就是对listeners属性进行操作,只是因为listeners是数组类型,所以具体操作起来有点复杂,添加的时候先新建一个比当前数组大1的数组,然后将原来的数据按顺序保存进去,并将新的添加进去,最后将新建的数组赋给listeners属性,删除的时候要先找到要删除的监听器在数组中的序号,然后也是新建一个比当前数组小1的数组,接着将除了要删除的监听器所在序号的元素按顺序添加进去,最后再赋值给listeners属性。另外LifecycleSupport中还定义了处理LifecycleEvent事件的fireLifecycleEvent方法,代码如下:
//org.apache.catalina.util.LifecycleSupport
public void fireLifecycleEvent(String type, Object data) {
    LifecycleEvent event = new LifecycleEvent(lifecycle, type, data);
    LifecycleListener interested[] = listeners;
    for (int i = 0; i < interested.length; i++)
        interested[i].lifecycleEvent(event);

}
这里按事件的类型(组件的状态)创建了LifecycleEvent事件,然后遍历所有的监听器进行了处理。
四个生命周期方法
四个生命周期方法的实现中首先判断当前的状态和要处理的方法是否匹配,如果不匹配就会执行相应方法使其匹配(如在init之前调用了start,这时会先执行init方法),或者不处理甚至抛出异常,如果匹配或者处理后匹配了,则会调用相应的模板方法并设置相应的状态。LifecycleBase中的状态是通过LifecycleState类型的state属性来保存的,最开始初始化值为LifecycleState.NEW。我们来看一下init方法:
// org.apache.catalina.util.LifecycleBase

public final synchronized void init() throws LifecycleException {
//开始的状态必须是LifecycleState.NEW,否则就会抛出异常
    if (!state.equals(LifecycleState.NEW)) {
        invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
    }
//初始化前将状态设置为LifecycleState.INITIALIZING
    setStateInternal(LifecycleState.INITIALIZING, null, false);


    try {
        // 通过模板方法具体执行初始化

initInternal();
    } catch (Throwable t) {
        ExceptionUtils.handleThrowable(t);
        setStateInternal(LifecycleState.FAILED, null, false);
        throw new LifecycleException(
                sm.getString("lifecycleBase.initFail",toString()), t);
    }
    //初始化后将状态设置为LifecycleState.INITIALIZED
    setStateInternal(LifecycleState.INITIALIZED, null, false);
}
因为init方法是最开始,所以状态必须是LifecycleState.NEW,如果不是就抛出异常。

如果开始的状态是LifecycleState.NEW,在具体初始化之前会将状态设置为LifecycleState.INITIALIZING,然后调用模板方法initInternal让子类具体执行初始化,初始化完成后将状态设置为LifecycleState.INITIALIZED。
init方法中如果状态不是LifecycleState.NEW会调用invalidTransition方法抛出异常,invalidTransition方法专门用于处理不符合要求的状态,在另外三个方法中如果状态也不合适而且不能进行别的处理,也会调用invalidTransition方法,其代码如下:
// org.apache.catalina.util.LifecycleBase
private void invalidTransition(String type) throws LifecycleException {

String msg = sm.getString("lifecycleBase.invalidTransition", type,
            toString(), state);
    throw new LifecycleException(msg);
}
其内部就是抛出了一个LifecycleException类型的异常。
start方法要稍微复杂一点,我们来看一下:
// org.apache.catalina.util.LifecycleBase
public final synchronized void start() throws LifecycleException {
    // 通过状态检查是否已经启动,如果已经启动则打印日志并直接返回
    if (LifecycleState.STARTING_PREP.equals(state) ||
            LifecycleState.STARTING.equals(state) ||
            LifecycleState.STARTED.equals(state)) {
        if (log.isDebugEnabled()) {
            Exception e = new LifecycleException();
            log.debug(sm.getString("lifecycleBase.alreadyStarted",toString()), e);
        } else if (log.isInfoEnabled()) {
            log.info(sm.getString("lifecycleBase.alreadyStarted",toString()));
        }
        return;
    }
    //如果没有初始化先进行初始化,如果启动失败则关闭,如果状态无法处理则抛出异常
    if (state.equals(LifecycleState.NEW)) {
        init();
    } else if (state.equals(LifecycleState.FAILED)){
        stop();
    } else if (!state.equals(LifecycleState.INITIALIZED) &&
            !state.equals(LifecycleState.STOPPED)) {
        invalidTransition(Lifecycle.BEFORE_START_EVENT);
    }


    //启动前将状态设置为LifecycleState.STARTING_PREP
    setStateInternal(LifecycleState.STARTING_PREP, null, false);


    try {
// 通过调用模板方法具体启动组件
        startInternal();
    } catch (Throwable t) {
        ExceptionUtils.handleThrowable(t);
//启动失败后会将状态设置为LifecycleState.FAILED
        setStateInternal(LifecycleState.FAILED, null, false);
        throw new LifecycleException(
                sm.getString("lifecycleBase.startFail",toString()), t);
    }
//如果启动失败则调用stop方法停止
    if (state.equals(LifecycleState.FAILED) ||
            state.equals(LifecycleState.MUST_STOP)) {
        stop();
    } else {
        // 如果启动后状态不是LifecycleState.STARTING则抛出异常
        if (!state.equals(LifecycleState.STARTING)) {
            invalidTransition(Lifecycle.AFTER_START_EVENT);
        }
 //成功启动后将状态设置为LifecycleState.STARTED
        setStateInternal(LifecycleState.STARTED, null, false);
    }
}
start方法在启动前先判断是不是已经启动了,如果已经启动了则直接返回,如果没有初始化会先执行初始化,如果是失败在状态就会调用stop方法进行关闭,如果状态不是前面那些也不是刚初始化完或者已经停止了则会抛出异常。如果状态是刚初始化完或者已经停止了,则会先将状态设置为LifecycleState.STARTING_PREP,然后执行startInternal模板方法调用子类的具体启动逻辑进行启动,最后根据是否启动成功设置相应的状态。其实主要就是各种状态的判断设置,stop和destroy方法的实现过程也差不多,就不具体分析了。
设置状态的setStateInternal方法中除了设置状态还可以检查设置的状态合不合逻辑,并且会在最后发布相应的事件,其代码如下:
// org.apache.catalina.util.LifecycleBase
private synchronized void setStateInternal(LifecycleState state,
        Object data, boolean check) throws LifecycleException {
    if (log.isDebugEnabled()) {
        log.debug(sm.getString("lifecycleBase.setState", this, state));

}
    if (check) {
        // 如果状态为空直接抛出异常并退出,正常情况下状态是不会为空的
        if (state == null) {
            invalidTransition("null");
            return;
        }


        // 如果状态不符合逻辑则抛出异常
        if (!(state == LifecycleState.FAILED ||
            (this.state == LifecycleState.STARTING_PREP &&
                    state == LifecycleState.STARTING) ||
            (this.state == LifecycleState.STOPPING_PREP &&
                    state == LifecycleState.STOPPING) ||
            (this.state == LifecycleState.FAILED &&
                    state == LifecycleState.STOPPING))) {
            invalidTransition(state.name());
        }
    }


 // 设置为新状态
    this.state = state;
 // 发布事件
    String lifecycleEvent = state.getLifecycleEvent();
    if (lifecycleEvent != null) {

fireLifecycleEvent(lifecycleEvent, data);
    }
}
setStateInternal方法中通过check参数判断是否需要检查传入的状态,如果需要检查则会检查传入的状态是否为空和是否符合逻辑,最后将传入的状态设置到state属性,并调用fireLifecycleEvent方法处理事件,fireLifecycleEvent方法调用了LifecycleSupport的fireLifecycleEvent方法来具体处理,代码如下:
// org.apache.catalina.util.LifecycleBase
protected void fireLifecycleEvent(String type, Object data) {
    lifecycle.fireLifecycleEvent(type, data);
}
LifecycleSupport的fireLifecycleEvent方法前面已经介绍过了,它里面首先创建了LifecycleEvent事件,然后遍历所有的监听器进行处理。
两个获取当前状态的方法
在生命周期的相应方法中已经将状态设置到了state属性,所以获取状态的两个方法的实现就非常简单了,直接将state返回就可以了,代码如下:
// org.apache.catalina.util.LifecycleBase
@Override
public LifecycleState getState() {
    return state;

}
@Override
public String getStateName() {
    return getState().toString();
}


7.3 Container分析
7.3.1 ContainerBase的结构
Container是Tomcat中容器的接口,通常使用的Servlet就封装在其子接口Wrapper中。Container一共有4个子接口Engine、Host、Context、Wrapper和一个默认实现类ContainerBase,每个子接口都是一个容器,这4个子容器都有一个对应的StandardXXX实现类,并且这些实现类都继承ContainerBase类。另外Container还继承Lifecycle接口,而且ContainerBase间接继承LifecycleBase,所以Engine、Host、Context、Wrapper 4个子容器都符合前面讲过的Tomcat生命周期管理模式,结构图如图7-3所示。


7.3.2 Container的4个子容器
Container的子容器Engine、Host、Context、Wrapper是逐层包含的关系,其中Engine是最顶层,每个service最多只能有一个Engine,Engine里面可以有多个Host,每个Host下可以有多个Context,每个Context下可以有多个Wrapper,它们的装配关系如图7-4所示。


图7-4 Container容器装配结构图
4个容器的作用分别是:
□Engine:引擎,用来管理多个站点,一个Service最多只能有一个Engine。
□Host:代表一个站点,也可以叫虚拟主机,通过配置Host就可以添加站点。
□Context:代表一个应用程序,对应着平时开发的一套程序,或者一个WEB-INF目录以及下面的web.xml文件。
□Wrapper:每个Wrapper封装着一个Servlet。
Context和Host的区别是Context表示一个应用,比如,默认配置下webapps下的每个目录都是一个应用,其中ROOT目录中存放着主应用,其他目录存放着别的子应用,而整个webapps是一个站点。假如www.excelib.com域名对应着webapps目录所代表的站点,其中的ROOT目录里的应用就是主应用,访问时直接使用域名就可以,而webapps/test目录存放的是test子应用,访问时需要用www.excelib.com/test,每一个应用对应一个Context,所有webapps下的应用都属于www.excelib.com站点,而blog.excelib.com则是另外一个站点,属于另外一个Host。

7.3.3 4种容器的配置方法
Engine和Host的配置都在conf/server.xml文件中,server.xml文件是Tomcat中最重要的配置文件,Tomcat的大部分功能都可以在这个文件中配置,比如下面是简化了的默认配置:
<?xml version='1.0' encoding='utf-8'?>

<Server port="8005" shutdown="SHUTDOWN">
    <Service name="Catalina">
        <Connector port="8080" protocol="HTTP/1.1"
            connectionTimeout="20000"
            redirectPort="8443" />
        <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />


        <Engine name="Catalina" defaultHost="localhost">
            <Host name="localhost"  appBase="webapps"
                unpackWARs="true" autoDeploy="true">
            </Host>

</Engine>
    </Service>
</Server>
这里首先定义了一个Server,在8005端口监听关闭命令“SHUTDOWN”;Server里定义了一个名为Catalina的Service;Service里定义了两个Connector,一个是HTTP协议,一个是AJP协议,AJP主要用于集成(如与Apache集成);Service里还定义了一个名为Catalina的Engine;Engine里定义了一个名为localhost的Host。
Engine和Host直接用Engine、Host标签定义到相应位置就可以了。Host标签中的name属性代表域名,所以上面定义的站点可以通过localhost访问,appBase属性指定站点的位置,比如,上面定义的站点就是默认的webapps目录,unpackWARs属性表示是否自动解压war文件,autoDeploy属性表示是否自动部署,如果autoDeploy为true那么Tomcat在运行过程中在webapps目录中加入新的应用将会自动部署并启动。另外Host还有一个Alias子标签,可以通过这个标签来定义别名,如果有多个域名访问同一个站点就可以这么定义,如www.excelib.com和excelib.com要访问同一个站点,可以做如下配置:
<Host name="www.excelib.com" appBase="/hosts/excelib.com"
        unpackWARs="true" autoDeploy="true">
    <Alias> excelib.com</Alias>
</Host>
Engine在定义的时候有个defaultHost属性,它表示接收到请求的域名如果在所有的Host的name和Alias中都找不到时使用的默认Host,比如,我们定义了www.excelib.com和excelib.com的Host,但是blog.excelib.com也解析到了这台主机的IP,但是Tomcat却找不到blog.excelib.com对应的Host,这时就会使用Engine中配置的defaultHost来处理,另外如果使用的是IP直接访问也会用到defaultHost,如果将Engine的defaultHost属性删除,然后启动后用127.0.0.1来访问本机的Tomcat就不可能了。
Context有三种配置方法:①通过文件配置;②将WAR应用直接放到Host目录下,Tomcat会自动查找并添加到Host中;③将应用的文件夹放到Host目录下,Tomcat也会自动查找并添加到Host中。
Context通过文件配置的方式一共有5个位置可以配置:
□conf/server.xml文件中的Context标签。

□conf/[enginename]/[hostname]/目录下以应用命名的xml文件。
□应用自己的/META-INF/context.xml文件。
□conf/context.xml文件。
□conf/[enginename]/[hostname]/context.xml.default文件。
其中前三个位置用于配置单独的应用,后两个配置的Context是共享的,conf/context.xml文件中配置的内容在整个Tomcat中共享,第5种配置的内容在对应的站点(Host)中共享。另外,因为conf/server.xml文件只有在Tomcat重启的时候才会重新加载,所以第一种配置方法不推荐使用。
Wrapper的配置就是我们在web.xml中配置的Servlet,一个Servlet对应一个Wrapper。另外也可以在conf/web.xml文件中配置全局的Wrapper,处理Jsp的JspServlet就配置在这里,所以不需要自己配置Jsp就可以处理Jsp请求了。
4个Container容器配置的方法就介绍完了。需要注意的是,同一个Service下的所有站点由于是共享Connector,所以监听的端口都一样。如果想要添加监听不同端口的站点,可以通过不同的Service来配置,Service也是在conf/server.xml文件中配置的。
7.3.4 Container的启动
Container的启动是通过init和start方法来完成的,在前面分析过这两个方法会在Tomcat启动时被Service调用。Container也是按照Tomcat的生命周期来管理的,init和start方法也会调用initInternal和startInternal方法来具体处理,不过Container和前面讲的Tomcat整体结构启动的过程稍微有点不一样,主要有三点区别:
□Container的4个子容器有一个共同的父类ContainerBase,这里定义了Container容器的initInternal和startInternal方法通用处理内容,具体容器还可以添加自己的内容;
□除了最顶层容器的init是被Service调用的,子容器的init方法并不是在容器中逐层循环调用的,而是在执行start方法的时候通过状态判断还没有初始化才会调用;
□start方法除了在父容器的startInternal方法中调用,还会在父容器的添加子容器的addChild方法中调用,这主要是因为Context和Wrapper是动态添加的,我们在站点目录下放一个应用的文件夹或者war包就可以添加一个Context,在web.xml文件中配置一个Servlet就可以添加一个Wrapper,所以Context和Wrapper是在容器启动的过程中才动态查找出来添加到相应的父容器中的。
先分析一下Container的基础实现类ContainerBase中的initInternal和startInternal方法,然后再对具体容器进行分析。
ContainerBase
ContainerBase的initInternal方法主要初始化ThreadPoolExecutor类型的startStopExecutor属性,用于管理启动和关闭的线程,具体代码如下:
// org.apache.catalina.core.ContainerBase
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();
}
ThreadPoolExecutor继承自Executor用于管理线程,特别是Runable类型的线程,具体用法在异步处理的相关内容中再具体讲解。另外需要注意的是,这里并没有设置生命周期的相应状态,所以如果具体容器也没有设置相应生命周期状态,那么即使已经调用init方法进行了初始化,在start进行启动前也会再次调用init方法。
ContainerBase的startInternal方法主要做了5件事:
□如果有Cluster和Realm则调用其start方法;
□调用所有子容器的start方法启动子容器;
□调用管道中Value的start方法来启动管道(管道的内容7.4节会详细讲解);
□启动完成后将生命周期状态设置为LifecycleState.STARTING状态;
□启用后台线程定时处理一些事情。

代码如下:
protected synchronized void startInternal() throws LifecycleException {
    logger = null;
    getLogger();


    //如果有Cluster和Realm则启动
    Cluster cluster = getClusterInternal();
    if ((cluster != null) && (cluster instanceof Lifecycle))
        ((Lifecycle) cluster).start();
    Realm realm = getRealmInternal();
    if ((realm != null) && (realm instanceof Lifecycle))((Lifecycle) realm).start();


    // 获取所有子容器
    Container children[] = findChildren();
    List<Future<Void>> results = new ArrayList<>();
    for (int i = 0; i < children.length; i++) {
        //这里通过线程调用子容器的start方法,相当于children[i].start();
        results.add(startStopExecutor.submit(new StartChild(children[i])));
    }


    // 处理子容器启动线程的Future

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"));
    }

// 启动管道
    if (pipeline instanceof Lifecycle)
        ((Lifecycle) pipeline).start();


    //将生命周期状态设置为LifecycleState.STARTING
    setState(LifecycleState.STARTING);


    // 启动后台线程
    threadStart();
}
这里首先启动了Cluster和Realm,启动方法是直接调用它们的start方法。Cluster用于配置集群,在server.xml中有注释的参考配置,它的作用就是同步Session,Realm是Tomcat的安全域,可以用来管理资源的访问权限。
子容器是使用startStopExecutor调用新线程来启动的,这样可以用多个线程来同时启动,效率更高,具体启动过程是通过一个for循环对每个子容器启动了一个线程,并将返回的Future保存到一个List中(更多线程相关内容会在异步处理中介绍),然后遍历每个Future并调用其get方法。遍历Future主要有两个作用:①其get方法是阻塞的,只有线程处理完之后才会向下走,这就保证了管道Pipeline启动之前容器已经启动完成了;②可以处理启动过程中遇到的异常。
启动子容器的线程类型StartChild是一个实现了Callable的内部类,主要作用就是调用子容器的start方法,代码如下:
// org.apache.catalina.core.ContainerBase
private static class StartChild implements Callable<Void> {
    private Container child;
    public StartChild(Container child) {
        this.child = child;
    }
    @Override
    public Void call() throws LifecycleException {
        child.start();
        return null;
    }
}
因为这里的startInternal方法是定义在所有容器的父类ContainerBase中的,所以所有容器启动的过程中都会调用子容器的start方法来启动子容器。
使用多线程在实际运行过程中可以提高效率,但调试起来会比较麻烦,在调试分析Tomcat代码的过程中可以直接调用子容器的start方法,也就是将如下代码:
for (int i = 0; i < children.length; i++) {
    results.add(startStopExecutor.submit(new StartChild(children[i])));
}
改为
for (int i = 0; i < children.length; i++) {
    children[i].start();
}

这样调试就简单了,Future相关的代码也可以注释起来。
子容器启动完成后接着启动容器的管道,管道在7.4节详细讲解,管道启动也是直接调用start方法来完成的。管道启动完之后设置了生命周期的状态,然后调用threadStart方法启动了后台线程。
threadStart方法启动的后台线程是一个while循环,内部会定期调用backgroundProcess方法做一些事情,间隔时间的长短是通过ContainerBase的backgroundProcessorDelay属性来设置的,单位是秒,如果小于0就不启动后台线程了,不过其backgroundProcess方法会在父容器的后台线程中调用。backgroundProcess方法是Container接口中的一个方法,一共有3个实现,分别在ContainerBase、StandardContext和StandardWrapper中,ContainerBase中提供了所有容器共同的处理过程,StandardContext和StandardWrapper的backgroundProcess方法除了处理自己相关的业务,也调用ContainerBase中的处理。ContainerBase的backgroundProcess方法中调用了Cluster、Realm和管道的backgroundProcess方法;StandardContext的background-Process方法中对Session过期和资源变化进行了处理;StandardWrapper的backgroundProcess方法会对Jsp生成的Servlet定期进行检查。
Engine
Service会调用最顶层容器的init和start方法,如果使用了Engine就会调用Engine的。Engine的默认实现类StandardEngine中的initInternal和startInternal方法如下:
// org.apache.catalina.core.StandardEngine
protected void initInternal() throws LifecycleException {
    getRealm();
    super.initInternal();
}
protected synchronized void startInternal() throws LifecycleException {
    if(log.isInfoEnabled())
        log.info( "Starting Servlet Engine: " + ServerInfo.getServerInfo());
    super.startInternal();
}
它们分别调用了ContainerBase中的相应方法,initInternal方法还调用了getRealm方法,其作用是如果没有配置Realm,则使用一个默认的NullRealm,代码如下:
// org.apache.catalina.core.StandardEngine
public Realm getRealm() {
    Realm configured = super.getRealm();
    if (configured == null) {
        configured = new NullRealm();
        this.setRealm(configured);
    }
    return configured;
}
Host
Host的默认实现类StandardHost没有重写initInternal方法,初始化默认调用ContainerBase的initInternal方法,startInternal方法代码如下:
//org.apache.catalina.core.StandardHost
@Override
protected synchronized void startInternal() throws LifecycleException {
    // 如果管道中没有ErrorReportValve则将其加入管道
    String errorValve = getErrorReportValveClass();
    if ((errorValve != null) && (!errorValve.equals(""))) {
        try {
            boolean found = false;
            Valve[] valves = getPipeline().getValves();

for (Valve valve : valves) {
                if (errorValve.equals(valve.getClass().getName())) {
                    found = true;
                    break;
                }
            }
            if(!found) {
                Valve valve =
                    (Valve) Class.forName(errorValve).newInstance();
                getPipeline().addValve(valve);
            }
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            log.error(sm.getString("standardHost.invalidErrorReportValveClass",
                    errorValve), t);
        }
    }
    super.startInternal();
}
这里的代码看起来虽然比较多,但功能却非常简单,就是检查Host的管道中有没有指定的Value,如果没有则添加进去。检查的方法是遍历所有的Value然后通过名字判断的,检查的Value的类型通过getErrorReportValveClass方法获取,它返回errorReportValveClass属性,可以配置,默认值是org.apache.catalina.valves.ErrorReportValve,代码如下:
//org.apache.catalina.core.StandardHost
private String errorReportValveClass ="org.apache.catalina.valves.ErrorReportValve";
public String getErrorReportValveClass() {
    return (this.errorReportValveClass);
}
这就是StandardHost的startInternal方法处理的过程。Host的启动除了startInternal方法,还有HostConfig中相应的方法,HostConfig继承自LifecycleListener的监听器(Engine也有对应的EngineConfig监听器,不过里面只是简单地做了日志记录),在接收到Lifecycle.START_EVENT事件时会调用start方法来启动,HostConfig的start方法会检查配置的Host站点配置的位置是否存在以及是不是目录,最后调用deployApps方法部署应用,deployApps方法代码如下:
// org.apache.catalina.startup.HostConfig
protected void deployApps() {
    File appBase = host.getAppBaseFile();
    File configBase = host.getConfigBaseFile();
    String[] filteredAppPaths = filterAppPaths(appBase.list());
    // 部署XML描述文件
    deployDescriptors(configBase, configBase.list());
    // 部署WAR文件
    deployWARs(appBase, filteredAppPaths);
    // 部署文件夹
    deployDirectories(appBase, filteredAppPaths);
}
一共有三种部署方式:通过XML描述文件、通过WAR文件和通过文件夹部署。XML文件指的是conf/[enginename]/[hostname]/*.xml文件,WAR文件和文件夹是Host站点目录下的WAR文件和文件夹,这里会自动找出来并部署上,所以我们如果要添加应用只需要直接放在Host站点的目录下就可以了。部署完成后,会将部署的Context通过StandardHost的addChild方法添加到Host里面。StandardHost的addChild方法会调用父类ContainerBase的addChild方法,其中会调用子类(这里指Context)的start方法来启动子容器。

Context
Context的默认实现类StandardContext在startInternal方法中调用了在web.xml中定义的Listener,另外还初始化了其中的Filter和load-on-startup的Servlet,代码如下:
// org.apache.catalina.core.StandardContext
protected synchronized void startInternal() throws LifecycleException {
......
// 触发listener
if (ok) {
    if (!listenerStart()) {
        log.error( "Error listenerStart");
        ok = false;

}
}


// 初始化Filter
if (ok) {
    if (!filterStart()) {
        log.error("Error filterStart");
        ok = false;
    }
}


// 初始化Servlets
if (ok) {
    if (!loadOnStartup(findChildren())){
        log.error("Error loadOnStartup");

ok = false;
    }
}
......
}
listenerStart、filterStart和loadOnStartup方法分别调用配置在Listener的contextInitialized方法以及Filter和配置了load-on-startup的Servlet的init方法。
Context和Host一样也有一个LifecycleListener类型的监听器ContextConfig,其中configureStart方法用来处理CONFIGURE_START_EVENT事件,这个方法里面调用webConfig方法,webConfig方法中解析了web.xml文件,相应地创建了Wrapper并使用addChild添加到了Context里面。
Wrapper

Wrapper的默认实现类StandardWrapper没有重写initInternal方法,初始化时会默认调用ContainerBase的initInternal方法,startInternal方法代码如下:
// org.apache.catalina.core.StandardWrapper
@Override
protected synchronized void startInternal() throws LifecycleException {
    if (this.getObjectName() != null) {
        Notification notification = 
            new Notification("j2ee.state.starting",this.getObjectName(),sequenceNumber++);
        broadcaster.sendNotification(notification);
    }


    super.startInternal();


    setAvailable(0L);


    if (this.getObjectName() != null) {
        Notification notification =
            new Notification("j2ee.state.running", this.getObjectName(), sequenceNumber++);
        broadcaster.sendNotification(notification);
    }
}

这里主要做了三件事情:
□用broadcaster发送通知,主要用于JMX;
□调用了父类ContainerBase中的startInternal方法;
□调用setAvailable方法让Servlet有效。
这里的setAvailable方法是Wrapper接口中的方法,其作用是设置Wrapper所包含的Servlet有效的起始时间,如果所设置的时间为将来的时间,那么调用所对应的Servlet就会产生错误,直到过了所设置的时间之后才可以正常调用,它的类型是long,如果设置为Long.MAX_VALUE就一直不可以调用了。
Wrapper没有别的容器那种XXXConfig样式的LifecycleListener监听器。

7.4 Pipeline-Valve管道
7.3节讲了Container自身的创建过程,Container处理请求是使用Pipeline-Valve管道来处理的,本节就详细分析一下Pipeline-Valve管道。首先介绍它的处理模式,然后分析其实现方法。
7.4.1 Pipeline-Valve处理模式
Pipeline-Valve是责任链模式,责任链模式是指在一个请求处理的过程中有多个处理者依次对请求进行处理,每个处理者负责做自己相应的处理,处理完成后将处理后的请求返回,再让下一个处理者继续处理,就好像驾车的过程中可能会遇到很多次交警检查,可能有查酒驾的也可能有查违章的,在一次驾车的过程中可能会遇到多次检查,这就是责任链模式,Pipeline就相当于驾车的过程,Valve相当于检查的交警。
不过Pipeline-Valve的管道模型和普通的责任链模式稍微有点不同,区别主要有两点:①每个Pipeline都有特定的Valve,而且是在管道的最后一个执行,这个Valve叫ValveBase,ValveBase是不可删除的;②在上层容器的管道的ValveBase中会调用下层容器的管道。这就好像快递的配货车,配货车除了在路途中可能遇到交警检查外,到达目的地后必然还会被中转站检查货物,这是必不可少的,如果在中转站检查后没有问题就会把货物交给另外的货车去派送,直到将货物送到客户手中,被客户检查并签收。4个容器的BaseValue分别是StandardEngineValve、StandardHostValve、StandardContextValve和StandardWrapperValve,整个处理的流程如图7-5所示。


在Engine的管道中依次执行Engine的各个Valve,最后执行StandardEngineValve,用于调用Host的管道,然后执行Host的Valve,这样依次类推最后执行Wrapper管道中的StandardWrapperValve。
在Filter中用到的FilterChain其实就是这种模式,FilterChain相当于Pipeline,每个Filter都相当于一个Valve,Servlet相当于最后的ValveBase。

7.4.2 Pipeline-Valve的实现方法
Pipeline管道的实现分为生命周期管理和处理请求两部分,下面分别介绍。

Pipeline管道生命周期的实现方法
Container中的Pipeline在抽象实现类ContainerBase中定义,并在生命周期的startInternal、stopInternal、destroyInternal方法中调用管道的相应生命周期方法(因为管道不需要初始化所以initInternal方法中并没有调用),代码如下:
//org.apache.catalina.core.ContainerBase
protected final Pipeline pipeline = new StandardPipeline(this); 


protected synchronized void startInternal() throws LifecycleException {
    ...
    // 调用管道的启动方法

if (pipeline instanceof Lifecycle)
        ((Lifecycle) pipeline).start();…
}
protected synchronized void stopInternal() throws LifecycleException {
...
    if (pipeline instanceof Lifecycle &&
            ((Lifecycle) pipeline).getState().isAvailable()) {
        ((Lifecycle) pipeline).stop();
    }
...
}
protected void destroyInternal()throws LifecycleException {…
    if (pipeline instanceof Lifecycle) {
        ((Lifecycle) pipeline).destroy();
    }
...
}
Container的4个子容器都继承自ContainerBase,所以4个子容器在执行生命周期的方法时都会调用管道相应的生命周期方法。
Pipeline使用的是StandardPipeline类型,它里面的Valve保存在first属性中,Valve是链式结构,可以通过getNext方法依次获取每个Valve,ValveBase单独保存在basic属性中(basic不可以为空,在调用addValve方法添加Valve时basic会同时保存到first的最后一个,如果没调用addValve方法则first可能为空)。StandardPipeline继承自LifecycleBase,所以实际处理生命周期的方法是startInternal、stopInternal和destroyInternal,代码如下:
// org.apache.catalina.core.StandardPipeline
@Override
protected synchronized void startInternal() throws LifecycleException {
    // 使用临时变量current来遍历Valve链里的所有Valve,如果first为空则使用basic
    Valve current = first;
    if (current == null) {
        current = basic;

}
    //遍历所有Valve并调用start方法
    while (current != null) {
        if (current instanceof Lifecycle)
            ((Lifecycle) current).start();
        current = current.getNext();
    }
    //设置LifecycleState.STARTING状态
    setState(LifecycleState.STARTING);
}
@Override
protected synchronized void stopInternal() throws LifecycleException {
    setState(LifecycleState.STOPPING

);
    Valve current = first;
    if (current == null) {
        current = basic;
    }
    while (current != null) {
        if (current instanceof Lifecycle)
            ((Lifecycle) current).stop();
        current = current.getNext();
    }
}
@Override
protected void destroyInternal() {
    Valve[] valves = getValves();
    for (Valve valve : valves) {

removeValve(valve);
    }
}
startInternal方法和stopInternal方法处理的过程非常相似,都是使用临时变量current来遍历Valve链里的所有Valve,如果first为空则使用basic,然后遍历所有Valve并调用相应的start和stop方法,然后设置相应的生命周期状态。destroyInternal方法是删除所有Valve,getValves方法可以得到包括basic在内的所有Valve的集合,代码如下:
// org.apache.catalina.core.StandardPipeline
@Override
public Valve[] getValves() {
    ArrayList<Valve> valveList = new ArrayList<>();

Valve current = first;
    if (current == null) {
        current = basic;
    }
    while (current != null) {
        valveList.add(current);
        current = current.getNext();
    }
    return valveList.toArray(new Valve[0]);
}
Pipeline管道处理请求的实现方法
Pipeline调用所包含Value的invoke方法来处理请求,并且在BaseValue里又调用了子容器Pipeline所包含Value的invoke方法,直到最后调用了Wrapper的Pipeline所包含的Base-Valve——StandardWrapperValve。

Connector在接收到请求后会调用最顶层容器的Pipeline来处理,顶层容器的Pipeline处理完之后就会在其BaseValue里调用下一层容器的Pipeline进行处理,这样就可以逐层调用所有容器的Pipeline来处理了。Engine的BaseValve是StandardEngineValve,它的invoke代码如下:
// org.apache.catalina.core.StandardEngineValve
public final void invoke(Request request, Response response)
    throws IOException, ServletException {


    // Host已经事先设置到request中,其他各层容器也一样都会事先设置到request中
    Host host = request.getHost();

if (host == null) {
        response.sendError
            (HttpServletResponse.SC_BAD_REQUEST,
             sm.getString("standardEngine.noHost",
                 request.getServerName()));
        return;
    }
    if (request.isAsyncSupported()) {
        request.setAsyncSupported(host.getPipeline().isAsyncSupported());
    }


    // 将请求传递到Host的管道

host.getPipeline().getFirst().invoke(request, response);
}
这里的实现非常简单,首先从request中获取到Host,然后调用其管道的第一个Value的invoke方法进行处理。Host的BaseValve也同样会调用Context的Pipeline,Context的BaseValve会调用Wrapper的Pipeline,Wrapper的Pipeline最后会在其BaseValve(Standard-WrapperValve)中创建FilterChain并调用其doFilter方法来处理请求,FilterChain包含着我们配置的与请求相匹配的Filter和Servlet,其doFilter方法会依次调用所有Filter的doFilter方法和Servlet的service方法,这样请求就得到处理了。
Filter和Servlet实际处理请求的方法在Wrapper的管道Pipeline的BaseValue——Standard-WrapperValve中调用,生命周期相关的方法是在Wrapper的实现类StandardWrapper中调用。

7.5 Connector分析
Connector用于接收请求并将请求封装成Request和Response来具体处理,最底层是使用Socket来进行连接的,Request和Response是按照HTTP协议来封装的,所以Connector同时实现了TCP/IP协议和HTTP协议,Request和Response封装完之后交给Container进行处理,Container就是Servlet的容器,Container处理完之后返回给Connector,最后Connector使用Socket将处理结果返回给客户端,这样整个请求就处理完了。
7.5.1 Connector的结构
Connector中具体是用ProtocolHandler来处理请求的,不同的ProtocolHandler代表不同的连接类型,比如,Http11Protocol使用的是普通Socket来连接的,Http11NioProtocol使用的是NioSocket来连接的。
ProtocolHandler里面有3个非常重要的组件:Endpoint、Processor和Adapter。Endpoint用于处理底层Socket的网络连接,Processor用于将Endpoint接收到的Socket封装成Request,Adapter用于将封装好的Request交给Container进行具体处理。也就是说Endpoint用来实现TCP/IP协议,Processor用来实现HTTP协议,Adapter将请求适配到Servlet容器进行具体处理。
Endpoint的抽象实现AbstractEndpoint里面定义的Acceptor和AsyncTimeout两个内部类和一个Handler接口。Acceptor用于监听请求,AsyncTimeout用于检查异步request的超时,Handler用于处理接收到的Socket,在内部调用了Processor进行处理。
Connector的结构如图7-6所示。


7.5.2 Connector自身类
Connector类本身的作用主要是在其创建时创建ProtocolHandler,然后在生命周期的相关方法中调用了ProtocolHandler的相关生命周期方法。Connector的使用方法是通过Connector标签配置在conf/server.xml文件中,所以Connector是在Catalina的load方法中根据conf/server.xml配置文件创建Server对象时创建的。Connector的生命周期方法是在Service中调用的。
Connector的创建
Connector的创建过程主要是初始化ProtocolHandler。server.xml配置文件中Connector标签的protocol属性会设置到Connector构造函数的参数中,它用于指定ProtocolHandler的类型,Connector的构造函数代码如下:
// org.apache.catalina.connector.Connector
public Connector(String protocol) {
    //根据protocol参数指定protocolHandlerClassName
    setProtocol(protocol);
    // 实例化ProtocolHandler
    ProtocolHandler p = null;
    try {
        Class<?> clazz = Class.forName(protocolHandlerClassName);      //初始化代码
        p = (ProtocolHandler) clazz.newInstance();
    } catch (Exception e) {
        log.error(sm.getString(
                "coyoteConnector.protocolHandlerInstantiationFailed"), e);
    } finally {      // 赋值给protocolHandler属性
        this.protocolHandler = p;
    }
    if (!Globals.STRICT_SERVLET_COMPLIANCE) {
        URIEncoding = "UTF-8";
        URIEncodingLower = URIEncoding.toLowerCase(Locale.ENGLISH);
    }
}
这里首先根据传入的protocol参数调用setProtocol方法设置了protocolHandlerClassName属性,接着用protocolHandlerClassName所代表的类创建了ProtocolHandler并赋值给了protocol-Handler属性。
设置protocolHandlerClassName属性的setProtocol方法代码如下:
// org.apache.catalina.connector.Connector
public void setProtocol(String protocol) {
    if (AprLifecycleListener.isAprAvailable()) {
        if ("HTTP/1.1".equals(protocol)) {
            setProtocolHandlerClassName
                ("org.apache.coyote.http11.Http11AprProtocol");

} else if ("AJP/1.3".equals(protocol)) {
            setProtocolHandlerClassName
                ("org.apache.coyote.ajp.AjpAprProtocol");
        } else if (protocol != null) {
            setProtocolHandlerClassName(protocol);
        } else {
            setProtocolHandlerClassName
                ("org.apache.coyote.http11.Http11AprProtocol");
        }
    } else {
        if ("HTTP/1.1".equals(protocol)) {
            setProtocolHandlerClassName("org.apache.coyote.http11.Http11NioProtocol");
        } else if ("AJP/1.3".equals(protocol)) {
            setProtocolHandlerClassName
                ("org.apache.coyote.ajp.AjpNioProtocol");
        } else if (protocol != null) {
            setProtocolHandlerClassName(protocol);
        }
    }
}
Apr是Apache Portable Runtime的缩写,是Apache提供的一个运行时环境,如果要使用Apr需要先安装,安装后Tomcat可以自己检测出来。如果安装了Apr,setProtocol方法

会根据配置的HTTP/1.1属性对应地将protocolHandlerClassName设置为org.apache.coyote.http11.Http11AprProtocol,如果没有安装Apr,会根据配置的HTTP/1.1属性将protocolHandler-ClassName设置为org.apache.coyote.http11.Http11NioProtocol,然后就会根据protocol-Handler ClassName来创建ProtocolHandler。
Connector生命周期处理方法
Connector的生命周期处理方法中主要调用了ProtocolHandler的相应生命周期方法,代码如下:
// org.apache.catalina.connector.Connector
protected void initInternal() throws LifecycleException {
    super.initInternal();

// 新建Adapter,并设置到protocolHandler
    adapter = new CoyoteAdapter(this);
    protocolHandler.setAdapter(adapter);
    ...
    try {
        // 初始化protocolHandler
        protocolHandler.init();
    } catch (Exception e) {
        throw new LifecycleException
            (sm.getString
             ("coyoteConnector.protocolHandlerInitializationFailed"), e);
    }
}

@Override
protected void startInternal() throws LifecycleException {
    if (getPort() < 0) {
        throw new LifecycleException(sm.getString(
            "coyoteConnector.invalidPort", Integer.valueOf(getPort())));
    }


    setState(LifecycleState.STARTING);


    try {
        protocolHandler.start();
    } catch (Exception e) {
        String errPrefix = "";

if(this.service != null) {
            errPrefix += "service.getName(): \"" + this.service.getName() + "\"; ";
        }
        throw new LifecycleException
            (errPrefix + " " + sm.getString
                ("coyoteConnector.protocolHandlerStartFailed"), e);
    }
}
@Override
protected void stopInternal() throws LifecycleException {
    setState(LifecycleState.STOPPING);
    try {

protocolHandler.stop();
    } catch (Exception e) {
        throw new LifecycleException
            (sm.getString
             ("coyoteConnector.protocolHandlerStopFailed"), e);
    }
}
@Override
protected void destroyInternal() throws LifecycleException {
    try {
        protocolHandler.destroy();
    } catch (Exception e) {
        throw new LifecycleException
            (sm.getString
             ("coyoteConnector.protocolHandlerDestroyFailed"), e);
    }
    if (getService() != null) {
        getService().removeConnector(this);
    }
    super.destroyInternal();
}
在initInternal方法中首先新建了一个Adapter并设置到ProtocolHandler中,然后对ProtocolHandler进行初始化;在startInternal方法中首先判断设置的端口是否小于0,如果小于0就抛出异常,否则就调用ProtocolHandler的start方法来启动;在stopInternal方法中先设置了生命周期状态,然后调用了ProtocolHandler的stop方法;在destroyInternal方法中除了调用ProtocolHandler的destroy方法,还会将当前的Connector从Service中删除并调用父类的destroyInternal方法。
7.5.3 ProtocolHandler
Tomcat中ProtocolHandler的继承结构如图7-7所示。


ProtocolHandler有一个抽象实现类AbstractProtocol,AbstractProtocol下面分了三种类型:Ajp、HTTP和Spdy。Ajp是Apache JServ Protocol的缩写,Apache的定向包协议,主要用于与前端服务器(如Apache)进行通信,它是长连接,不需要每次通信都重新建立连接,这样就节省了开销;HTTP协议前面已经介绍过了,就不重复介绍了;Spdy协议是Google开发的协议,作用类似HTTP,比HTTP效率高,不过这只是Google制定的企业级协议,使用并不广泛,而且在HTTP/2协议中已经包含了Spdy所提供的优势,所以Spdy协议平常很少使用,不过Tomcat提供了支持。
这里的ProtocolHandler以默认配置中的org.apache.coyote.http11.Http11NioProtocol为例来分析,它使用HTTP1.1协议,TCP层使用NioSocket来传输数据。
Http11NioProtocol的构造函数中创建了NioEndpoint类型的Endpoint,并新建了Http11-ConnectionHandler类型的Handler然后设置到了Endpoint中,代码如下:
// org.apache.coyote.http11.Http11NioProtocol
public Http11NioProtocol() {
    endpoint=new NioEndpoint();
    cHandler = new Http11ConnectionHandler(this);
    ((NioEndpoint) endpoint).setHandler(cHandler);
    setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);
    setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
    setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);
}
四个生命周期方法是在父类AbstractProtocol中实现的,其中主要调用了Endpoint的生命周期方法。

7.5.4 处理TCP/IP协议的Endpoint
Endpoint用于处理具体连接和传输数据,NioEndpoint继承自org.apache.tomcat.util.net.AbstractEndpoint,在NioEndpoint中新增了Poller和SocketProcessor内部类,NioEndpoint中处理请求的具体流程如图7-8所示。


NioEndpoint的init和start方法在父类AbstractEndpoin中,代码如下:
// org.apache.tomcat.util.net.AbstractEndpoint
public final void init() throws Exception {
    if (bindOnInit) {

bind();
        bindState = BindState.BOUND_ON_INIT;
    }
}


public final void start() throws Exception {
    if (bindState == BindState.UNBOUND) {
        bind();
        bindState = BindState.BOUND_ON_START;
    }
    startInternal();
}
这两个方法主要调用bind和startInternal方法,它们是模板方法,在NioEndpoint中实现,bind方法代码如下:
// org.apache.tomcat.util.net.NioEndpoint
public void bind() throws Exception {
    serverSock = ServerSocketChannel.open();
    socketProperties.setProperties(serverSock.socket());
    InetSocketAddress addr = (getAddress()!=null?new InetSocketAddress(getAddress(),getPort()):new InetSocketAddress(getPort()));
    serverSock.socket().bind(addr,getBacklog());
    serverSock.configureBlocking(true);
    serverSock.socket().setSoTimeout(getSocketProperties().getSoTimeout());


    // 初始化需要启动acceptor线程的个数,如果为0则改为1,否则将不能工作
    if (acceptorThreadCount == 0) {
        acceptorThreadCount = 1;
    }
    // 初始化需要启动poller线程的个数,如果小于等于0则改为1
    if (pollerThreadCount <= 0) {
        pollerThreadCount = 1;
    }
    stopLatch = new CountDownLatch(pollerThreadCount);

// 如果需要则初始化SSL
    if (isSSLEnabled()) {
        SSLUtil sslUtil = handler.getSslImplementation().getSSLUtil(this);


        sslContext = sslUtil.createSSLContext();
        sslContext.init(wrap(sslUtil.getKeyManagers()),sslUtil.getTrustManagers(), null);


        SSLSessionContext sessionContext =sslContext.getServerSessionContext();
        if (sessionContext != null) {

sslUtil.configureSessionContext(sessionContext);
        }
        enabledCiphers = sslUtil.getEnableableCiphers(sslContext);
        enabledProtocols = sslUtil.getEnableableProtocols(sslContext);
    }
    if (oomParachute>0) reclaimParachute(true);
    selectorPool.open();
}
这里的bind方法中首先初始化了ServerSocketChannel,然后检查了代表Acceptor和Poller初始化的线程数量的acceptorThreadCount属性和pollerThreadCount属性,它们至少为1,Acceptor用于接收请求,接收到请求后交给Poller处理,它们都是启动线程来处理的。另外还处理了初始化SSL等内容。
NioEndpoint的startInternal方法代码如下:
// org.apache.tomcat.util.net.NioEndpoint
@Override
public void startInternal() throws Exception {
    if (!running) {
        running = true;
        paused = false;


        processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,socketProperties.getProcessorCache());

keyCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,socketProperties.getKeyCache());
        eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,socketProperties.getEventCache());
        nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,socketProperties.getBufferPool());


        // 创建Executor
        if ( getExecutor() == null ) {
            createExecutor();
        }

initializeConnectionLatch();


        //启动poller线程
        pollers = new Poller[getPollerThreadCount()];
        for (int i=0; i<pollers.length; i++) {
            pollers[i] = new Poller();
            Thread pollerThread = new Thread(pollers[i], getName() + "-ClientPoller-"+i);
            pollerThread.setPriority(threadPriority);
            pollerThread.setDaemon(true);
            pollerThread.start();
        }

startAcceptorThreads();
    }
}
这里首先初始化了一些属性,然后启动了Poller和Acceptor来处理请求,初始化的属性中的processorCache属性是SynchronizedStack<SocketProcessor>类型,SocketProcessor并不是前面介绍的Processor,而是NioEndpoint的一个内部类,Poller接收到请求后就会交给它处理,SocketProcessor又会将请求传递到Handler。启动Acceptor的startAcceptorThreads方法在AbstractEndpoint中,代码如下:
// org.apache.tomcat.util.net.AbstractEndpoint
protected final void startAcceptorThreads() {
    int count = getAcceptorThreadCount();
    acceptors = new Acceptor[count];


    for (int i = 0; i < count; i++) {
        acceptors[i] = createAcceptor();
        String threadName = getName() + "-Acceptor-" + i;
        acceptors[i].setThreadName(threadName);
        Thread t = new Thread(acceptors[i], threadName);
        t.setPriority(getAcceptorThreadPriority());
        t.setDaemon(getDaemon());

t.start();
    }
}
这里的getAcceptorThreadCount方法就是获取的init方法中处理过的acceptorThreadCount属性,获取到后就会启动相应数量的Acceptor线程来接收请求。
7.5.5 处理HTTP协议的Processor
Processor用于处理应用层协议(如HTTP),它的继承结构如图7-9所示。



Processor有两个AbstractProtocol抽象继承类,图7-9中上面的AbstractProtocol是在org.apache.coyote.http11.upgrade包中,下面的AbstractProtocol在org.apache.coyote包中。正常处理协议使用的是下面的AbstractProtocol及其实现类,上面的AbstractProtocol是Servlet3.1之后才新增的,用于处理HTTP的升级协议,当正常(下面)的Processor处理之后如果Socket的状态是UPGRADING,那么Endpoint中的Handler将会接着创建并调用org.apache.coyote.http11.upgrade包中的Processor进行处理,这里的HTTP升级协议指的是WebSocket协议。
图7-9中下方org.apache.coyote包中的Processor和前面介绍过的ProtocolHandler一一对应,这里就不详细解释了,具体实现应用层协议处理请求的是AbstractAjpProcessor和Abst-ractHttp11Processor中的process方法,这个方法中首先封装了Request和Response,然后调用Adapter将请求传递到了Container中,最后对处理的结果进行了处理,如有没有启动异步处理、处理过程中有没有抛出异常等。
7.5.6 适配器Adapter
Adapter只有一个实现类,那就是org.apache.catalina.connector包下的CoyoteAdapter类。Processor在其process方法中会调用Adapter的service方法来处理请求,Adapter的service方法主要是调用Container管道中的invoke方法来处理请求,在处理之前对Request和Response做了处理,将原来创建的org.apache.coyote包下的Request和Response封装成了org.apache.catalina.connector的Request和Response,并在处理完成后判断是否启动了Comet(长连接推模式)和是否启动了异步请求,并作出相应处理。调用Container管道的相应代码片段如下:
// org.apache.catalina.connector.CoyoteAdapter.process
connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
这里首先从Connector中获取到Service(Connector在initInternal方法中创建Coyote-Adapter的时候已经将自己设置到了CoyoteAdapter中),然后从Service中获取Container,接着获取管道,再获取管道的第一个Value,最后调用invoke方法执行请求。Service中保存的是最顶层的容器,当调用最顶层容器管道的invoke方法时,管道将逐层调用各层容器的管道中Value的invoke方法,直到最后调用Wrapper的管道中的BaseValue(StandardWrapperValve)来处理Filter和Servlet。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值