Tomcat学习笔记(6)- 生命周期与日志管理

1. Lifecycle接口

为了方便管理Tomcat众多组件的启动, 销毁,Tomcat抽象出一层LifeCycle接口来作为解决方案。

/**
 *组件生命周期方法的公共接口。卡特琳娜组件
*可以实现此接口(以及适用于他们支持的功能),以提供一致的机制
*启动和停止组件。

*支持{@link生命周期}的组件的有效状态转换
*:这图很重要哦
 * <pre>
 *            start()
 *  -----------------------------
 *  |                           |
 *  | init()                    |
 * NEW -»-- INITIALIZING        |
 * | |           |              |     ------------------«-----------------------
 * | |           |auto          |     |                                        |
 * | |          \|/    start() \|/   \|/     auto          auto         stop() |
 * | |      INITIALIZED --»-- STARTING_PREP --»- STARTING --»- STARTED --»---  |
 * | |         |                                                            |  |
 * | |destroy()|                                                            |  |
 * | --»-----«--    ------------------------«--------------------------------  ^
 * |     |          |                                                          |
 * |     |         \|/          auto                 auto              start() |
 * |     |     STOPPING_PREP ----»---- STOPPING ------»----- STOPPED -----»-----
 * |    \|/                               ^                     |  ^
 * |     |               stop()           |                     |  |
 * |     |       --------------------------                     |  |
 * |     |       |                                              |  |
 * |     |       |    destroy()                       destroy() |  |
 * |     |    FAILED ----»------ DESTROYING ---«-----------------  |
 * |     |                        ^     |                          |
 * |     |     destroy()          |     |auto                      |
 * |     --------»-----------------    \|/                         |
 * |                                 DESTROYED                     |
 * |                                                               |
 * |                            stop()                             |
 * ----»-----------------------------»------------------------------
 *
*任何状态都可以转换为失败。
 *
*当组件处于STARTING_PREP、start或状态时调用start()
*启动没有效果。
 *
*当组件处于NEW状态时调用start()将导致init()为
*在进入start()方法后立即调用。
 *
*当组件处于STOPPING_PREP、stop或状态时调用stop()
*停止没有效果。
 *
*在组件处于状态时调用stop()
*停了下来。这通常在组件启动失败时遇到
*不会启动所有子组件。当组件停止时,它会停止
*尝试停止所有子组件-即使那些没有启动。
*
*尝试任何其他转换将抛出{@link LifecycleException}。
 *

属性中定义状态更改期间触发的{@link LifecycleEvent}
*触发改变的方法。没有{@link LifecycleEvent}被触发,如果
*尝试转换无效。
 */
public interface Lifecycle {


    // ----------------------------------------------------- 显化常数


    /**
     * “init之前的组件”事件的LifecycleEvent类型。
     */
    public static final String BEFORE_INIT_EVENT = "before_init";


    /**
     * “init之后的组件”事件的LifecycleEvent类型。
     */
    public static final String AFTER_INIT_EVENT = "after_init";


    /**
     * “组件启动”事件的LifecycleEvent类型。
     */
    public static final String START_EVENT = "start";


    /**
     * “启动前组件”事件的LifecycleEvent类型。
     */
    public static final String BEFORE_START_EVENT = "before_start";


    /**
     * “启动后组件”事件的LifecycleEvent类型。
     */
    public static final String AFTER_START_EVENT = "after_start";


    /**
     * “组件停止”事件的LifecycleEvent类型。
     */
    public static final String STOP_EVENT = "stop";


    /**
     * “停止前组件”事件的LifecycleEvent类型。
     */
    public static final String BEFORE_STOP_EVENT = "before_stop";


    /**
     * “停止后组件”事件的LifecycleEvent类型。
     */
    public static final String AFTER_STOP_EVENT = "after_stop";


    /**
     * “组件销毁后”事件的LifecycleEvent类型。
     */
    public static final String AFTER_DESTROY_EVENT = "after_destroy";


