前面的文章分析了StandardEngine,这里来分析一下由Engine对象来管理的另外一种对象吧:Host。。
一般情况下,在tomcat中都是默认使用StandardHost类型。。。这里先来看看在catalina是如何配置创建StandardHost对象的吧:
//创建host对象
digester.addObjectCreate(prefix + "Host",
"org.apache.catalina.core.StandardHost", //创建host对象的配置
"className");
digester.addSetProperties(prefix + "Host");
digester.addRule(prefix + "Host",
new CopyParentClassLoaderRule()); //会将host的parentClassloader设置为engine的,engine被设置为sharedloader
digester.addRule(prefix + "Host", //为host设置配置的监听
new LifecycleListenerRule
("org.apache.catalina.startup.HostConfig", //这个算是比较重要的吧,在里面会具体的创建context啥的
"hostConfigClass"));
digester.addSetNext(prefix + "Host",
"addChild",
"org.apache.catalina.Container"); // 在engine上面调用addChild方法,用于添加当前的host到engine上面去这部分具体的涉及到Host对象的创建,第一个是具体的创建要用的host对象,也就是StandardHost对象,接着是指定为host设置的parentclassLoader为engine的parentClassLoader,在前面的文章中,我们知道engine的parentClassLoader会被设置为sharedLoader,所以这里host对象的parentClassLoader也会被设置为sharedLoader。。
然后还有比较重要的配置项吧,为StandardHost对象添加lifecycle的监听器,为HostConfig类型的对象。。它是比较重要的吧,因为它会具体的负责对context的创建,启动啥的。。这个一会再来说吧。。
然后就最后调用engine的addChild方法将当前host对象添加到engine上面去了。。。
好啦,到这里怎样创建host对象应该算是知道了吧。。。那么接下来来看看host接口是如何定义的吧:
public interface Host extends Container {
//host的一些事件的定义,添加别名,移除别名啥的
public static final String ADD_ALIAS_EVENT = "addAlias";
public static final String REMOVE_ALIAS_EVENT = "removeAlias";
public String getXmlBase(); //当前host对象的配置文件的路径,这个文件不一定存在吧 /conf/enginename/hostname/
public void setXmlBase(String xmlBase);
public File getConfigBaseFile(); //当前host的配置xml文件
public String getAppBase(); //当前host的app在什么地方
public File getAppBaseFile(); //获取app的放的目录的文件引用
public void setAppBase(String appBase); //设置app存放的路径
public boolean getAutoDeploy(); //是否自动部署
public void setAutoDeploy(boolean autoDeploy); //是否自动部署
public String getConfigClass(); //用于监听context的listener的类型
public void setConfigClass(String configClass);
public boolean getDeployOnStartup(); //启动的时候部署?
public void setDeployOnStartup(boolean deployOnStartup);
public String getDeployIgnore();
public Pattern getDeployIgnorePattern(); //context名字匹配用的正则表达式
public void setDeployIgnore(String deployIgnore);
public ExecutorService getStartStopExecutor(); //用于启动和停止子container(也就是context)的executor
public boolean getCreateDirs(); //如果是ture的话,那么会尝试为应用程序和host的配置创建文件夹
public void setCreateDirs(boolean createDirs);
public boolean getUndeployOldVersions(); //是否自动卸载程序的老版本
public void setUndeployOldVersions(boolean undeployOldVersions);
public void addAlias(String alias); //为当前host添加别名
public String[] findAliases(); //获取当前host的所有别名
public void removeAlias(String alias); //移除一个别名
}
接口定义稍微长一些吧,不过也还挺简单的,主要是一些配置,别名什么的管理,这个具体看上面的注释应该能比较的清楚吧。
好啦,接下来来看看StandardHost是怎么实现的吧,先来看看简单的继承体系:
这个应该算是很简单的吧,首先也是一个容器。。。然后实现了host接口。。。这里来看看它的构造函数吧:
public StandardHost() {
super();
pipeline.setBasic(new StandardHostValve()); //设置basic
}没做什么事情吧,无非是在pipeline上面添加了一个basic的valve对象。。。接下来再来看看一些重要的属性的定义吧:
private String[] aliases = new String[0]; //当前host对象的别名的数组
private final Object aliasesLock = new Object(); //锁
private String appBase = "webapps"; //默认的app的路径是tomcat根路径下的webapps文件夹
private volatile File appBaseFile = null; //引用这个文件夹
private String xmlBase = null; //xml配置文件所在的目录
private volatile File hostConfigBase = null; //host的默认配置路径 conf/ + enginename + / + hostname
private boolean autoDeploy = true; //默认是自动部署的
private String configClass =
"org.apache.catalina.startup.ContextConfig"; // 默认的config类型,是个listener,通过监听当前host的状态来部署context啥的
private String contextClass =
"org.apache.catalina.core.StandardContext"; //默认用到的context对象的类型
private boolean deployOnStartup = true; //默认在启动的时候部署应用
private boolean deployXML = !Globals.IS_SECURITY_ENABLED;
private boolean copyXML = false; //
private String errorReportValveClass =
"org.apache.catalina.valves.ErrorReportValve"; //用于保存的valve默认的类型
private boolean unpackWARs = true; //默认要解压war包
private String workDir = null; //app的work路径
private boolean createDirs = true; //默认在启动的时候创建文件夹
private final Map<ClassLoader, String> childClassLoaders = //跟踪每个app的classLoaer,用于定位内存泄露
new WeakHashMap<>();
private Pattern deployIgnore = null;
private boolean undeployOldVersions = false; //默认不卸载老版本
嗯,具体这些属性的用处在注释上应该比较的清楚了。。其实host对象本身无非就是对这些属性的管理。。自己并没有太多的要做的事情。。方法也基本上都是对这些属性的设置什么的。。。这里就不具体的来分析这些方法了,有兴趣自己看看就是了。。挺简单的。。。
那么这里来看看host对象的pipeline上的basic的valve干了的invoke做了什么事情吧。。我们在前面知道。。在engine的basic的vavle上将会调用请求所属的host的pipeline来处理请求。。
//其实这里主要是是调用当前请求的context的pipeline来处理
public final void invoke(Request request, Response response)
throws IOException, ServletException {
// Select the Context to be used for this Request
Context context = request.getContext(); //获取当前请求所属的context
if (context == null) { //如果没法找到context,那么可以直接返回错误了
response.sendError
(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
sm.getString("standardHost.noContext"));
return;
}
context.bind(Globals.IS_SECURITY_ENABLED, MY_CLASSLOADER);
if (request.isAsyncSupported()) { //设置当前请求是否支持异步
request.setAsyncSupported(context.getPipeline().isAsyncSupported());
}
// Don‘t fire listeners during async processing
// If a request init listener throws an exception, the request is
// aborted
boolean asyncAtStart = request.isAsync();
// An async error page may dispatch to another resource. This flag helps
// ensure an infinite error handling loop is not entered
boolean errorAtStart = response.isError();
if (asyncAtStart || context.fireRequestInitEvent(request)) { //用于让ServletRequestListener,表示有请求进来了
// Ask this Context to process this request
try {
context.getPipeline().getFirst().invoke(request, response); //调用所属的context来处理了
} catch (Throwable t) { //如果有异常的话,那么需要返回错误
ExceptionUtils.handleThrowable(t);
if (errorAtStart) {
container.getLogger().error("Exception Processing " +
request.getRequestURI(), t);
} else {
request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, t);
throwable(request, response, t);
}
}
// If the request was async at the start and an error occurred then
// the async error handling will kick-in and that will fire the
// request destroyed event *after* the error handling has taken
// place
if (!(request.isAsync() || (asyncAtStart &&
request.getAttribute(
RequestDispatcher.ERROR_EXCEPTION) != null))) {
// Protect against NPEs if context was destroyed during a
// long running request.
if (context.getState().isAvailable()) {
if (!errorAtStart) {
// Error page processing
response.setSuspended(false);
Throwable t = (Throwable) request.getAttribute(
RequestDispatcher.ERROR_EXCEPTION);
if (t != null) {
throwable(request, response, t);
} else {
status(request, response);
}
}
context.fireRequestDestroyEvent(request);
}
}
}
// Access a session (if present) to update last accessed time, based on a
// strict interpretation of the specification
if (ACCESS_SESSION) {
request.getSession(false);
}
context.unbind(Globals.IS_SECURITY_ENABLED, MY_CLASSLOADER);
}
嗯,代码虽然挺长的,其实主要也就是要调用当前请求所属的context的pipeline来处理这个请求的。。。
嗯,这里其实StandardHost部分的内容也就差不多了吧。。接下来来看看前面提到的HostConfig对象。。它将会用于监听host对象的生命周期事件,例如启动,停止是什么的。。。
继承体系还是蛮简单的吧,实现了LifecycleListener接口,那么表示当前对象可以响应lifecycle对象的生命周期事件,例如启动停止。。。接下来来看看它的一些重要的属性以及构造函数吧:
protected String contextClass = "org.apache.catalina.core.StandardContext"; //用到的context的类型的名字
protected Host host = null; //监听的host对象
protected ObjectName oname = null; //在jmx上面注册的名字
protected static final StringManager sm =
StringManager.getManager(Constants.Package);
protected boolean deployXML = false; //是否要处理app的context的xml配置文件
protected boolean copyXML = false; //是否要将xml配置文件移动到/conf/enginename/hostname/下面
protected boolean unpackWARs = false; //是否要解压war
protected final Map<String, DeployedApplication> deployed = //所有已经部署的应用,key是context的名字
new ConcurrentHashMap<>();
protected final ArrayList<String> serviced = new ArrayList<>();
protected Digester digester = createDigester(contextClass); //用于即系xml文件的
private final Object digesterLock = new Object();
protected final Set<String> invalidWars = new HashSet<>(); //忽略的war包
public String getContextClass() { //获取用到的context的类型
return (this.contextClass);
}这里属性有些还是非常很总要的,例如deployed,用于代表每一个已经部署的web应用程序。。。接下来来看看它的lifecycleEvent方法的定义吧,也就是它是如何响应事件的:
//相应监听的host的生命周期的事件
public void lifecycleEvent(LifecycleEvent event) {
// Identify the host we are associated with
try {
host = (Host) event.getLifecycle(); //当前所监听的lifecycle对象,这里监听的是host对象
if (host instanceof StandardHost) { //根据host的信息,来设置一些配置
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(); //停止
}
}这里首先是在host对象上面来拿一些配置的信息,然后根据事件的类型进行相应的处理。。这里就来看看对于启动的事件是怎么相应的吧:
//当监听的host启动的时候会执行这个方法,其实主要是context的部署
public void start() {
if (log.isDebugEnabled())
log.debug(sm.getString("hostConfig.start"));
try {
ObjectName hostON = host.getObjectName(); //获取host的jmx上面注册的名字
oname = new ObjectName
(hostON.getDomain() + ":type=Deployer,host=" + host.getName()); //根据host的名字生成当前对象的名字
Registry.getRegistry(null, null).registerComponent
(this, oname, this.getClass().getName()); //在jmx上面注册当前对象
} catch (Exception e) {
log.error(sm.getString("hostConfig.jmx.register", oname), e);
}
if (host.getCreateDirs()) { //如果要创建文件夹,这个是用于存放host的配置文件。/conf/enginename/hostname,还有就是app的目录
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()) { //如果app的目录不是文件夹,那么错误了
log.error(sm.getString("hostConfig.appBase", host.getName(),
host.getAppBaseFile().getPath()));
host.setDeployOnStartup(false);
host.setAutoDeploy(false);
}
if (host.getDeployOnStartup()) //一般都是
deployApps(); //开始部署应用
}这里要做的其实主要是在jmx上的注册,然后对配置文件目录的处理,接着再是调用deployApps方法来具体的部署应用web应用程序。。。
//app的部署
protected void deployApps() {
File appBase = host.getAppBaseFile(); //获取app的路径目录的文件夹引用 /webapps
File configBase = host.getConfigBaseFile(); //获取host的配置文件的路径 /conf/enginename/hostname/
String[] filteredAppPaths = filterAppPaths(appBase.list()); //这里过滤一下app的路径,用host里的正则表达式来判断文件夹的名字是否符合规定
// Deploy XML descriptors from configBase
deployDescriptors(configBase, configBase.list()); //先处理host的配置
// Deploy WARs
deployWARs(appBase, filteredAppPaths); //部署app文件夹的war包
// Deploy expanded folders
deployDirectories(appBase, filteredAppPaths); //部署app文件夹里面的文件
}
首先获取了host的配置文件的目录以及存放app的文件夹的目录,然后对app的文件的名字进行一些过滤,毕竟context的名字不能随便取的嘛。。接着就是开始部署应用程序了,这里分为两种吧,一种是不是war包类型的,另外一种就是部署文件夹类型的。。。这里就来看看部署文件夹类型的吧,war包无非就是多了一层解压而已。。。
// 部署应用,文件夹类型的,前面是所有app所在的目录的引用,后面是要部署的文件夹的名字
protected void deployDirectories(File appBase, String[] files) {
if (files == null)
return;
ExecutorService es = host.getStartStopExecutor(); //后去host对象用于启动停止子container的executor
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]); //创建文件的file引用
if (dir.isDirectory()) { //这里需要是一个文件夹
ContextName cn = new ContextName(files[i], false); //根据文件夹的名字来设置context的名字
if (isServiced(cn.getName()) || deploymentExists(cn.getName())) //是否有同名的
continue;
results.add(es.submit(new DeployDirectory(this, cn, dir))); //添加一个deploy文件夹的任务,派遣到executor里面进行 其实是config.deployDirectory
}
}
for (Future<?> result : results) {
try {
result.get();
} catch (Exception e) {
log.error(sm.getString(
"hostConfig.deployDir.threaded.error"), e);
}
}
}这里是遍历当前app所在文件夹,然后根据文件夹的名字来创建context的名字,然后在host的executor上面提交部署的任务,具体的执行如下:
//第一个参数是context的名字,第二个参数是文件夹的引用
protected void deployDirectory(ContextName cn, File dir) {
// Deploy the application in this directory
if( log.isInfoEnabled() ) //打印正在部署啥
log.info(sm.getString("hostConfig.deployDir",
dir.getAbsolutePath()));
Context context = null;
//有的web应用可能有定义context的配置
File xml = new File(dir, Constants.ApplicationContextXml); //"META-INF/context.xml"; 当前context的配置文件,这个也不一一定有
File xmlCopy =
new File(host.getConfigBaseFile(), cn.getBaseName() + ".xml"); //获取host的配置文件夹里面当前context的配置,这个不一定有
DeployedApplication deployedApp; //用于引用已经部署的app
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) {
// 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 org.apache.catalina.core.StandardContext
context = (Context) Class.forName(contextClass).newInstance();
}
Class<?> clazz = Class.forName(host.getConfigClass()); //为context创建config对象 org.apache.catalina.startup.ContextConfig
LifecycleListener listener =
(LifecycleListener) clazz.newInstance(); // 创建listener的对象,然后添加到context上面去
context.addLifecycleListener(listener);
context.setName(cn.getName()); //设置当前context的名字
context.setPath(cn.getPath()); //应用所在的路径
context.setWebappVersion(cn.getVersion()); //当前版本
context.setDocBase(cn.getBaseName());
host.addChild(context); //在host添加context,在host里面,会将context的name与context对应起来,而且这里还会进行context的启动
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString("hostConfig.deployDir.error",
dir.getAbsolutePath()), t);
} finally {
deployedApp = new DeployedApplication(cn.getName(), //创建DeployedApplication对象,表设一个部署的应用
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()) { //如果有context的配置文件
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); //添加web应用程序资源的监控
// 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); //表示这个app已经部署了,key是当前context的名字,后面是
}
代码也还算是比较长的吧,其实主要要做得到事情就是处理当前web应用程序的context的配置,然后创建context的对象,然后调用host对象的addChild方法将当前创建的context加入到host里面去。。。
好啦,到这里就算差不多了。。干货不多吧,主要就集中在context的创建和部署上了。。。
Tomcat源码阅读之StandardHost与HostConfig的分析,布布扣,bubuko.com
Tomcat源码阅读之StandardHost与HostConfig的分析
原文:http://blog.csdn.net/fjslovejhl/article/details/21107331