Catalina启动分析
1. #load()方法:
public void load() {
if (loaded) {
return;
}
loaded = true;
long t1 = System.nanoTime();
//1. 读取环境变量中java.io.tmpdir,没有打印错误日志
initDirs();
// Before digester - it may be needed
initNaming();
// Set configuration source
ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(Bootstrap.getCatalinaBaseFile(), getConfigFile()));
File file = configFile();
// 2. 解析server.xml文件,并设置Catalina,Server对应属性值
Digester digester = createStartDigester();
try (ConfigurationSource.Resource resource = ConfigFileLoader.getSource().getServerXml()) {
InputStream inputStream = resource.getInputStream();
InputSource inputSource = new InputSource(resource.getURI().toURL().toString());
inputSource.setByteStream(inputStream);
digester.push(this);
digester.parse(inputSource);
} catch (Exception e) {
log.warn(sm.getString("catalina.configFail", file.getAbsolutePath()), e);
if (file.exists() && !file.canRead()) {
log.warn(sm.getString("catalina.incorrectPermissions"));
}
return;
}
//3. 设置Server
getServer().setCatalina(this);
getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());
//4. 设置输出
initStreams();
//5. Server初始化
try {
getServer().init();
} catch (LifecycleException e) {
if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
throw new java.lang.Error(e);
} else {
log.error(sm.getString("catalina.initError"), e);
}
}
long t2 = System.nanoTime();
if(log.isInfoEnabled()) {
log.info(sm.getString("catalina.init", Long.valueOf((t2 - t1) / 1000000)));
}
}
主要流程如下:
- 解析conf/server.xml并创建Server对象,赋值Catalina,Server的相关属性。
- 执行Server#init()初始化。
2. start方法:
public void start() {
//1. Server为空重新load一次
if (getServer() == null) {
load();
}
//2. 如果Server还为空,直接返回
if (getServer() == null) {
log.fatal(sm.getString("catalina.noServer"));
return;
}
long t1 = System.nanoTime();
//3. 执行Server#start()
try {
getServer().start();
} catch (LifecycleException e) {
log.fatal(sm.getString("catalina.serverStartFail"), e);
try {
getServer().destroy();
} catch (LifecycleException e1) {
log.debug("destroy() failed for failed Server ", e1);
}
return;
}
long t2 = System.nanoTime();
if(log.isInfoEnabled()) {
log.info(sm.getString("catalina.startup", Long.valueOf((t2 - t1) / 1000000)));
}
//4. 注册shutdown钩子,用来执行Catalina#stop()
if (useShutdownHook) {
if (shutdownHook == null) {
shutdownHook = new CatalinaShutdownHook();
}
Runtime.getRuntime().addShutdownHook(shutdownHook);
LogManager logManager = LogManager.getLogManager();
if (logManager instanceof ClassLoaderLogManager) {
((ClassLoaderLogManager) logManager).setUseShutdownHook(
false);
}
}
//5. #init()之后await为true,启动一个Socket等待接受shutdown命令,用来停止Tomcat
if (await) {
await();
stop();
}
}
主要流程如下:
- 判断Server是否为空,为空重新执行一次#load()。
- 执行Server#start(),启动Server。
- 注册shutdown钩子,用来执行Catalina#stop()。
- 启动一个Socket等待接受shutdown命令,用来停止Tomcat。
3. #createStartDigester()方法:
protected Digester createStartDigester() {
long t1=System.currentTimeMillis();
// Initialize the digester
Digester digester = new Digester();
// 设置为false表示解析xml时不需要进行DTD的规则校验
digester.setValidating(false);
// 是否进行节点设置规则校验,如果xml中相应节点没有设置解析规则会在控制台显示提示信息
digester.setRulesValidation(true);
Map<Class<?>, List<String>> fakeAttributes = new HashMap<>();
// Ignore className on all elements
List<String> objectAttrs = new ArrayList<>();
objectAttrs.add("className");
fakeAttributes.put(Object.class, objectAttrs);
// Ignore attribute added by Eclipse for its internal tracking
List<String> contextAttrs = new ArrayList<>();
contextAttrs.add("source");
fakeAttributes.put(StandardContext.class, contextAttrs);
// Ignore Connector attribute used internally but set on Server
List<String> connectorAttrs = new ArrayList<>();
connectorAttrs.add("portOffset");
fakeAttributes.put(Connector.class, connectorAttrs);
digester.setFakeAttributes(fakeAttributes);
digester.setUseContextClassLoader(true);
// addObjectCreate方法的意思是碰到xml文件中的Server节点则创建一个StandardServer对象
digester.addObjectCreate("Server",
"org.apache.catalina.core.StandardServer",
"className");
// 根据Server节点中的属性信息调用相应属性的setter方法,以上面的xml文件为例则会调用setPort、setShutdown方法,入参分别是8005、SHUTDOWN
digester.addSetProperties("Server");
// 将Server节点对应的对象作为入参调用栈顶对象的setServer方法,这里的栈顶对象即下面的digester.push方法所设置的当前类的对象this,就是说调用StandardServer类的setServer方法
digester.addSetNext("Server",
"setServer",
"org.apache.catalina.Server");
digester.addObjectCreate("Server/GlobalNamingResources",
"org.apache.catalina.deploy.NamingResourcesImpl");
digester.addSetProperties("Server/GlobalNamingResources");
digester.addSetNext("Server/GlobalNamingResources",
"setGlobalNamingResources",
"org.apache.catalina.deploy.NamingResourcesImpl");
// 碰到xml的Server节点下的Listener节点时取className属性的值作为实例化类实例化一个对象
digester.addRule("Server/Listener",
new ListenerCreateRule(null, "className"));
digester.addSetProperties("Server/Listener");
digester.addSetNext("Server/Listener",
"addLifecycleListener",
"org.apache.catalina.LifecycleListener");
digester.addObjectCreate("Server/Service",
"org.apache.catalina.core.StandardService",
"className");
digester.addSetProperties("Server/Service");
digester.addSetNext("Server/Service",
"addService",
"org.apache.catalina.Service");
//省略部分...
return digester;
}
一般来说 Java 里解析 xml 文件有两种方式:一种是 Dom4J 之类将文件全部读取到内存中,在内存里构造一棵 Dom 树的方式来解析。一种是 SAX 的读取文件流,在流中碰到相应的xml节点触发相应的节点事件回调相应方法,基于事件的解析方式,优点是不需要先将文件全部读取到内存中。
Digester 本身是采用 SAX 的解析方式,在其上提供了一层包装,对于使用者更简便友好罢了。最早是在 struts 1 里面用的,后来独立出来成为 apache 的 Commons 下面的一个单独的子项目。Tomcat 里又把它又封装了一层,这里不做多介绍。
这里Catalina的启动分析结束,接下来看Server的启动。
总结
通过上面的介绍,我们了解到Catalina类主要完成了server.xml的解析以及相应属性的赋值,然后调用最顶层容器Server的#init()和#start()方法开始了整个Tomcat的加载之旅,下面一个一个来分析。
网友评论