    /**
     * “销毁前组件”事件的LifecycleEvent类型。
     */
    public static final String BEFORE_DESTROY_EVENT = "before_destroy";


    /**
     * “周期”事件的LifecycleEvent类型。
     */
    public static final String PERIODIC_EVENT = "periodic";


    /**
   *“configure_start”事件的LifecycleEvent类型。使用这些
*使用独立组件执行配置和
*在需要进行配置时需要发出信号——通常是在配置完成后
* {@link #BEFORE_START_EVENT}和之前的{@link #START_EVENT}。
     */
    public static final String CONFIGURE_START_EVENT = "configure_start";


    /**
   *“configure_stop”事件的LifecycleEvent类型。使用这些
	*使用独立组件执行配置和
	*需要发出信号时,应执行解除配置-通常在之后
	* {@link #AFTER_STOP_EVENT}和之前的{@link #AFTER_STOP_EVENT}。
	    */
    public static final String CONFIGURE_STOP_EVENT = "configure_stop";


    // --------------------------------------------------------- Public Methods


    /**
   添加一个LifecycleEvent监听器到这个组件。
     *
     * @param listener The listener to add
     */
    public void addLifecycleListener(LifecycleListener listener);


    /**
    获取与此生命周期关联的生命周期侦听器。
	*
	返回一个数组,其中包含与之关联的生命周期监听器
	*生命周期。如果该组件没有注册侦听器,则调用
	*返回Listener数组。
     */
    public LifecycleListener[] findLifecycleListeners();


    /**
	  从这个组件中移除一个LifecycleEvent监听器。
     *
     * @param listener The listener to remove
     */
    public void removeLifecycleListener(LifecycleListener listener);


    /**
    *准备启动组件。此方法应该执行任何操作
	*初始化需要post对象创建。以下
 	* {@link LifecycleEvent}将按照以下顺序触发:
     * <ol>
     *   <li>INIT_EVENT:在组件成功完成时
     *                   initialization.</li>
     * </ol>
     *
     * @exception 如果该组件检测到致命错误,则使用LifecycleException
     * 这将阻止该组件被使用
     */
    public void init() throws LifecycleException;

    /**
    *准备开始积极使用公共方法,而不是
	*此组件的属性getter /setter和生命周期方法。这
	方法应该被调用之前的任何公共方法
	*此组件的属性getter /setter和生命周期方法是
	*利用。下面的{@link LifecycleEvent}将在
	*以下顺序 :
     * <ol>
     *   <li>BEFORE_START_EVENT: At the beginning of the method. It is as this
     *                           point the state transitions to
     *                           {@link LifecycleState#STARTING_PREP}.</li>
     *   <li>START_EVENT: During the method once it is safe to call start() for
     *                    any child components. It is at this point that the
     *                    state transitions to {@link LifecycleState#STARTING}
     *                    and that the public methods other than property
     *                    getters/setters and life cycle methods may be
     *                    used.</li>
     *   <li>AFTER_START_EVENT: At the end of the method, immediately before it
     *                          returns. It is at this point that the state
     *                          transitions to {@link LifecycleState#STARTED}.
     *                          </li>
     * </ol>
     *
     * @exception 如果该组件检测到致命错误,则使用LifecycleException
*防止该组件被使用
     */
    public void start() throws LifecycleException;


