2013-09-07
周末来啊,宅的日子又到了... 我们知道tomcat的基本是容器,通过容器来完成servlet容器的设计。从上一篇blog中看到了tomcat的结构框架图,图中容器嵌套,组件组合,tomcat如何设置他们的结构关系,如何将他们融合成能完成Servlet容器更能的东西,通过对tomcat6.0.x启动过程的了解,我们应该可以知道tomcat如何组织他们的。
要想知道tomcat的启动,首先得知道tomcat的启动类时哪一个,跟踪启动脚本会发现启动类:org.apache.catalina.startup.Bootstrap,启动之后可以启用eclipse的单步跟踪分析启动过程。
org.apache.catalina.startup.Bootstrap,用来启动管理tomcat的运行,例如启动、停止等,在Bootstrap的main函数中我们可以看到先判断有没有实例化过,然后调用init()函数,init()的代码
public void init() throws Exception {
// Set Catalina path
setCatalinaHome();
setCatalinaBase();
//构造所需的类加载器
initClassLoaders();
//设置当前线程的上下文类加载器为catalinaLoader
Thread.currentThread().setContextClassLoader(catalinaLoader);
//设置安全的类加载器为catalinaLoader
SecurityClassLoad.securityClassLoad(catalinaLoader);
// Load our startup class and call its process() method
if (log.isDebugEnabled())
log.debug("Loading startup class");
//使用catalinaLoader加载器加载类org.apache.catalina.startup.Catalina(提供Startup and Shutdown shell)
Class startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.newInstance();
//设置startupInstance的父加载器为共享类加载器的
// Set the shared extensions class loader
if (log.isDebugEnabled())
log.debug("Setting startup class properties");
String methodName = "setParentClassLoader";
Class paramTypes[] = new Class[1];
paramTypes[0] = Class.forName("java.lang.ClassLoader");
Object paramValues[] = new Object[1];
paramValues[0] = sharedLoader;
Method method = startupInstance.getClass().getMethod(methodName,
paramTypes);
method.invoke(startupInstance, paramValues);
catalinaDaemon = startupInstance;
}
从代码中可以看出来再init()函数中完成了Servlet容器类加载器的设置,加载在web app用应用到的common、server、shared三个不同的类加载器,设置类加载器是处于安全的考虑,例如一个Context不能直接访问另一个Context,同时也是为了共享jar,在initClassLoaders()方法中完成了3个不同类加载器对应目录下面的jar包,类文件的查找加载。还有一个很重要的就是实例化org.apache.catalina.startup.Catalina,并通过反射来设置org.apache.catalina.startup.Catalina的父类加载器。完成设置类加载和实例化org.apache.catalina.startup.Catalina后,根据传人参数来决定动作。在“start”参数选择启动servlet容器,会执行load(argments)方法和start()方法,load方法,负责调用org.apache.catalina.startup.Catalina.load()方法来加载配置文件,组织资源等,然后调用start方法来调用org.apache.catalina.startup.Catalina.start()方法,至此tomcat 完成启动。其实只要内容是在org.apache.catalina.startup.Catalina.load()中和org.apache.catalina.startup.Catalina.start()中。
org.apache.catalina.startup.Catalina 是真正负责管理tomcat的类,主要是解析server.xml,初始化组件,操作组件状态,设置一个程序退出的Hook。Catalina 继承自Embedded,而Embedded又继承自StandardService,说明Catalina 是一个Service(这个有意思,难道tomcat只能有一个service,不太可能吧)。
在org.apache.catalina.startup.Bootstrap方法load()中调用了Catalina的load()方法,Catalina的方法load(argments):
public void load() {
long t1 = System.nanoTime();
//catalina.home和catalina.base
initDirs();
// Before digester - it may be needed
//初始化命名
initNaming();
// Create and execute our Digester
//创建将xml转成对象的解析规则器
Digester digester = createStartDigester();
//搜寻配置文件
InputSource inputSource = null;
InputStream inputStream = null;
File file = null;
try {
//加载conf/server.xml配置文件
file = configFile();
inputStream = new FileInputStream(file);
inputSource = new InputSource("file://" + file.getAbsolutePath());
} catch (Exception e) {
;
}
if (inputStream == null) {
try {
inputStream = getClass().getClassLoader().getResourceAsStream(
getConfigFile());
inputSource = new InputSource(getClass().getClassLoader()
.getResource(getConfigFile()).toString());
} catch (Exception e) {
;
}
}
// This should be included in catalina.jar
// Alternative: don't bother with xml, just create it manually.
if (inputStream == null) {
try {
inputStream = getClass().getClassLoader().getResourceAsStream(
"server-embed.xml");
inputSource = new InputSource(getClass().getClassLoader()
.getResource("server-embed.xml").toString());
} catch (Exception e) {
;
}
}
if ((inputStream == null) && (file != null)) {
log.warn("Can't load server.xml from " + file.getAbsolutePath());
if (file.exists() && !file.canRead()) {
log.warn("Permissions incorrect, read permission is not allowed on the file.");
}
return;
}
try {
inputSource.setByteStream(inputStream);
digester.push(this);
digester.parse(inputSource);
inputStream.close();
} catch (Exception e) {
log.warn("Catalina.start using " + getConfigFile() + ": ", e);
return;
}
// Stream redirection
//设置输出流设备
initStreams();
// Start the new server
//启动新server
if (getServer() instanceof Lifecycle) {
try {
getServer().initialize();
} catch (LifecycleException e) {
if (Boolean
.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"))
throw new java.lang.Error(e);
else
log.error("Catalina.start", e);
}
}
long t2 = System.nanoTime();
if (log.isInfoEnabled())
log.info("Initialization processed in " + ((t2 - t1) / 1000000)
+ " ms");
}
从方法中可以知道,主要完成以下几个操作:
- 配置Servlet容器目录环境
- 设置Naming(JNDI)
- 使用Digester库来解析conf/server.xml,并设置容器组件层次结构关系
- 初始化Server
其中重要点在Digester库来解析conf/server.xml和初始化Server上面。 Digester digester = createStartDigester();创建一个对象解析server.xml。
createStartDigester():
protected Digester createStartDigester() {
long t1 = System.currentTimeMillis();
// Initialize the digester
Digester digester = new Digester();
digester.setValidating(false);
digester.setRulesValidation(true);
HashMap<Class, List<String>> fakeAttributes = new HashMap<Class, List<String>>();
ArrayList<String> attrs = new ArrayList<String>();
attrs.add("className");
fakeAttributes.put(Object.class, attrs);
digester.setFakeAttributes(fakeAttributes);
//设置ClassLoader确保在同一个jvm的命名空间中,可以相互访问
digester.setClassLoader(StandardServer.class.getClassLoader());
// Configure the actions we will be using
//转化对象
digester.addObjectCreate("Server","org.apache.catalina.core.StandardServer", "className");
digester.addSetProperties("Server");
digester.addSetNext("Server", "setServer", "org.apache.catalina.Server");
digester.addObjectCreate("Server/GlobalNamingResources",
"org.apache.catalina.deploy.NamingResources");
digester.addSetProperties("Server/GlobalNamingResources");
digester.addSetNext("Server/GlobalNamingResources",
"setGlobalNamingResources",
"org.apache.catalina.deploy.NamingResources");
digester.addObjectCreate("Server/Listener", null, // MUST be specified
// in the element
"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");
digester.addObjectCreate("Server/Service/Listener", null, // MUST be
// specified
// in the
// element
"className");
digester.addSetProperties("Server/Service/Listener");
digester.addSetNext("Server/Service/Listener", "addLifecycleListener",
"org.apache.catalina.LifecycleListener");
// Executor
digester.addObjectCreate("Server/Service/Executor",
"org.apache.catalina.core.StandardThreadExecutor", "className");
digester.addSetProperties("Server/Service/Executor");
digester.addSetNext("Server/Service/Executor", "addExecutor",
"org.apache.catalina.Executor");
digester.addRule("Server/Service/Connector", new ConnectorCreateRule());
digester.addRule("Server/Service/Connector", new SetAllPropertiesRule(
new String[] { "executor" }));
digester.addSetNext("Server/Service/Connector", "addConnector",
"org.apache.catalina.connector.Connector");
digester.addObjectCreate("Server/Service/Connector/Listener", null, // MUST
// be
// specified
// in
// the
// element
"className");
digester.addSetProperties("Server/Service/Connector/Listener");
digester.addSetNext("Server/Service/Connector/Listener",
"addLifecycleListener", "org.apache.catalina.LifecycleListener");
// Add RuleSets for nested elements
digester.addRuleSet(new NamingRuleSet("Server/GlobalNamingResources/"));
digester.addRuleSet(new EngineRuleSet("Server/Service/"));
digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"));
digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Host/"));
digester.addRuleSet(ClusterRuleSetFactory
.getClusterRuleSet("Server/Service/Engine/Host/Cluster/"));
digester.addRuleSet(new NamingRuleSet(
"Server/Service/Engine/Host/Context/"));
// When the 'engine' is found, set the parentClassLoader.
digester.addRule("Server/Service/Engine", new SetParentClassLoaderRule(
parentClassLoader));
digester.addRuleSet(ClusterRuleSetFactory
.getClusterRuleSet("Server/Service/Engine/Cluster/"));
long t2 = System.currentTimeMillis();
if (log.isDebugEnabled())
log.debug("Digester for server.xml created " + (t2 - t1));
return (digester);
}
方法中设置了tomcat容器组件层次之间的关系,Server解析,Service解析,Engine解析,Host解析等,Digester 是一个规则解析引擎,根据设定的规则来解析server.xml文件,同时实例化对象,调用方法设置层次关系,很强大,但是这也增加了阅读的难度。
在设置规则完成后,调用下面代码执行解析动作
digester.push(this);//压入本类实例对象,提供解析生成容器组件组合宿主
digester.parse(inputSource);//解析server.xml
解析过程是很复杂的,可以设置断点跟踪查看。
在解析完成之后,tomcat调用getServer().initialize();对server初始化,Server的标准实现StandardServer,StandardServer方法initialize()负责对Server初始化,跟踪代码发现主要是对注册在Server下面的service数组进行初始化。
疑问:竟然Server下面有service数组,那么Catalina这个也实现了service接口的类在tomcat中充当怎样的角色?简单的控制角色吗?那干啥要实现service接口?
可以通过验证catalina是否是Server中service数组的一员,直接打印对象对吧输出结果,在下一篇中验证。
初始化完成后,返回load方法,在返回到Bootstrap中,然后调用start方法来启动容器,在start中调用Catalina中的start方法启动容器。
Catalina的start()方法:
public void start() {
if (getServer() == null) {
load();
}
if (getServer() == null) {
log.fatal("Cannot start server. Server instance is not configured.");
return;
}
long t1 = System.nanoTime();
// Start the new server
if (getServer() instanceof Lifecycle) {
try {
((Lifecycle) getServer()).start();
} catch (LifecycleException e) {
log.error("Catalina.start: ", e);
}
}
long t2 = System.nanoTime();
if (log.isInfoEnabled())
log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");
try {
// Register shutdown hook
//添加hook,在程序退出时处理一些清理工作
if (useShutdownHook) {
if (shutdownHook == null) {
shutdownHook = new CatalinaShutdownHook();
}
Runtime.getRuntime().addShutdownHook(shutdownHook);
// If JULI is being used, disable JULI's shutdown hook since
// shutdown hooks run in parallel and log messages may be lost
// if JULI's hook completes before the CatalinaShutdownHook()
LogManager logManager = LogManager.getLogManager();
if (logManager instanceof ClassLoaderLogManager) {
((ClassLoaderLogManager) logManager)
.setUseShutdownHook(false);
}
}
} catch (Throwable t) {
// This will fail on JDK 1.2. Ignoring, as Tomcat can run
// fine without the shutdown hook.
}
if (await) {
await();//等待线程结束(在catalina单线程中,等待结束)
stop();
}
}
方法中先判断Server是否存在实例,如果不存在,掉load()方法,加载server.xml配置文件,解析创建对象,然后调用start()方法启动容器,在server的start()方法中,跟initialize()方法也有,也是调用service数组中service的initialize()方法。在启动容器后,添加一个Runtime.getRuntime().addShutdownHook(shutdownHook);监控程序异常退出时清理资源。
tomcat的启动有很多信息,service的initialize()方法和验证疑问以及service的start()方法会在接下来的blog中跟踪...
tomcat启动,首先设置类加载器,加载jar包和类,设置环境目录,使用Digester库解析server.xml生成容器组件层次关系,初始化Server对象,启动Server,完成启动过程,在这里还没有讲到ServerSocket部分,是Service中的内容,还有server.xml的文件内容,还有Server的角色扮演。
欢迎吐槽...
慢慢走下去,路就会清晰,再看一眼,在清晰一点