HostConfig是standedHost的生命周期状态的监听类,主要是根据监听状态进行相应的操作。今天我们主要讲start状态下,hostConfig的解析过程,大概的解析过程如下:
1.获取到start监听。lifecycleEvent(LifecycleEvent event)是具体的状态处理方法,在里面根据状态判断走具体的步骤,当状态为start时,将会调用start()方法。代码如下:
public void lifecycleEvent(LifecycleEvent event) {
........
// Process the event that has occurred
if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) {
check();
} else if (event.getType().equals(Lifecycle.START_EVENT)) {
//当状态为start时,调用start方法
start();
} else if (event.getType().equals(Lifecycle.STOP_EVENT)) {
stop();
}
}
2.进入start方法后,首先创建appBase、xmlBase目录,如果存在就不会进行创建,其次判断host.getAppBaseFile()是否为目录,如果是非目录将会设置DeployOnStartup和AutoDeploy为false。最后判断DeployOnStartup是否为true,如果为true将会deployApps()方法发布应用。代码如下:
public void start() {
..........
if (host.getCreateDirs()) {//判断是否需要创建appBase(webapps绝对路径)、configBase(conf\Catalina\localhost的绝对路径)目录
File[] dirs = new File[] {host.getAppBaseFile(),host.getConfigBaseFile()};
for (int i=0; i<dirs.length; i++) {
if (!dirs[i].mkdirs() && !dirs[i].isDirectory()) {
log.error(sm.getString("hostConfig.createDirs",dirs[i]));
}
}
}
if (!host.getAppBaseFile().isDirectory()) {//如果webapps不是目录,将会设置DeployOnStartup和AutoDeploy为false
log.error(sm.getString("hostConfig.appBase", host.getName(),
host.getAppBaseFile().getPath()));
host.setDeployOnStartup(false);
host.setAutoDeploy(false);
}
//判断DeployOnStartup是否为true,如果为true将会deployApps()方法发布应用
if (host.getDeployOnStartup())
deployApps();
}
3.进入deployApps方法,将会对Descriptors(描述符,catalina/localhost/下的xml文件解析及发布)、WARs(webapps下的war包)、directories(webapps下的目录应用)进行发布。代码如下:
protected void deployApps() {
File appBase = host.getAppBaseFile();
File configBase = host.getConfigBaseFile();
String[] filteredAppPaths = filterAppPaths(appBase.list());
// 加载并解析catalina/localhost/目录下的xxx.xml文件,读取Context标签(<Context path="/helloapp" docBase="C:\ chapter03\helloapp" reloadable="true"/>)
//加载docBase目录下,作为应用,如果是目录
deployDescriptors(configBase, configBase.list());
// Deploy webapps下的WARs
deployWARs(appBase, filteredAppPaths);
// Deploy webapps下的folders
deployDirectories(appBase, filteredAppPaths);
}
4.Descriptors(描述符)发布
4.1进入deployDescriptors()方法,在方法中将catalina/localhost/下的context文件,生成file对象,采用ExecutorService异步执行发布context任务,代码如下:
protected void deployDescriptors(File configBase, String[] files) {
ExecutorService es = host.getStartStopExecutor();
List<Future<?>> results = new ArrayList<>();
for (int i = 0; i < files.length; i++) {
File contextXml = new File(configBase, files[i]);
if (files[i].toLowerCase(Locale.ENGLISH).endsWith(".xml")) {
ContextName cn = new ContextName(files[i]);
if (isServiced(cn.getName()) || deploymentExists(cn.getName()))
continue;
//封装成DeployDescriptor对象,最后执行run方法
results.add(es.submit(new DeployDescriptor(this, cn, contextXml)));
}
}
for (Future<?> result : results) {
try {
//将会调用DeployDescriptor类的,run方法
result.get();
} catch (Exception e) {
log.error(sm.getString("hostConfig.deployDescriptor.threaded.error"), e);
}
}
}
4.2进入DeployDescriptor对象的run方法,在run方法里面会调用HostConfig的deployDescriptor方法,代码如下:
private static class DeployDescriptor implements Runnable {
private HostConfig config;
private ContextName cn;
private File descriptor;
public DeployDescriptor(HostConfig config, ContextName cn,
File descriptor) {
this.config = config;
this.cn = cn;
this.descriptor= descriptor;
}
@Override
public void run() {
config.deployDescriptor(cn, descriptor);
}
}
4.3进入config.deployDescriptor方法,在方法中,将会解析xml文件,生成standedContext、通过反射生成监听类ContextConfig对象,并把standedContext对象添加成StandHost对象的子对象。代码如下:
try (FileInputStream fis = new FileInputStream(contextXml)) {
synchronized (digesterLock) {
try {
//使用digester解析xml文件,生成standedContext对象。
context = (Context) digester.parse(fis);
} catch (Exception e) {
log.error(sm.getString("hostConfig.deployDescriptor.error",contextXml.getAbsolutePath()), e);
} finally {
if (context == null) {
context = new FailedContext();
}
digester.reset();
}
}
//使用反射生成生成standedContext对象的生命周期状态监听类ContextConfig
Class<?> clazz = Class.forName(host.getConfigClass());
LifecycleListener listener =
(LifecycleListener) clazz.newInstance();
//设置contexty的监听对象、配置文件地址、name、path及版本等
context.addLifecycleListener(listener);
context.setConfigFile(contextXml.toURI().toURL());
context.setName(cn.getName());
context.setPath(cn.getPath());
context.setWebappVersion(cn.getVersion());
//判断context指定的应用目录是否为空,如果不为空,判断它是目录还是war,如果是war是内部的还是外部的
if (context.getDocBase() != null) {
File docBase = new File(context.getDocBase());
if (!docBase.isAbsolute()) {
docBase = new File(host.getAppBaseFile(), context.getDocBase());
}
// 如果是外部的目录
if (!docBase.getCanonicalPath().startsWith(
host.getAppBaseFile().getAbsolutePath() + File.separator)) {
isExternal = true;
deployedApp.redeployResources.put(
contextXml.getAbsolutePath(),
Long.valueOf(contextXml.lastModified()));
deployedApp.redeployResources.put(docBase.getAbsolutePath(),
Long.valueOf(docBase.lastModified()));
if (docBase.getAbsolutePath().toLowerCase(Locale.ENGLISH).endsWith(".war")) {
isExternalWar = true;
}
} else {
log.warn(sm.getString("hostConfig.deployDescriptor.localDocBaseSpecified",
docBase));
// Ignore specified docBase
context.setDocBase(null);
}
}
host.addChild(context);
在host.addChild添加过程中在里面最终会调用上层容器ContainerBase的addChildInternal,在该方法中,会调standedContext的start方法。代码如下:
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);
}
if ((getState().isAvailable() ||
LifecycleState.STARTING_PREP.equals(getState())) &&
startChildren) {
try {
child.start();
} catch (LifecycleException e) {
log.error("ContainerBase.addChild: start: ", e);
throw new IllegalStateException
("ContainerBase.addChild: start: " + e);
}
}
fireContainerEvent(ADD_CHILD_EVENT, child);
}
在start方法中会进行standedContext生命周期函数的初始和start操作,在执行这些生命周期函数时,会触发监听事件,让ContextConfig执行初始化和start操作,里面包含war的解压及jar的加载、生成Wrappe,生成拦截器链等(具体代码分析在ContextConfig章节讲解)。
5.deployWARs发布
主要是对webapps目录下的war包进行发布。
5.1进入deployWARs()方法,对war包进行检查后判断,然后采用ExecutorService异步执行发布context任务,代码如下:
results.add(es.submit(new DeployWar(this, cn, war)));
}
}
for (Future<?> result : results) {
try {
result.get();
} catch (Exception e) {
log.error(sm.getString(
"hostConfig.deployWar.threaded.error"), e);
}
}
5.2进入DeployWar的run方法,在该方法中将会调用HostConfig.deployWAR方法,在该方法中根据不同情况生成StandContext对象:1).查找war包中或war包目录下是否存在/META-INF/context.xml,如果存在,就根据该配置文件生成standContext对象;2).不存在,反射org.apache.catalina.core.StandardContext生成对象;3).通过反射生成StandedContext的生命周期对象ContextConfig对象。
并把standedContext对象添加成StandHost对象的子对象,代码如下:
protected void deployWAR(ContextName cn, File war) {
JarFile jar = null;
InputStream istream = null;
FileOutputStream fos = null;
BufferedOutputStream ostream = null;
File xml = new File(host.getAppBaseFile(),
cn.getBaseName() + "/META-INF/context.xml");
boolean xmlInWar = false;
JarEntry entry = null;
try {
jar = new JarFile(war);
entry = jar.getJarEntry(Constants.ApplicationContextXml);
if (entry != null) {
xmlInWar = true;
}
} catch (IOException e) {
/* Ignore */
} finally {
/* Ignore */
}
Context context = null;
try {
if (deployXML && xml.exists() && !copyXML) {
synchronized (digesterLock) {
try {
context = (Context) digester.parse(xml);
} catch (Exception e) {
log.error(sm.getString(
"hostConfig.deployDescriptor.error",
war.getAbsolutePath()), e);
} finally {
/* Ignore */
}
}
context.setConfigFile(xml.toURI().toURL());
} else if (deployXML && xmlInWar) {
synchronized (digesterLock) {
try {
jar = new JarFile(war);
entry =
jar.getJarEntry(Constants.ApplicationContextXml);
istream = jar.getInputStream(entry);
context = (Context) digester.parse(istream);
} catch (Exception e) {
log.error(sm.getString(
"hostConfig.deployDescriptor.error",
war.getAbsolutePath()), e);
} finally {
/* Ignore */
}
} else if (!deployXML && xmlInWar) {
// 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(), Constants.ApplicationContextXml,
new File(host.getConfigBaseFile(), cn.getBaseName() + ".xml")));
} else {
context = (Context) Class.forName(contextClass).newInstance();
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString("hostConfig.deployWar.error",
war.getAbsolutePath()), t);
} finally {
if (context == null) {
context = new FailedContext();
}
}
boolean copyThisXml = false;
if (deployXML) {
if (host instanceof StandardHost) {
copyThisXml = ((StandardHost) host).isCopyXML();
}
// If Host is using default value Context can override it.
if (!copyThisXml && context instanceof StandardContext) {
copyThisXml = ((StandardContext) context).getCopyXML();
}
if (xmlInWar && copyThisXml) {
// Change location of XML file to config base
xml = new File(host.getConfigBaseFile(),
cn.getBaseName() + ".xml");
entry = null;
try {
jar = new JarFile(war);
entry =
jar.getJarEntry(Constants.ApplicationContextXml);
istream = jar.getInputStream(entry);
fos = new FileOutputStream(xml);
ostream = new BufferedOutputStream(fos, 1024);
byte buffer[] = new byte[1024];
while (true) {
int n = istream.read(buffer);
if (n < 0) {
break;
}
ostream.write(buffer, 0, n);
}
ostream.flush();
} catch (IOException e) {
/* Ignore */
} finally {
/* Ignore */
}
}
}
DeployedApplication deployedApp = new DeployedApplication(cn.getName(),
xml.exists() && deployXML && copyThisXml);
// Deploy the application in this WAR file
if(log.isInfoEnabled())
log.info(sm.getString("hostConfig.deployWar",
war.getAbsolutePath()));
try {
// Populate redeploy resources with the WAR file
deployedApp.redeployResources.put
(war.getAbsolutePath(), Long.valueOf(war.lastModified()));
if (deployXML && xml.exists() && copyThisXml) {
deployedApp.redeployResources.put(xml.getAbsolutePath(),
Long.valueOf(xml.lastModified()));
} else if (!copyThisXml ) {
// In case an XML file is added to the config base later
deployedApp.redeployResources.put(
(new File(host.getConfigBaseFile(),
cn.getBaseName() + ".xml")).getAbsolutePath(),
Long.valueOf(0));
}
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() + ".war");
host.addChild(context);
5.3.host.addChild(context)的操作和4.3的是一样的。
6.deployWdeployDirectories发布
6.1进入deployWdeployDirectories()方法,对路径进行是否为目录进行判断,然后采用ExecutorService异步执行发布context任务,代码如下:
protected void deployDirectories(File appBase, String[] files) {
if (files == null)
return;
ExecutorService es = host.getStartStopExecutor();
List<Future<?>> results = new ArrayList<>();
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]);
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) {
......
}
}
}
6.2进入DeployDirectory的run方法,将会读取子目录下的META-INF/context.xml,生成standedContext对象,通过反射生成StandedContext的生命周期对象ContextConfig对象并把standedContext对象添加成StandHost对象的子对象,代码如下:
protected void deployDirectory(ContextName cn, File dir) {
if( log.isInfoEnabled() )
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 = 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 {
if (context == null) {
context = new FailedContext();
}
digester.reset();
}
}
if (copyThisXml == false && context instanceof StandardContext) {
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);
} finally {
// Ignore
}
context.setConfigFile(xmlCopy.toURI().toURL());
} else {
context.setConfigFile(xml.toURI().toURL());
}
} else if (!deployXML && xml.exists()) {
log.error(sm.getString("hostConfig.deployDescriptor.blocked",
cn.getPath(), xml, xmlCopy));
context = new FailedContext();
} else {
context = (Context) Class.forName(contextClass).newInstance();
}
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);
6.3.host.addChild(context)的操作和4.3的是一样的。