    /**
    优雅地终止公共方法的使用
	*此组件的属性getter /setter和生命周期方法。一次
	* STOP_EVENT被触发,公共方法而不是属性
	*不应该使用getter /setter和生命周期方法。以下
	* {@link LifecycleEvent}将按照以下顺序触发:
     * <ol>
     *   <li>BEFORE_STOP_EVENT: At the beginning of the method. It is at this
     *                          point that the state transitions to
     *                          {@link LifecycleState#STOPPING_PREP}.</li>
     *   <li>STOP_EVENT: During the method once it is safe to call stop() for
     *                   any child components. It is at this point that the
     *                   state transitions to {@link LifecycleState#STOPPING}
     *                   and that the public methods other than property
     *                   getters/setters and life cycle methods may no longer be
     *                   used.</li>
     *   <li>AFTER_STOP_EVENT: At the end of the method, immediately before it
     *                         returns. It is at this point that the state
     *                         transitions to {@link LifecycleState#STOPPED}.
     *                         </li>
     * </ol>
     *
    *注意,如果从{@link LifecycleState#转换失败},那么
	*上面的三个事件将被触发,但是组件将转换
	*直接从{@link LifecycleState#FAILED}到
	* {@link LifecycleState# stop},绕过
	* {@link LifecycleState # STOPPING_PREP}
	*
	* @exception LifecycleException如果这个组件检测到一个致命错误
	那件事需要报告
     */
    public void stop() throws LifecycleException;

    /**
    *准备丢弃物品。下面的{@link LifecycleEvent}将会
	*按以下顺序被解雇:
     * <ol>
     *   <li>DESTROY_EVENT: On the successful completion of component
     *                      destruction.</li>
     * </ol>
     *
     * @exception 如果该组件检测到致命错误,则使用LifecycleException
*防止该组件被使用
     */
    public void destroy() throws LifecycleException;


    /**
   获取源组件的当前状态。
     *
     * @return 源组件的当前状态。
     */
    public LifecycleState getState();


    /**
   获取当前组件状态的文本表示。有用的
	JMX的*。这个字符串的格式可能在点释放和点释放之间有所不同
	*不应该被用来确定组件的状态。来确定
	*组件状态,使用{@link #getState()}。
     *
     * @return The name of the current component state.
     */
    public String getStateName();


    /**
    *标记接口,用来指示该实例只能被使用
	*一次。在支持此接口的实例上调用{@link #stop()}
	*将在{@link #stop()}之后自动调用{@link #destroy()}
	*完成。
     */
    public interface SingleUse {
    }
}

Tomcat各个核心的组件有包含与被包含的关系,那么,可以通过父容器启动它的子容器,这样只要启动根容器,即可把其他所有容器都启动,达到统一启动、停止、关闭的效果。

从上面的源码可以看出:
Lifecycle其实就定义了一些状态常量和几个方法,这里主要看init、start、stop三个方法,所有需要被生命周期管理的容器都要实现这个接口,并且各自被父容器的相应方法调用。例如,在初始化阶段,根容器Server组件会调用init方法,而在init方法里会调用它的子容器Service组件的init方法,以此类推。

例如:org.apache.catalina.core.StandardServer#initInternal
在这里插入图片描述

2. 状态转化

public enum LifecycleState {
    NEW(false, null),
    INITIALIZING(false, Lifecycle.BEFORE_INIT_EVENT),
    INITIALIZED(false, Lifecycle.AFTER_INIT_EVENT),
    STARTING_PREP(false, Lifecycle.BEFORE_START_EVENT),
    STARTING(true, Lifecycle.START_EVENT),
    STARTED(true, Lifecycle.AFTER_START_EVENT),
    STOPPING_PREP(true, Lifecycle.BEFORE_STOP_EVENT),
    STOPPING(false, Lifecycle.STOP_EVENT),
    STOPPED(false, Lifecycle.AFTER_STOP_EVENT),
    DESTROYING(false, Lifecycle.BEFORE_DESTROY_EVENT),
    DESTROYED(false, Lifecycle.AFTER_DESTROY_EVENT),
    FAILED(false, null);

    private final boolean available;
    private final String lifecycleEvent;

    private LifecycleState(boolean available, String lifecycleEvent) {
        this.available = available;
        this.lifecycleEvent = lifecycleEvent;
    }

