目录
Catalina中解析server.xml的配置规则
Lifecycle/LifecycleBase
ContainerBase
initInternal
startInternal
threadStart
backgroundProcess
StandardEngine
initInternal
startInternal
EngineConfig
StandardHost
initInternal
startInternal
HostConfig
beforeStart
start
stop
check
StandardContext
才疏学浅,现在阅读源码也是学习笔记,大神勿喷,主要是对web应用程序启动加载的阅读
Connector组件最终调用如下方法将请求送入容器处理,Tomcat中的Container容器有四个子容器,分别是Engine,Host,Context,Wrapper,这四个按顺序之间是父子关系,还有个Pipeline管道的概念,pipeline中链表结构,存放Value组件,每个容器中都有pipeline,责任链模式,请求会在容器的管道每个Value中依次处理
connector.getService().getContainer().getPipeline().getFirst().invoke(
request, response);
如下图的XML配置文件,一个Engine容器管理多个虚拟主机,每个Host下可以管理多个应用程序Context,每个Context中又存在多个处理程序的Warpper(Servlet)
另外的Realm组件是进行权限控制(加载tomcat-user.xml访问应用时验证),Context下Value组件是对每次请求进行日志处理
org.apache.catalina.startup.Catalina#createStartDigester
tomcat启动时,解析server.xml,配置对各组件的解析规则,再一步一步根据规则,从父组件到子组件,挨个解析、初始化、启动。
EngineRuleSet
HostRuleSet
ContextRuleSet
为三个容器都加上了一个生命周期事件监听器,分别是EngineConfig, HostConfig, ContextConfig,默认是这三类,可以自定义。这三个监听器在对应容器初始化,启动前等对容器进行配置等,例如ContextConfig在启动Context前,查找应用程序的web.xml,以及获取初始化应用程序的类(SpringBoot程序获取SpringBootServletInitializer之类)。
tomcat几乎所有组件都继承了这个类,控制生命周期,其中定义了init()、start()、stop()等入口方法,组件都使用这些方法进行启动和终止。主要就是设置当前容器状态并对当前组件监听器发布相关事件,例如init_before\init_after\start_before\start_after等等,再调用子类实现的initInternal()、startInternal()等方法处理核心逻辑
StandardEngine,StandardHost,StandardContext,StandardWrapper四个容器实现类都继承自父类ContainerBase,先来看父类中进行了什么操作
初始化核心方法,初始化前后会发布 before_init 和 after_init 事件,容器监听器处理事件
创建了一个用于启动/停止子容器的线程池,用来异步启动/停止子容器
public abstract class ContainerBase extends LifecycleMBeanBase
implements Container {
@Override
protected void initInternal() throws LifecycleException {
// 启动和停止子容器使用的线程池,异步操作子容器
BlockingQueue startStopQueue = new LinkedBlockingQueue<>();
startStopExecutor = new ThreadPoolExecutor(
getStartStopThreadsInternal(),
getStartStopThreadsInternal(), 10, TimeUnit.SECONDS,
startStopQueue,
new StartStopThreadFactory(getName() + "-startStop-"));
startStopExecutor.allowCoreThreadTimeOut(true);
// 注册MBean
super.initInternal();
}
}
启动容器核心方法,启动前后会发布 before_start 和 after_start 事件,容器监听器处理事件
启动容器下配置的集群/权限组件,并异步启动子容器,发布当前容器Starting事件,启动后台周期任务线程
@Override
protected synchronized void startInternal() throws LifecycleException {
// 启动子容器和各种组件
logger = null;
getLogger();
// 自带集群组件,session同步
Cluster cluster = getClusterInternal();
if (cluster instanceof Lifecycle) {
((Lifecycle) cluster).start();
}
// Realm权限控制组件启动
Realm realm = getRealmInternal();
if (realm instanceof Lifecycle) {
((Lifecycle) realm).start();
}
// 线程池异步启动子容器
// StartChild任务里边就是child.start();一行
Container children[] = findChildren();
List> results = new ArrayList<>();
for (int i = 0; i < children.length; i++) {
results.add(startStopExecutor.submit(new StartChild(children[i])));
}
MultiThrowable multiThrowable = null;
for (Future result : results) {
try {
result.get();
} catch (Throwable e) {
log.error(sm.getString("containerBase.threadedStartFailed"), e);
if (multiThrowable == null) {
multiThrowable = new MultiThrowable();
}
multiThrowable.add(e);
}
}
if (multiThrowable != null) {
throw new LifecycleException(sm.getString("containerBase.threadedStartFailed"),
multiThrowable.getThrowable());
}
// 启动管道中的链式Value组件
if (pipeline instanceof Lifecycle) {
((Lifecycle) pipeline).start();
}
// 设置启动中状态,发布容器STARTING事件
setState(LifecycleState.STARTING);
// 每个容器都会有后台线程周期性的执行后台任务
threadStart();
}
启动线程,周期的执行当前容器及子容器的后台任务,默认情况下,Engine会10秒一次执行子容器的后台任务
protected void threadStart() {
// 当前容器已经启动就return
if (thread != null)
return;
// 如果配置的后台周期任务间隔事件大于0才会启动线程执行后台任务
// 默认Engine初始化为10,会启动一个
// 除此之外其他容器server.xml不配置的话都默认-1,不启动
if (backgroundProcessorDelay <= 0)
return;
threadDone = false;
String threadName = "ContainerBackgroundProcessor[" + toString() + "]";
thread = new Thread(new ContainerBackgroundProcessor(), threadName);
thread.setDaemon(true);
thread.start();
}
protected class ContainerBackgroundProcessor implements Runnable {
@Override
public void run() {
// 删除了try-catch-finally代码
while (!threadDone) {
try {
Thread.sleep(backgroundProcessorDelay * 1000L);
} catch (InterruptedException e) {
// Ignore
}
if (!threadDone) {
// 依次执行当前容器的backgroundProcess方法和子容器的backgroundProcess方法
processChildren(ContainerBase.this);
}
}
}
protected void processChildren(Container container) {
ClassLoader originalClassLoader = null;
try {
if (container instanceof Context) {
Loader loader = ((Context) container).getLoader();
if (loader == null) {
return;
}
originalClassLoader = ((Context) container).bind(false, null);
}
// 执行本容器的backgroundProcess方法
container.backgroundProcess();
Container[] children = container.findChildren();
for (int i = 0; i < children.length; i++) {
// 如果子容器的间隔时间未设置才会继续调用子容器的backgroundProcess方法
// 因为如果设置了间隔时间,该容器会有自己的后台线程去执行
if (children[i].getBackgroundProcessorDelay() <= 0) {
processChildren(children[i]);
}
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
} finally {
if (container instanceof Context) {
((Context) container).unbind(false, originalClassLoader);
}
}
}
}
容器后台周期执行的动作
执行该容器下各组件的 backgroundProcess 后台任务,并发布当前容器的 periodic 周期事件
@Override
public void backgroundProcess() {
if (!getState().isAvailable())
return;
Cluster cluster = getClusterInternal();
if (cluster != null) {
try {
cluster.backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString("containerBase.backgroundProcess.cluster",
cluster), e);
}
}
Realm realm = getRealmInternal();
if (realm != null) {
try {
realm.backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString("containerBase.backgroundProcess.realm", realm), e);
}
}
Valve current = pipeline.getFirst();
while (current != null) {
try {
current.backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString("containerBase.backgroundProcess.valve", current), e);
}
current = current.getNext();
}
fireLifecycleEvent(Lifecycle.PERIODIC_EVENT, null);
}
没什么重要的,主要就是设置了backgroundProcessorDelay参数,在engine中启动了执行后台周期任务的线程,默认情况下子容器host,context等都不会设置这个参数,因此后台任务线程默认情况只有一个
public StandardEngine() {
super();
// engine容器具有一个基本的处理请求的Value组件StandardEngineValve
pipeline.setBasic(new StandardEngineValve());
try {
setJvmRoute(System.getProperty("jvmRoute"));
} catch(Exception ex) {
log.warn(sm.getString("standardEngine.jvmRouteFail"));
}
// engine默认存在一个周期任务线程
// 间隔10秒执行一次engine和其子容器的backgroundProcess 方法
backgroundProcessorDelay = 10;
}
@Override
protected void initInternal() throws LifecycleException {
// 获取Realm配置
getRealm();
// 执行ContainerBase中方法,创建startstop线程池
super.initInternal();
}
@Override
public Realm getRealm() {
// 获取xml中配置的Realm
Realm configured = super.getRealm();
// 确保engine存在一个Realm对象,如果没配置给个空实现
if (configured == null) {
configured = new NullRealm();
this.setRealm(configured);
}
return configured;
}
啥也没干,打印了一行信息 ,直接走父类,启动子组件和子容器
@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容器的默认生命周期监听器,继承自LifecycleListener,关注Start和Stop事件,没干实质的东西,打印了两行
@Override
public void lifecycleEvent(LifecycleEvent event) {
// Identify the engine we are associated with
try {
engine = (Engine) event.getLifecycle();
} catch (ClassCastException e) {
log.error(sm.getString("engineConfig.cce", event.getLifecycle()), e);
return;
}
// Process the event that has occurred
if (event.getType().equals(Lifecycle.START_EVENT))
start();
else if (event.getType().equals(Lifecycle.STOP_EVENT))
stop();
}
protected void start() {
if (engine.getLogger().isDebugEnabled())
engine.getLogger().debug(sm.getString("engineConfig.start"));
}
protected void stop() {
if (engine.getLogger().isDebugEnabled())
engine.getLogger().debug(sm.getString("engineConfig.stop"));
}
public StandardHost() {
super();
// 设置默认的Host容器的基础Value组件
pipeline.setBasic(new StandardHostValve());
}
Host并没有自己独有的初始化动作,走父类ContainerBase.initInternal方法,初始化start-stop线程池
为当前Host容器增加一个Value组件,ErrorReportValve,用来返回如下的错误界面
@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();
}
Host容器的监听器,用来部署其下配置的context,所谓部署,即就是根据配置将对应路径下的应用文件夹/War包封装为一个Context容器对象添加到当前Host子容器,然后启动Context容器,并监听该Context的配置文件,如果被修改了,进行重新部署
public class HostConfig implements LifecycleListener {
public void lifecycleEvent(LifecycleEvent event) {
// Identify the host we are associated with
try {
host = (Host) event.getLifecycle();
if (host instanceof StandardHost) {
// 将StandardHost一些属性设置到HostConfig中
setCopyXML(((StandardHost) host).isCopyXML());
setDeployXML(((StandardHost) host).isDeployXML());
setUnpackWARs(((StandardHost) host).isUnpackWARs());
setContextClass(((StandardHost) host).getContextClass());
}
} catch (ClassCastException e) {
log.error(sm.getString("hostConfig.cce", event.getLifecycle()), e);
return;
}
// 处理各种事件
if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) {
// 周期任务事件,监听各应用配置文件,如果修改,重新部署/加载
check();
} else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
// 启动前事件 before_start
beforeStart();
} else if (event.getType().equals(Lifecycle.START_EVENT)) {
// start事件
start();
} else if (event.getType().equals(Lifecycle.STOP_EVENT)) {
// 停止事件
stop();
}
}
}
Host容器startInternal() 之前发布before_start事件,进入此方法
判断两个文件夹是否存在,否则创建:例如我的D:\Tomcat\SourceCode\tomcat8\webapps 应用文件夹 和 D:\Tomcat\SourceCode\tomcat8\conf\Catalina\localhost 存放host下应用的配置文件
public void beforeStart() {
if (host.getCreateDirs()) {
File[] dirs = new File[] {host.getAppBaseFile(),host.getConfigBaseFile()};
for (int i=0; i
多途径部署应用Context
public void start() {
try {
// 将当前HostConfig注册MBean
ObjectName hostON = host.getObjectName();
oname = new ObjectName
(hostON.getDomain() + ":type=Deployer,host=" + host.getName());
Registry.getRegistry(null, null).registerComponent
(this, oname, this.getClass().getName());
} catch (Exception e) {
log.warn(sm.getString("hostConfig.jmx.register", oname), e);
}
// 默认启动时自动部署host下的app, 可xml中配置Host.deployOnStartup属性修改
if (host.getDeployOnStartup())
deployApps();
}
// 部署
protected void deployApps() {
// %TOMCAT_HOME%\webapps
File appBase = host.getAppBaseFile();
// %TOMCAT_HOME%\conf\Catalina\localhost
File configBase = host.getConfigBaseFile();
// 获取%TOMCAT_HOME%\webapps下的文件列表
// Host标签有个参数可以控制哪个Context不加载,例如 deployIgnore="manager",即manager应用不加载
String[] filteredAppPaths = filterAppPaths(appBase.list());
// Deploy XML descriptors from configBase
deployDescriptors(configBase, configBase.list());
// Deploy WARs
deployWARs(appBase, filteredAppPaths);
// Deploy expanded folders
deployDirectories(appBase, filteredAppPaths);
}
这里需要解释一下,Tomcat部署应用有多种途径
tomcat用以上几种方式,将应用添加到当前Host容器中,并寻找web.xml文件开始启动应用程序。
相对应的deployApps方法中 deployDescriptors 就是第四种方式,deployWARs第二种,deployDirectories则是最基本的第一种方式
deployDirectory
获取到webapps下的文件夹列表后,每个文件夹都是一个应用,异步部署,下边是单个应用部署的代码 。
每个应用都对应一个StandardContext容器,所谓部署就是实例化StandardContext对象,将context添加到当前host容器,启动context,并注册context的配置文件到Map中监听,如果修改,会在周期任务执行时重新部署
protected void deployDirectory(ContextName cn, File dir) {
long startTime = 0;
// Deploy the application in this directory
if( log.isInfoEnabled() ) {
startTime = System.currentTimeMillis();
log.info(sm.getString("hostConfig.deployDir",
dir.getAbsolutePath()));
}
Context context = null;
File xml = new File(dir, Constants.ApplicationContextXml);
File xmlCopy =
new File(host.getConfigBaseFile(), cn.getBaseName() + ".xml");
DeployedApplication deployedApp;
boolean copyThisXml = isCopyXML();
boolean deployThisXML = isDeployThisXML(dir, cn);
try {
if (deployThisXML && xml.exists()) {
synchronized (digesterLock) {
try {
context = (Context) digester.parse(xml);
} catch (Exception e) {
log.error(sm.getString(
"hostConfig.deployDescriptor.error",
xml), e);
context = new FailedContext();
} finally {
digester.reset();
if (context == null) {
context = new FailedContext();
}
}
}
if (copyThisXml == false && context instanceof StandardContext) {
// Host is using default value. Context may override it.
copyThisXml = ((StandardContext) context).getCopyXML();
}
if (copyThisXml) {
Files.copy(xml.toPath(), xmlCopy.toPath());
context.setConfigFile(xmlCopy.toURI().toURL());
} else {
context.setConfigFile(xml.toURI().toURL());
}
} else if (!deployThisXML && xml.exists()) {
// Block deployment as META-INF/context.xml may contain security
// configuration necessary for a secure deployment.
log.error(sm.getString("hostConfig.deployDescriptor.blocked",
cn.getPath(), xml, xmlCopy));
context = new FailedContext();
} else {
context = (Context) Class.forName(contextClass).getConstructor().newInstance();
}
Class> clazz = Class.forName(host.getConfigClass());
LifecycleListener listener = (LifecycleListener) clazz.getConstructor().newInstance();
context.addLifecycleListener(listener);
context.setName(cn.getName());
context.setPath(cn.getPath());
context.setWebappVersion(cn.getVersion());
context.setDocBase(cn.getBaseName());
host.addChild(context);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString("hostConfig.deployDir.error",
dir.getAbsolutePath()), t);
} finally {
deployedApp = new DeployedApplication(cn.getName(),
xml.exists() && deployThisXML && copyThisXml);
// Fake re-deploy resource to detect if a WAR is added at a later
// point
deployedApp.redeployResources.put(dir.getAbsolutePath() + ".war",
Long.valueOf(0));
deployedApp.redeployResources.put(dir.getAbsolutePath(),
Long.valueOf(dir.lastModified()));
if (deployThisXML && xml.exists()) {
if (copyThisXml) {
deployedApp.redeployResources.put(
xmlCopy.getAbsolutePath(),
Long.valueOf(xmlCopy.lastModified()));
} else {
deployedApp.redeployResources.put(
xml.getAbsolutePath(),
Long.valueOf(xml.lastModified()));
// Fake re-deploy resource to detect if a context.xml file is
// added at a later point
deployedApp.redeployResources.put(
xmlCopy.getAbsolutePath(),
Long.valueOf(0));
}
} else {
// Fake re-deploy resource to detect if a context.xml file is
// added at a later point
deployedApp.redeployResources.put(
xmlCopy.getAbsolutePath(),
Long.valueOf(0));
if (!xml.exists()) {
deployedApp.redeployResources.put(
xml.getAbsolutePath(),
Long.valueOf(0));
}
}
addWatchedResources(deployedApp, dir.getAbsolutePath(), context);
// Add the global redeploy resources (which are never deleted) at
// the end so they don't interfere with the deletion process
addGlobalRedeployResources(deployedApp);
}
deployed.put(cn.getName(), deployedApp);
if( log.isInfoEnabled() ) {
log.info(sm.getString("hostConfig.deployDir.finished",
dir.getAbsolutePath(), Long.valueOf(System.currentTimeMillis() - startTime)));
}
}
只是从MBean注册表中移除当前Host
Engine启动了一个线程定时执行子容器的backgroundProcess()后台处理方法,执行时会发布periodic周期事件,check即事件触发后执行的方法,HostConfig对已经部署的应用配置文件(conf/localhost/xxx.xml,META-INF/content.xml)进行检测,如果文件修改被修改,重新部署该应用程序
监听的配置文件列表,修改context.xml重新部署,修改了web.xml重新加载应用Servlet、Filter等:
Tomcat源码笔记(八)Context