Tomcat启动分析(七) - 容器及相关组件
本文首先分析Container接口及ContainerBase抽象类,然后分析Engine、Host、Context和Pipeline等组件的初始化和启动过程。
Container
Container是一个重要的接口,表示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();
}
- 一个容器可以包含其他容器,Container接口提供了addChild、findChild、findChildren、removeChild和setParent等接口方法;
- 可以向容器添加事件监听器,Container接口提供了addContainerListener、findContainerListeners和removeContainerListener等接口方法,还提供了fireContainerEvent方法用于触发事件;
ContainerBase抽象类是Container接口的默认实现,也是其他标准组件如StandardEngine、StandardWrapper、StandardHost和StandardContext的父类,其部分代码如下:
public abstract class ContainerBase extends LifecycleMBeanBase
implements Container {
// 省略一些代码
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
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();
}
@Override
protected synchronized void startInternal() throws LifecycleException {
// Start our subordinate components, if any
logger = null;
getLogger();
Cluster cluster = getClusterInternal();
if (cluster instanceof Lifecycle) {
((Lifecycle) cluster).start();
}
Realm realm = getRealmInternal();
if (realm instanceof Lifecycle) {
((Lifecycle) realm).start();
}
// 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();
}
// 省略一些代码
}
- pipeline变量引用一个StandardPipeline;
- 初始化过程为自己创建了一个线程池,该线程池用于执行子容器的启动、停止等生命周期过程;
- 启动过程利用初始化过程创建的线程池启动了各个子容器。
Engine
Engine元素表示整个请求处理的机构,它从一个或多个Connector中接收并处理请求,并将响应返回给Connector。Service元素内有且仅能有一个Engine元素。
Engine接口继承了Container接口,StandardEngine类继承了ContainerBase基类并实现了Engine接口,其部分代码如下所示:
public StandardEngine() {
super();
pipeline.setBasic(new StandardEngineValve());
/* Set the jmvRoute using the system property jvmRoute */
try {
setJvmRoute(System.getProperty("jvmRoute"));
} catch(Exception ex) {
log.warn(sm.getString("standardEngine.jvmRouteFail"));
}
// By default, the engine will hold the reloading thread
backgroundProcessorDelay = 10;
}
public void setService(Service service) {
this.service = service;
}
@Override
public void addChild(Container child) {
if (!(child instanceof Host))
throw new IllegalArgumentException
(sm.getString("standardEngine.notHost"));
super.addChild(child);
}
@Override
public void setParent(Container container) {
throw new IllegalArgumentException
(sm.getString("standardEngine.notParent"));
}
@Override
protected void initInternal() throws LifecycleException {
// Ensure that a Realm is present before any attempt is made to start
// one. This will create the default NullRealm if necessary.
getRealm();
super.initInternal();
}
@Override
protected synchronized void startInternal() throws LifecycleException {
// Log our server identification information
if(log.isInfoEnabled())
log.info( "Starting Servlet Engine: " + ServerInfo.getServerInfo());
// Standard container startup
super.startInternal();
}
- 成员变量的命名和注释基本解释了作用,也可参考Engine的配置文档;
- 构造函数为自己的Pipeline添加了基本阀StandardEngineValve;
- initInternale和startInternal都很简单地调用了ContainerBase的对应方法,Engine的addChild只能添加Host,Engine也没有父容器,只有父Service(因为Engine在server.xml中是Service的子元素)。
Host
Host元素表示一个虚拟主机,在Host元素内可以有多个Context元素与之关联以表示不同的Web应用,Engine元素内可以配置多个Host,但其中一个的名称必须与Engine的defaultHost属性值相匹配。
Host接口继承了Container接口,StandardHost类继承了ContainerBase基类并实现了Host接口,其部分代码如下所示。
public class StandardHost extends ContainerBase implements Host {
// ----------------------------------------------------------- Constructors
/**
* Create a new StandardHost component with the default basic Valve.
*/
public StandardHost() {
super();
pipeline.setBasic(new StandardHostValve());
}
// ----------------------------------------------------- Instance Variables
/**
* The set of aliases for this Host.
*/
private String[] aliases = new String[0];
private final Object aliasesLock = new Object();
/**
* The application root for this Host.
*/
private String appBase = "webapps";
private volatile File appBaseFile = null;
/**
* The XML root for this Host.
*/
private String xmlBase = null;
/**
* host's default config path
*/
private volatile File hostConfigBase = null;
/**
* The auto deploy flag for this Host.
*/
private boolean autoDeploy = true;
/**
* The Java class name of the default context configuration class
* for deployed web applications.
*/
private String configClass =
"org.apache.catalina.startup.ContextConfig";
/**
* The Java class name of the default Context implementation class for
* deployed web applications.
*/
private String contextClass = "org.apache.catalina.core.StandardContext";
/**
* The deploy on startup flag for this Host.
*/
private boolean deployOnStartup = true;
/**
* deploy Context XML config files property.
*/
private boolean deployXML = !Globals.IS_SECURITY_ENABLED;
/**
* Should XML files be copied to
* $CATALINA_BASE/conf/<engine>/<host> by default when
* a web application is deployed?
*/
private boolean copyXML = false;
/**
* The Java class name of the default error reporter implementation class
* for deployed web applications.
*/
private String errorReportValveClass =
"org.apache.catalina.valves.ErrorReportValve";
/**
* Unpack WARs property.
*/
private boolean unpackWARs = true;
/**
* Work Directory base for applications.
*/
private String workDir = null;
/**
* Should we create directories upon startup for appBase and xmlBase
*/
private boolean createDirs = true;
/**
* Track the class loaders for the child web applications so memory leaks
* can be detected.
*/
private final Map<ClassLoader, String> childClassLoaders =
new WeakHashMap<>();
/**
* Any file or directory in {@link #appBase} that this pattern matches will
* be ignored by the automatic deployment process (both
* {@link #deployOnStartup} and {@link #autoDeploy}).
*/
private Pattern deployIgnore = null;
private boolean undeployOldVersions = false;
private boolean failCtxIfServletStartFails = false;
@Override
public void addChild(Container child) {
child.addLifecycleListener(new MemoryLeakTrackingListener());
if (!(child instanceof Context))
throw new IllegalArgumentException
(sm.getString("standardHost.notContext"));
super.addChild(child);
}
// 省略一些代码
}
- 成员变量的命名和注释基本解释了作用,也可参考Host的配置文档;
- 构造函数为自己的Pipeline添加了基本阀StandardHostValve;
- Host的addChild只能添加Context。
startInternal方法如下所示,如果容器关联的pipeline中没有错误报告阀(errorReportValveClass),那么就给pipeline添加一个,否则什么也不做。
@Override
protected synchronized void startInternal() throws LifecycleException {
// Set error report valve
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).getConstructor().newInstance();
getPipeline().addValve(valve);
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString(
"standardHost.invalidErrorReportValveClass",
errorValve), t);
}
}
super.startInternal();
}
Context
Context元素表示一个Web应用,运行于一个特定的虚拟主机内。每个Web应用可以基于WAR文件,也可以基于相应的未打包目录。Catalina根据HTTP请求URI基于最长匹配选择处理该请求的Web应用,一旦选择,该Context就会根据定义好的servlet映射选择一个适当的servlet去处理。
Context接口继承了Container接口,StandardContext类继承了ContainerBase基类并实现了Context接口,其部分代码如下所示。
public class StandardContext extends ContainerBase
implements Context, NotificationEmitter {
/**
* The display name of this web application.
*/
private String displayName = null;
/**
* Override the default context xml location.
*/
private String defaultContextXml;
/**
* Override the default web xml location.
*/
private String defaultWebXml;
/**
* The distributable flag for this web application.
*/
private boolean distributable = false;
/**
* The document root for this web application.
*/
private String docBase = null;
/**
* The reloadable flag for this web application.
*/
private boolean reloadable = false;
/**
* Encoded path.
*/
private String encodedPath = null;
/**
* Unencoded path for this web application.
*/
private String path = null;
// 省略一些代码
public StandardContext() {
super();
pipeline.setBasic(new StandardContextValve());
broadcaster = new NotificationBroadcasterSupport();
// Set defaults
if (!Globals.STRICT_SERVLET_COMPLIANCE) {
// Strict servlet compliance requires all extension mapped servlets
// to be checked against welcome files
resourceOnlyServlets.add("jsp");
}
}
// 省略一些代码
}
- 成员变量的命名和注释基本解释了作用,也可参考Context的配置文档;
- 构造函数为自己的Pipeline添加了基本阀StandardContextValve;
- Context的addChild只能添加Wrapper。
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是由一系列阀组成的链表,基础阀始终指向最后一个阀;
- 其余的方法与链表操作有关,如添加、移除阀等方法。
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,具体可参考阀的配置文档。
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方法处理请求,处理规约可以参考API文档。