    /**
    *可能除了属性的getter /setter和生命周期之外的公共方法
	*方法被调用的组件在这种状态?它返回
	* true适用于任何处于以下状态的组件:
     * <ul>
     * <li>{@link #STARTING}</li>
     * <li>{@link #STARTED}</li>
     * <li>{@link #STOPPING_PREP}</li>
     * </ul>
     *
     * @return <code>true</code> 如果组件可用,
     *         otherwise false
     * 
	 */
    public boolean isAvailable() {
        return available;
    }

    public String getLifecycleEvent() {
        return lifecycleEvent;
    }
}

其次,在LifeCycle接口内的注释就有详细的状态转换图。
在这里插入图片描述

3. 生命周期事件监听

对于存在状态的转换事件,对事件进行监听,并同时做点什么,这样大大增加了代码的扩展性,并满足了在一定的状态同时做点什么的需求。
一般来说,事件监听器需要三个参与者:

  • 事件对象,用于封装事件的信息,在事件监听器接口的统一方法中作为参数使用,一般继承java. util.EventObject类。
  • 事件源,触发事件的源头,不同的事件源会触发不同的事件类型。
  • 事件监听器,负责监听事件源发出的事件,更确切地说,应该是每当发生事件时,事件源就会调用监听器的统一方法去处理,监听器一般实现java.util.EventListener接口。

而在Tomcat中LifecycleEvent就是事件对象。

/ * *
*一般事件,通知侦听器重大变化的组件
*实现了生命周期接口。
* /
public final class LifecycleEvent extends EventObject {

LifecycleListener为事件监听器接口。


/**
为重要事件(包括“组件”)定义侦听器的接口
*启动"和"组件停止"产生的组件,实现
*生命周期接口。侦听器将在关联状态之后被触发
发生了变化。
 */
public interface LifecycleListener {


