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