1、容器组件的监听器
已知在tomcat中context代表一个个应用程序。创建Context等组件时会同时注册对应的监听器,监听器都实现了LifecycleListener接口。在Catalina容器启动过程中会加载server.xml配置文件,并通过Digester完成解析,注意到Catalina.createStartDigester()方法中有这样几行代码:
digester.addRuleSet(new EngineRuleSet("Server/Service/"));
digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"));
digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Host/"));
表示向Digester实例中添加多个规则(RuleSet),HostRuleSet继承了RuleSetBase类,并实现了它的抽像方法:addRuleInstances。
在HostRuleSet类的addRuleInstances方法中为Host节点添加一个org.apache.catalina.startup.HostConfig对象作为org.apache.catalina.core.StandardHost对象的监听器:
public void addRuleInstances(Digester digester) {
digester.addObjectCreate(prefix + "Host",
"org.apache.catalina.core.StandardHost",
"className");
digester.addSetProperties(prefix + "Host");
digester.addRule(prefix + "Host",
new CopyParentClassLoaderRule());
digester.addRule(prefix + "Host",
new LifecycleListenerRule
("org.apache.catalina.startup.HostConfig",
"hostConfigClass"));
digester.addSetNext(prefix + "Host",
"addChild",
"org.apache.catalina.Container");
//...
}
类似地,ContextRuleSet中的addRuleInstances方法中,为Engine节点添加EngineConfig对象作为StandardEngine的监听器。 EngineRuleSet中的addRuleInstances方法中,为Context节点添加ContextConfig对象作为StandardContext的监听器。
2、启动过程中web应用程序的发布
从tomcat启动过程可知,当StandardHost启动时会触发Lifecycle. START_EVENT事件,然后通知所有注册在host上的监听器。
从上面的分析我们已经知道HostConfig是StandardHost的监听器。在HostConfig的lifecycleEvent方法中可以看到,Host组件是如何响应Lifecycle. START _EVENT事件的:
public void lifecycleEvent(LifecycleEvent event) {
// Identify the host we are associated with
try {
host = (Host) event.getLifecycle();
if (host instanceof StandardHost) {
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;
}
// Process the event that has occurred
if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) {
check();
} else if (event.getType().equals(Lifecycle.START_EVENT)) {
start();
} else if (event.getType().equals(Lifecycle.STOP_EVENT)) {
stop();
}
}
如果事件类型是Lifecycle. START _EVENT,执行此监听器(HostConfig对象)的start方法()。
public void start() {
if (log.isDebugEnabled())
log.debug(sm.getString("hostConfig.start"));
// 为Host注册JMX MBean
try {
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.error(sm.getString("hostConfig.jmx.register", oname), e);
}
// 创建appBase目录,Context配置目录(也即conf/Catalina/localhost/)
if (host.getCreateDirs()) {
File[] dirs = new File[] { appBase(), configBase() };
for (int i = 0; i < dirs.length; i++) {
if (!dirs[i].mkdirs() && !dirs[i].isDirectory()) {
log.error(sm.getString("hostConfig.createDirs", dirs[i]));
}
}
}
// 如果Host的appBase目录(默认为wabapps)不存在,则设置deployOnStartup为true
if (!appBase().isDirectory()) {
log.error(sm.getString("hostConfig.appBase", host.getName(), appBase().getPath()));
host.setDeployOnStartup(false);
host.setAutoDeploy(false);
}
// 部署Web应用
if (host.getDeployOnStartup())
deployApps();
}
Start()方法最后调用deployApps()方法发布应用。
protected void deployApps() {
File appBase = appBase();//conf/[Engine Name]/[Host Name]
File configBase = configBase();//(默认webapps)
String[] filteredAppPaths = filterAppPaths(appBase.list());
// Deploy XML descriptors from configBase 部署configBase中的XML描述符
deployDescriptors(configBase, configBase.list());
// Deploy WARs 部署appBase下的war工程
deployWARs(appBase, filteredAppPaths);
// Deploy expanded folders 部署扩展文件夹
deployDirectories(appBase, filteredAppPaths);
}
可以看到发布应用的方法有3种,XML文件描述符、WAR包、文件目录(webapp)。这三种方法,都是创建Context对象,将构建好的Context对象与Host组件关联(调用host.addChild(context))。
以deployDirectories为例,在部署的过程最后有几行,会新创建的Context对象,并添加一个监听器,这里监听器的名称是configClass变量,其默认值就是ContextConfig。然后设置context的一些属性name,path,version等,最后添加到父容器host中。
Class clazz = Class.forName(host.getConfigClass());
LifecycleListener listener =
(LifecycleListener) clazz.newInstance();
context.addLifecycleListener(listener);
context.setName(cn.getName());
context.setPath(cn.getPath());
context.setWebappVersion(cn.getVersion());
context.setDocBase(cn.getBaseName());
host.addChild(context);
host的addChild方法:
public void addChild(Container child) {
child.addLifecycleListener(new MemoryLeakTrackingListener());
if (!(child instanceof Context))
throw new IllegalArgumentException
(sm.getString("standardHost.notContext"));
super.addChild(child);
}
其中调用父类(ContainerBase)的addChild方法,此方法又调用addChildInternal:
private void addChildInternal(Container child) {
if( log.isDebugEnabled() )
log.debug("Add child " + child + " " + this);
synchronized(children) {
if (children.get(child.getName()) != null)
throw new IllegalArgumentException("addChild: Child name '" +
child.getName() +
"' is not unique");
child.setParent(this); // May throw IAE
children.put(child.getName(), child);
}
try {
if ((getState().isAvailable() ||
LifecycleState.STARTING_PREP.equals(getState())) &&
startChildren) {
child.start();
}
} catch (LifecycleException e) {
log.error("ContainerBase.addChild: start: ", e);
throw new IllegalStateException("ContainerBase.addChild: start: " + e);
} finally {
fireContainerEvent(ADD_CHILD_EVENT, child);
}
}
从上面方法中可以看到会调用子容器的start方法,就是指调用StandardContext的start方法。也就是说,给host添加子容器context时调用context的start方法启动子容器。StandardContext的startInternal方法会触发事件CONFIGURE_START_EVENT。
// Notify our interested LifecycleListeners
fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);
前面已知在创建Context对象时都会注册一个监听器ContextConfig,我们看看这个类的lifecycleEvent方法中监听了哪些事件:
public void lifecycleEvent(LifecycleEvent event) {
// Identify the context we are associated with
try {
context = (Context) event.getLifecycle();
} catch (ClassCastException e) {
log.error(sm.getString("contextConfig.cce", event.getLifecycle()), e);
return;
}
// Process the event that has occurred
if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
configureStart();
} else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
beforeStart();
} else if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) {
// Restore docBase for management tools
if (originalDocBase != null) {
context.setDocBase(originalDocBase);
}
} else if (event.getType().equals(Lifecycle.CONFIGURE_STOP_EVENT)) {
configureStop();
} else if (event.getType().equals(Lifecycle.AFTER_INIT_EVENT)) {
init();
} else if (event.getType().equals(Lifecycle.AFTER_DESTROY_EVENT)) {
destroy();
}
}
如果事件是Lifecycle.CONFIGURE_START_EVENT,则调用configureStart方法,此方法将直接调用webConfig(),web.xml文件的解析就是由这个方法完成的。
网易云新用户大礼包:https://www.163yun.com/gift
本文来自网易实践者社区,经作者何欢授权发布。