    /**
   *确认指定事件的发生。
     *
     * @param 已经发生的事件LifecycleEvent
     */
    public void lifecycleEvent(LifecycleEvent event);
}

。最后缺一个事件源,一般来说,组件和容器就是事件源。Tomcat提供了一个辅助类LifecycleSupport,用于帮助管理该组件或容器上的监听器,里面维护了一个监听器数组,并提供了注册、移除、触发监听器等方法,这样整个监听器框架就完成了。
书上写的是LifecycleSupport,但我在源码里查找到的应该是NotificationBroadcasterSupport。
在这里插入图片描述

其继承接口:NotificationEmitter。
在这里插入图片描述

/**
 * <p>由发出通知的MBean实现的接口。它
*允许侦听器以通知的形式向MBean注册
*侦听器。</p>
 *
 * <h3>通知调度</h3>
 *
 *<p>当MBean发出通知时,它会考虑已发送的每个侦听器
*使用{@link #addNotificationListener addNotificationListener}添加而不是
*随后使用{@link #removeNotificationListener removeNotificationListener}删除。
 *如果侦听器提供了一个过滤器,如果过滤器的
* {@link NotificationFilter#isNotificationEnabled}方法返回
如果错误,听者就被忽略了。否则,侦听器的
方法被调用时使用通知,以及提供给的handback对象
* {@code是addNotificationListener}。</p>
 *
 * <p>如果同一个侦听器被添加了不止一次,那么它被考虑的次数和以前一样多
*补充道。给同一个侦听器添加不同的过滤器或handback通常是有用的对象。</p>
 *
 * <p>此接口的实现可能因方法所在的线程而有所不同
*的过滤器和监听器调用。</p>
 *
 * <p>如果过滤器或侦听器的方法调用抛出{@link异常},则
*异常不应该阻止其他监听器被调用。但是,如果方法
*调用抛出一个{@link错误},建议处理通知
*此时停止,如果有可能应该做将{@code Error}传播给通知的发送方
*</p>
 *
 * <p>此接口应优先用于新代码,而不是
* {@link notification播音员}接口。</p>
 *
 * <p>该接口和{@code notification播音员}的实现
*应该小心同步。特别是,它不是一个好的
*一个实现的想法,以持有任何锁,当它调用
*侦听器。处理侦听器列表可能出现的情况
*当通知被发送时,一个好的策略是
*对这个列表使用{@link CopyOnWriteArrayList}。
 */
public interface NotificationEmitter extends NotificationBroadcaster {
    /**
   从这个MBean中删除一个监听器。MBean必须具有
	*与给定的listener完全匹配的侦听器,
	* filter, handback参数。如果
	*有多个这样的侦听器,只有一个被删除filter and handback参数
	*可能是空,当且仅当他们是空的侦听器
	*删除。< / p >
	以前添加到其中的侦听器 MBean。
	* @param过滤侦听器时指定的过滤器
	* @exception listener不是已注册到MBean,或未注册到已提供filter及handler。
     */
    public void removeNotificationListener(NotificationListener listener,
                                           NotificationFilter filter,
                                           Object handback)
            throws ListenerNotFoundException;
}

其父接口:


public interface NotificationBroadcaster {

  
    public void addNotificationListener(NotificationListener listener,
                                        NotificationFilter filter,
                                        Object handback)
            throws java.lang.IllegalArgumentException;

  
    public void removeNotificationListener(NotificationListener listener)
            throws ListenerNotFoundException;

 
    public MBeanNotificationInfo[] getNotificationInfo();
}

所有监听器通过addLifecycleListener方法交给NotificationBroadcasterSupport对象维护,于是通过调用NotificationBroadcasterSupport对象的fireLifecycleEvent方法遍历所有的监听器,一个一个发送事件,就可以达到触发事件的效果。

4. Log接口

Tomcat中提供了统一的日志接口Log供系统使用。这个接口只提供使用的方法,而不管具体使用什么方法实现日志的记录。接口中提供了多种方法来记录日志,每种方法代表不一样的日志级别,实际使用中根据不同级别使用不同的方法。

/**
 * <p>一个简单的日志接口抽象了日志api。为了
*通过{@link LogFactory}成功实例化实现的类
*这个接口必须有一个构造函数,它接受一个字符串表示该日志的“名称”的参数。</p>
 *
 * <p> Log使用的六个日志级别是(in order):</p>
 * <ol>
 * <li>trace(最不严重)</li>
 * <li>debug</li>
 * <li>info</li>
 * <li>warn</li>
 * <li>error</li>
 * <li>fatal (the most serious)</li>
 * </ol>
 * <p>将这些日志级别映射到底层使用的概念
*日志系统依赖于实现。
*实现应该确保这种顺序的行为
*如预期。</p>
 *
 * 性能通常是一个日志问题。
*通过检查适当的属性,
组件可以避免昂贵的操作(生成信息)
*将被记录)。
 *
 * <p> For example,
 * <code>
 *    if (log.isDebugEnabled()) {
 *        ... do something expensive ...
 *        log.debug(theResult);
 *    }
 * </code>
 * </p>
 *
 * <p>通常会完成底层日志系统的配置
*外部的日志api,通过任何支持的机制
*系统。</p>
 */

在这里插入图片描述
日志架构使用了工厂模式进行设计,LogFactory日志工厂类专门提供一个获取扩展的Log接口的类实例的方法,方便日志的使用。

LogFactory获取到的LogFactory还是一个单例模式。

   public static LogFactory getFactory() throws LogConfigurationException {
        return singleton;
    }

通过getInstance返回Log实例属于DirectJDKLog。


