本篇结构:
- 前言
- 总览一下main方法
- init
- setAwait
- load
- start
一、前言
上一篇中介绍了startup.bat和catalina.bat脚本。了解到日常双击startup.bat启动tomcat,其实是来到catalina.bat脚本中,由catalina.bat脚本去执行org.apache.catalina.startup.Bootstrap这个类中的main方法。
这里就来看看Bootstrap类的main方法做了些什么。
二、总览一下main方法
public static void main(String args[]) {
//上部分
if (daemon == null) {
// Don't set daemon until init() has completed
Bootstrap bootstrap = new Bootstrap();
try {
bootstrap.init();
} catch (Throwable t) {
handleThrowable(t);
t.printStackTrace();
return;
}
daemon = bootstrap;
} else {
// When running as a service the call to stop will be on a new
// thread so make sure the correct class loader is used to prevent
// a range of class not found exceptions.
Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
}
//下部分
try {
String command = "start";
if (args.length > 0) {
command = args[args.length - 1];
}
if (command.equals("startd")) {
args[args.length - 1] = "start";
daemon.load(args);
daemon.start();
} else if (command.equals("stopd")) {
args[args.length - 1] = "stop";
daemon.stop();
} else if (command.equals("start")) {
daemon.setAwait(true);
daemon.load(args);
daemon.start();
} else if (command.equals("stop")) {
daemon.stopServer(args);
} else if (command.equals("configtest")) {
daemon.load(args);
if (null==daemon.getServer()) {
System.exit(1);
}
System.exit(0);
} else {
log.warn("Bootstrap: command \"" + command + "\" does not exist.");
}
} catch (Throwable t) {
// Unwrap the Exception for clearer error reporting
if (t instanceof InvocationTargetException &&
t.getCause() != null) {
t = t.getCause();
}
handleThrowable(t);
t.printStackTrace();
System.exit(1);
}
}
可以看出main方法大致可以分为两部分。
上部分是实例化一个Bootstrap对象,并调用init方法,然后赋值给daemon变量,当然如果daemon已经不是空了,说明已经初始化过了,就将daemon.catalinaLoader直接设置到当前线程(daemon.catalinaLoader是用来加载tomcat内部服务器所需类的类加载器)。
下部分是根据传递进来的参数决定走哪一步,当双击startup.bat时,传进来的是start,所以会来到这段:
else if (command.equals("start")) {
daemon.setAwait(true);
daemon.load(args);
daemon.start();
}
这里主要是调用三个方法,setAwait,load和start。
所以对于Bootstrap重要关注的就是init,setAwait,load和start这四个方法。
三、init
public void init() throws Exception {
initClassLoaders();
Thread.currentThread().setContextClassLoader(catalinaLoader);
SecurityClassLoad.securityClassLoad(catalinaLoader);
// Load our startup class and call its process() method
if (log.isDebugEnabled())
log.debug("Loading startup class");
Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.getConstructor().newInstance();
// 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方法调用initClassLoaders初始化类加载器,然后将初始化好的catalinaLoader设置到当前线程,接着通过反射调用org.apache.catalina.startup.Catalina类的setParentClassLoader,将sharedLoader传入。
3.1、首先看initClassLoaders方法:
private void initClassLoaders() {
try {
commonLoader = createClassLoader("common", null);
if( commonLoader == null ) {
// no config file, default to this loader - we might be in a 'single' env.
commonLoader=this.getClass().getClassLoader();
}
catalinaLoader = createClassLoader("server", commonLoader);
sharedLoader = createClassLoader("shared", commonLoader);
} catch (Throwable t) {
handleThrowable(t);
log.error("Class loader creation threw exception", t);
System.exit(1);
}
}
在类加载器篇中有提到,这里初始化三个类加载器,分别是commonLoader,catalinaLoader,sharedLoader并建立他们之间的关系,catalinaLoader和sharedLoader的parent是commonLoader。
这三个类加载器都是通过createClassLoader建立的:
private ClassLoader createClassLoader(String name, ClassLoader parent)
throws Exception {
String value = CatalinaProperties.getProperty(name + ".loader");
if ((value == null) || (value.equals("")))
return parent;
value = replace(value);
List<Repository> repositories = new ArrayList<>();
String[] repositoryPaths = getPaths(value);
for (String repository : repositoryPaths) {
// Check for a JAR URL repository
try {
@SuppressWarnings("unused")
URL url = new URL(repository);
repositories.add(
new Repository(repository, RepositoryType.URL));
continue;
} catch (MalformedURLException e) {
// Ignore
}
// Local repository
if (repository.endsWith("*.jar")) {
repository = repository.substring
(0, repository.length() - "*.jar".length());
repositories.add(
new Repository(repository, RepositoryType.GLOB));
} else if (repository.endsWith(".jar")) {
repositories.add(
new Repository(repository, RepositoryType.JAR));
} else {
repositories.add(
new Repository(repository, RepositoryType.DIR));
}
}
return ClassLoaderFactory.createClassLoader(repositories, parent);
}
createClassLoader首先回去读取CatalinaProperties中的common.loader,server.loader,shared.loader三个属性,进入CatalinaProperties类中会发现这三个属性来自conf/catalina.properties文件。
接着往下createClassLoader会将common.loader,server.loader,shared.loader三个属性中的值获取然后解析成Repository,然后交给ClassLoaderFactory.createClassLoader方法去创建类加载器,最后可以实现三个不同的类加载器分别加载不同目录下的类。
当然要说清楚的是,默认情况下,catalina.properties中server.loader,shared.loader并没有配置值,三个类加载是同一个,默认加载{catalina.home}/lib目录下的类和jar包。
如果想配置对所有web应用都可见但对tomcat内部服务器不可见的类,此时应该在catalina.properties文件中的shared.loader下进行配置。
Thread.currentThread().setContextClassLoader(catalinaLoader)将catalinaLoader设置为Tomcat主线程的线程上下文类加载器。
3.2、SecurityClassLoad.securityClassLoad(catalinaLoader)
SecurityClassLoad.securityClassLoad用于线程安全的加载tomcat容器所需的class。
当然,要使这个方法真正起作用,需要启动tomcat安全管理器,由代码可知:
public static void securityClassLoad(ClassLoader loader) throws Exception {
securityClassLoad(loader, true);
}
static void securityClassLoad(ClassLoader loader, boolean requireSecurityManager)
throws Exception {
if (requireSecurityManager && System.getSecurityManager() == null) {
return;
}
loadCorePackage(loader);
loadCoyotePackage(loader);
loadLoaderPackage(loader);
loadRealmPackage(loader);
loadServletsPackage(loader);
loadSessionPackage(loader);
loadUtilPackage(loader);
loadJavaxPackage(loader);
loadConnectorPackage(loader);
loadTomcatPackage(loader);
}
如果没启用安全管理器,System.getSecurityManager()=null,直接return。
可以通过命令行的方式启功安全管理器:
catalina.bat run -security或者startup.bat -security
一旦启动了安全管理器,就会根据conf/catalina.policy文件定义的提供默认的安全策略,securityClassLoad方法中System.getSecurityManager()不再等于null,于是就会去执行一系列加载方法,将tomcat的class加载进来。
想了解to吗tomcat的安全策略,可以参考下这篇博文:
3.3、通过反射实例化catalina
Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.getConstructor().newInstance();
// Set the shared extensions class loader
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;
可以看到这部分代码是通过反射来实例化catalina,然后反射调用setParentClassLoader将sharedLoader传入到catalina实例中。
为什么不直接通过Bootstrap类直接启动tomcat,而是通过反射生成Catalina实例启动?
再查看tomcat目录结构时应该发现,Bootstrap并不在$CATALINA_HOME/lib目录下,而是在$CATALINA_HOME/bin目录中,Bootstrap和Catalina松耦合(通过反射调用Catalina),它直接依赖JRE运行并为Tomcat应用服务器创建共享类加载器,用于构造Catalina和整个tomcat服务器。实现了启动入口和核心环境的解耦,简化了启动。
四、setAwait
来看setAwait:
public void setAwait(boolean await)
throws Exception {
Class<?> paramTypes[] = new Class[1];
paramTypes[0] = Boolean.TYPE;
Object paramValues[] = new Object[1];
paramValues[0] = Boolean.valueOf(await);
Method method =
catalinaDaemon.getClass().getMethod("setAwait", paramTypes);
method.invoke(catalinaDaemon, paramValues);
}
通过反射调用catalina的setAwait方法,其设置的值是留给后面用的,当Catalina将Tomcat的所有组件启动之后,会检查await属性,如果为true,会调用Catalina.await(),而Catalina.await()又会调用其内部的Server的await()。
public void start() {
...
if (await) {
await();
stop();
}
}
Server.await()包含一个while循环,此循环用于监听指定socket端口(默认为8005)的连接,当某个连接传入的参数为”SHUTDOWN”(默认为”SHUTDOWN”)时,终止此while循环(端口号和终止while循环的参数,在server.xml的Server标签设置)。
Server.await()用来维持Bootstrap的main方法(main thread)处于运行状态,而线程池中监听http请求的线程是守护线程(daemon thread)。
当Tomcat的指定端口接收到关闭命令时,Server.await()内的while循环终止,然后Catalina会调用stop()方法,关闭Tomcat的所有组件,最终Bootstrap的main thread终止,Tomcat关闭。
五、load
private void load(String[] arguments)
throws Exception {
// Call the load() method
String methodName = "load";
Object param[];
Class<?> paramTypes[];
if (arguments==null || arguments.length==0) {
paramTypes = null;
param = null;
} else {
paramTypes = new Class[1];
paramTypes[0] = arguments.getClass();
param = new Object[1];
param[0] = arguments;
}
Method method =
catalinaDaemon.getClass().getMethod(methodName, paramTypes);
if (log.isDebugEnabled())
log.debug("Calling startup class " + method);
method.invoke(catalinaDaemon, param);
}
该方法通过反射调用catalina的load方法。
catalina的load方法首先初始化目录(initDirs)和初始化命名服务(initNaming),然后是createStartDigester(要了解这个方法,应该先了解一下Digester,它是apache的一个开源组件,通过它可以很方便的从xml文件生成java对象),该方法初始化Digester,为Xml的标签即解析模式增加处理规则rule。
关于Digester,可以参考这篇博文:
来看createStartDigester方法:
protected Digester createStartDigester() {
long t1=System.currentTimeMillis();
// Initialize the digester
Digester digester = new Digester();
digester.setValidating(false);
digester.setRulesValidation(true);
Map<Class<?>, List<String>> fakeAttributes = new HashMap<>();
List<String> attrs = new ArrayList<>();
attrs.add("className");
fakeAttributes.put(Object.class, attrs);
digester.setFakeAttributes(fakeAttributes);
digester.setUseContextClassLoader(true);
// 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.NamingResourcesImpl");
digester.addSetProperties("Server/GlobalNamingResources");
digester.addSetNext("Server/GlobalNamingResources",
"setGlobalNamingResources",
"org.apache.catalina.deploy.NamingResourcesImpl");
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", "sslImplementationName", "protocol"}));
digester.addSetNext("Server/Service/Connector",
"addConnector",
"org.apache.catalina.connector.Connector");
digester.addObjectCreate("Server/Service/Connector/SSLHostConfig",
"org.apache.tomcat.util.net.SSLHostConfig");
digester.addSetProperties("Server/Service/Connector/SSLHostConfig");
digester.addSetNext("Server/Service/Connector/SSLHostConfig",
"addSslHostConfig",
"org.apache.tomcat.util.net.SSLHostConfig");
digester.addRule("Server/Service/Connector/SSLHostConfig/Certificate",
new CertificateCreateRule());
digester.addRule("Server/Service/Connector/SSLHostConfig/Certificate",
new SetAllPropertiesRule(new String[]{"type"}));
digester.addSetNext("Server/Service/Connector/SSLHostConfig/Certificate",
"addCertificate",
"org.apache.tomcat.util.net.SSLHostConfigCertificate");
digester.addObjectCreate("Server/Service/Connector/SSLHostConfig/OpenSSLConf",
"org.apache.tomcat.util.net.openssl.OpenSSLConf");
digester.addSetProperties("Server/Service/Connector/SSLHostConfig/OpenSSLConf");
digester.addSetNext("Server/Service/Connector/SSLHostConfig/OpenSSLConf",
"setOpenSslConf",
"org.apache.tomcat.util.net.openssl.OpenSSLConf");
digester.addObjectCreate("Server/Service/Connector/SSLHostConfig/OpenSSLConf/OpenSSLConfCmd",
"org.apache.tomcat.util.net.openssl.OpenSSLConfCmd");
digester.addSetProperties("Server/Service/Connector/SSLHostConfig/OpenSSLConf/OpenSSLConfCmd");
digester.addSetNext("Server/Service/Connector/SSLHostConfig/OpenSSLConf/OpenSSLConfCmd",
"addCmd",
"org.apache.tomcat.util.net.openssl.OpenSSLConfCmd");
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");
digester.addObjectCreate("Server/Service/Connector/UpgradeProtocol",
null, // MUST be specified in the element
"className");
digester.addSetProperties("Server/Service/Connector/UpgradeProtocol");
digester.addSetNext("Server/Service/Connector/UpgradeProtocol",
"addUpgradeProtocol",
"org.apache.coyote.UpgradeProtocol");
// 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/"));
addClusterRuleSet(digester, "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));
addClusterRuleSet(digester, "Server/Service/Engine/Cluster/");
long t2=System.currentTimeMillis();
if (log.isDebugEnabled()) {
log.debug("Digester for server.xml created " + ( t2-t1 ));
}
return digester;
}
该方法初始化digester,创建一系列解析规则,然后在load方法中会调用:
digester.parse(inputSource);
可见digester解析的源是inputSource,而inputSource是来自于conf/server.xml:
file = configFile();
inputStream = new FileInputStream(file);
inputSource = new InputSource(file.toURI().toURL().toString());
configFile:
protected File configFile() {
//protected String configFile = "conf/server.xml";
File file = new File(configFile);
if (!file.isAbsolute()) {
file = new File(Bootstrap.getCatalinaBase(), configFile);
}
return file;
}
这样便创建出了StandardServer对象,接着便调用getServer().init();
init方法来自StandardServer的父类LifecycleBase:
public final synchronized void init() throws LifecycleException {
if (!state.equals(LifecycleState.NEW)) {
invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
}
try {
setStateInternal(LifecycleState.INITIALIZING, null, false);
initInternal();
setStateInternal(LifecycleState.INITIALIZED, null, false);
} catch (Throwable t) {
handleSubClassException(t, "lifecycleBase.initFail", toString());
}
}
具体实现是在子类的initInternal方法中,在调用initInternal方法前后都会设置状态,LifecycleState.INITIALIZING代表正在初始化,LifecycleState.INITIALIZED表示初始化完成,相应会触发生命周期事件。
在StandardServer的initInternal方法中会调用子组件Services的init方法,并依次传递下去,完成所有组件的init。
可见catalina的load方法主要是根据conf/server.xml配置文件利用Digester创建服务器组件,然后调用Server的init方法,逐层次的实现所有组件的初始化。
六、start
最后看下start方法:
public void start()
throws Exception {
if( catalinaDaemon==null ) init();
Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
method.invoke(catalinaDaemon, (Object [])null);
}
同样也是通过反射调用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
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("Server startup in " + ((t2 - t1) / 1000000) + " ms");
}
// Register shutdown 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);
}
}
if (await) {
await();
stop();
}
}
该方法主要触发StandardServer的start方法,StandardServer的start方法同init方法一样来自LifecycleBase,主要是改变生命周期的状态,同时触发相应的生命周期时间,具体的执行逻辑交由具体的子类startInternal方法实现:
public final synchronized void start() throws LifecycleException {
if (LifecycleState.STARTING_PREP.equals(state) || LifecycleState.STARTING.equals(state) ||
LifecycleState.STARTED.equals(state)) {
return;
}
if (state.equals(LifecycleState.NEW)) {
init();
} else if (state.equals(LifecycleState.FAILED)) {
stop();
} else if (!state.equals(LifecycleState.INITIALIZED) &&
!state.equals(LifecycleState.STOPPED)) {
invalidTransition(Lifecycle.BEFORE_START_EVENT);
}
try {
setStateInternal(LifecycleState.STARTING_PREP, null, false);
startInternal();
if (state.equals(LifecycleState.FAILED)) {
// This is a 'controlled' failure. The component put itself into the
// FAILED state so call stop() to complete the clean-up.
stop();
} else if (!state.equals(LifecycleState.STARTING)) {
// Shouldn't be necessary but acts as a check that sub-classes are
// doing what they are supposed to.
invalidTransition(Lifecycle.AFTER_START_EVENT);
} else {
setStateInternal(LifecycleState.STARTED, null, false);
}
} catch (Throwable t) {
// This is an 'uncontrolled' failure so put the component into the
// FAILED state and throw an exception.
handleSubClassException(t, "lifecycleBase.startFail", toString());
}
}
在StandardServer的startInternal方法中会调用子组件service的start方法,并依次调用其他组件的start方法。
protected void startInternal() throws LifecycleException {
fireLifecycleEvent(CONFIGURE_START_EVENT, null);
setState(LifecycleState.STARTING);
globalNamingResources.start();
// Start our defined Services
synchronized (servicesLock) {
for (int i = 0; i < services.length; i++) {
services[i].start();
}
}
}
所以同load方法很相似,start方法主要是实现各组件的start方法依次调用,可以用一张图来理解:
还应该看到Catalina的start方法会使用前面的setAwait方法传递的值,为true时,会在8005端口监听,保证主线程一直在运行,直到收到SHUTDOWN命令。