坚持一下,把源码看完,勤奋一点,不要在懒惰了,你已经落下别人很多了
本文主要介绍Tomcat 是如何加载webapps下的项目
一、server.xml
在server.xml中有如下关于Host配置的一段代码
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
Host 的appBase:指定了存放web项目的文件夹 是webapps
二、加载流程
1、Digester解析server.xml
Catalina.createStartDigester 有如下一部分代码:
digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"));
HostRuleSet的addRuleInstances中有如下代码
digester.addRule(prefix + "Host",
new LifecycleListenerRule
("org.apache.catalina.startup.HostConfig",
"hostConfigClass"));
digester解析Host配置信息的时候定义了这么一条规则, LifecycleListenerRule,
查看该规则的构造方法
public LifecycleListenerRule(String listenerClass, String attributeName) {
this.listenerClass = listenerClass;
this.attributeName = attributeName;
}
也就是传入构造方法的两个参数一个代表了listenerClass的类名称,一个表示属性名称, LifecycleListenerRule的begin方法
public void begin(String namespace, String name, Attributes attributes)
throws Exception {
Container c = (Container) digester.peek();
Container p = null;
Object obj = digester.peek(1);
if (obj instanceof Container) {
p = (Container) obj;
}
String className = null;
// Check the container for the specified attribute
if (attributeName != null) {
String value = attributes.getValue(attributeName);
if (value != null)
className = value;
}
// Check the container's parent for the specified attribute
if (p != null && className == null) {
String configClass =
(String) IntrospectionUtils.getProperty(p, attributeName);
if (configClass != null && configClass.length() > 0) {
className = configClass;
}
}
// Use the default
if (className == null) {
className = listenerClass;
}
// Instantiate a new LifecycleListener implementation object
Class<?> clazz = Class.forName(className);
LifecycleListener listener =
(LifecycleListener) clazz.newInstance();
// Add this LifecycleListener to our associated component
c.addLifecycleListener(listener);
}
最终可以确定Container c 指定的是Host对象 StandardHost类的实例
listener是 org.apache.catalina.startup.HostConfig 对象的实例,
通过调用 c.addLifecycleListener(listener); 将该listener 与StandardHost进行绑定。
2、org.apache.catalina.startup.HostConfig
当Tomcat启动的时候,最终加载webapps文件夹下的项目最终交由org.apache.catalina.startup.HostConfig 来处理。
HostConfig 实现了LifecycleListener接口,对接口定义的lifecycleEvent方法 提供了自己的实现。
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.BEFORE_START_EVENT)) {
beforeStart();
} else if (event.getType().equals(Lifecycle.START_EVENT)) {
start();
} else if (event.getType().equals(Lifecycle.STOP_EVENT)) {
stop();
}
}
start方法会调用deployApps 方法。
deployApps方法如下:
protected void deployApps() {
File appBase = appBase();// 在server.xml中的Host标签指定appbase的属性为 webapps
File configBase = configBase();
String[] filteredAppPaths = filterAppPaths(appBase.list());//列出appBase下的所有文件、文件夹,进行过滤
// Deploy XML descriptors from configBase
deployDescriptors(configBase, configBase.list());
// Deploy WARs
deployWARs(appBase, filteredAppPaths);//部署war包
// Deploy expanded folders
deployDirectories(appBase, filteredAppPaths);//部署项目文件夹
}
deployDirestories代码如下:
protected void deployDirectories(File appBase, String[] files) {
if (files == null)
return;
ExecutorService es = host.getStartStopExecutor();
List<Future<?>> results = new ArrayList<Future<?>>();
for (int i = 0; i < files.length; i++) {
if (files[i].equalsIgnoreCase("META-INF"))
continue;
if (files[i].equalsIgnoreCase("WEB-INF"))
continue;
File dir = new File(appBase, files[i]);
if (dir.isDirectory()) {
ContextName cn = new ContextName(files[i], false);
if (isServiced(cn.getName()) || deploymentExists(cn.getName()))
continue;
results.add(es.submit(new DeployDirectory(this, cn, dir)));
}
}
for (Future<?> result : results) {
try {
result.get();
} catch (Exception e) {
log.error(sm.getString(
"hostConfig.deployDir.threaded.error"), e);
}
}
}
DeployDirectory代码如下:
private static class DeployDirectory implements Runnable {
private HostConfig config;
private ContextName cn;
private File dir;
public DeployDirectory(HostConfig config, ContextName cn, File dir) {
this.config = config;
this.cn = cn;
this.dir = dir;
}
@Override
public void run() {
config.deployDirectory(cn, dir);
}
}
Hostconfig.deployDirectory代码如下:
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(configBase(), cn.getBaseName() + ".xml");
DeployedApplication deployedApp;
boolean copyThisXml = copyXML;
try {
if (deployXML && 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) {
InputStream is = null;
OutputStream os = null;
try {
is = new FileInputStream(xml);
os = new FileOutputStream(xmlCopy);
IOTools.flow(is, os);
// Don't catch IOE - let the outer try/catch handle it
} finally {
try {
if (is != null) is.close();
} catch (IOException e){
// Ignore
}
try {
if (os != null) os.close();
} catch (IOException e){
// Ignore
}
}
context.setConfigFile(xmlCopy.toURI().toURL());
} else {
context.setConfigFile(xml.toURI().toURL());
}
} else if (!deployXML && 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).newInstance();//创建StandardContext的实例
}
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);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString("hostConfig.deployDir.error",
dir.getAbsolutePath()), t);
} finally {
deployedApp = new DeployedApplication(cn.getName(),
xml.exists() && deployXML && 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 (deployXML && 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)));
}
}
通过上面的代码可以确认,Host添加child 会把webapps文件夹下的项目添加进去,并且直接调用child的start方法。context 添加了一个listener,该listener 是类
org.apache.catalina.startup.ContextConfig 的实例,在StandardHost中有定义:private String configClass ="org.apache.catalina.startup.ContextConfig";
3、org.apache.catalina.startup.ContextConfig
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();
}
}
结论: 将web项目部署到tomcat之后
tomcat启动:Catalina.start-->Server.start-->Service.start-->StandardEngine.start-->StandardHost.start
---》根据server.xml配置的Host的appbase 确认 存放web项目的位置,也就是 Tomcat根路径下的webapps文件夹
--》扫描webapps文件夹下的所有文件/文件夹,根据文件/文件夹的名字命名context,context默认是StandardContext的实例
--》将生成的context通过调用Host的addChild方法 添加到Host中,这里的Host 是StandardHost的实例
--》addChild 的时候 会自动调用child 的start方法。 这里会调用StandardContext的start方法。
至于对StandardContext的分析,放在后面进行。