    /**
	构造(如果需要)并返回一个Log实例,
	*使用工厂的当前配置属性集
	*
	*
	NOTE -视实现而定
	*您正在使用的LogFactory, Log
	*你返回的实例可能是,也可能不是当前本地的
	*申请,并可能或不可能再次被退回
	使用相同的参数调用。
	
	*
	* @param name Log实例的逻辑名
	*返回(这个名字的意思)
     *
     * @return 具有请求名称的日志实例
     *
     * @exception LogConfigurationException如果Log
实例不能返回
     */
    public Log getInstance(String name) throws LogConfigurationException {
        if (discoveredLogConstructor == null) {
            return DirectJDKLog.getInstance(name);
        }

        try {
            return discoveredLogConstructor.newInstance(name);
        } catch (ReflectiveOperationException | IllegalArgumentException e) {
            throw new LogConfigurationException(e);
        }
    }

DirectJDKLog 使用JDK自带的日志工具,它是在JDK的java.util.logging日志包基础上进行封装的。

class DirectJDKLog implements Log {

日志的操作频繁而且类似,我们需要一个更高层的类对这些类进行封装,从而更加方便使用。Tomcat中用StringManager类对其进行封装,它提供了两个getString方法,我们一般只会用到这两个方法。

StringManager的使用非常简单,但它的设计比较独特。我们知道在每个Java包里都有对应不同语言的Properties文件,每个包下的Java类的日志信息只需要到对应包下的Properties文件查找中就可以。当一个类要获取属性文件的一个错误信息时,如果要通过StringManager的方法获取,那么它必须实例化一个StringManager对象。
Tomcat为每一个Java包提供一个StringManager对象,相当于一个Java包一个单例,这个单例在包内被所有的类共享,各自的StringManager对象管理各自包下的Properties文件。

    public static final synchronized StringManager getManager(
            String packageName, Locale locale) {

        Map<Locale,StringManager> map = managers.get(packageName);
        if (map == null) {
            /*
            *不希望HashMap扩展到LOCALE_CACHE_SIZE之外。
			*当size()超过容量时发生扩展。因此,保持
			*尺寸达到或低于容量。
			* removeEldestEntry()在插入之后执行,因此测试
			*去除时,需要使用一个小于最大期望尺寸的
             *
             */
            map = new LinkedHashMap<Locale,StringManager>(LOCALE_CACHE_SIZE, 1, true) {
                private static final long serialVersionUID = 1L;
                @Override
                protected boolean removeEldestEntry(
                        Map.Entry<Locale,StringManager> eldest) {
                    if (size() > (LOCALE_CACHE_SIZE - 1)) {
                        return true;
                    }
                    return false;
                }
            };
            managers.put(packageName, map);
        }

        StringManager mgr = map.get(locale);
        if (mgr == null) {
            mgr = new StringManager(packageName, locale);
            map.put(locale, mgr);
        }
        return mgr;
    }

5. AccessLog接口

客户端访问日志一般会记录客户端的访问相关信息,包括客户端IP、请求时间、请求协议、请求方法、请求字节数、响应码、会话ID、处理时间等。通过访问日志可以统计用户访问数量、访问时间分布等规律。

在这里插入图片描述
接口内只提供了日志记录方法,具体实现时则要考虑日志记录在哪里。
在这里插入图片描述

当我们不需要考虑或者同时打印与记录到数据库时,我们可以写一个适配器来做这样的事。

public class AccessLogAdapter implements AccessLog {

    private AccessLog[] logs;

    public AccessLogAdapter(AccessLog log) {
        Objects.requireNonNull(log);
        logs = new AccessLog[] { log };
    }

    public void add(AccessLog log) {
        Objects.requireNonNull(log);
        AccessLog newArray[] = Arrays.copyOf(logs, logs.length + 1);
        newArray[newArray.length - 1] = log;
        logs = newArray;
    }

    @Override
    public void log(Request request, Response response, long time) {
        for (AccessLog log: logs) {
            log.log(request, response, time);
        }
    }

    @Override
    public void setRequestAttributesEnabled(boolean requestAttributesEnabled) {
        // NOOP
    }

    @Override
    public boolean getRequestAttributesEnabled() {
        // NOOP. Could return logs[0].getRequestAttributesEnabled(), but I do
        // not see a use case for that.
        return false;
    }

}

其次,访问日志格式的自定义也可以自己实现。

    protected interface AccessLogElement {
        public void addElement(CharArrayWriter buf, Date date, Request request,
                Response response, long time);
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值