本篇結(jié)構(gòu):
- 前言
- 總覽一下main方法
- init
- setAwait
- load
- start
一立由、前言
上一篇中介紹了startup.bat和catalina.bat腳本。了解到日常雙擊startup.bat啟動tomcat腰根,其實(shí)是來到catalina.bat腳本中按声,由catalina.bat腳本去執(zhí)行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方法大致可以分為兩部分汉形。
上部分是實(shí)例化一個Bootstrap對象,并調(diào)用init方法倍阐,然后賦值給daemon變量概疆,當(dāng)然如果daemon已經(jīng)不是空了,說明已經(jīng)初始化過了峰搪,就將daemon.catalinaLoader直接設(shè)置到當(dāng)前線程(daemon.catalinaLoader是用來加載tomcat內(nèi)部服務(wù)器所需類的類加載器)岔冀。
下部分是根據(jù)傳遞進(jìn)來的參數(shù)決定走哪一步,當(dāng)雙擊startup.bat時(shí)概耻,傳進(jìn)來的是start使套,所以會來到這段:
else if (command.equals("start")) {
daemon.setAwait(true);
daemon.load(args);
daemon.start();
}
這里主要是調(diào)用三個方法,setAwait鞠柄,load和start侦高。
所以對于Bootstrap重要關(guān)注的就是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方法調(diào)用initClassLoaders初始化類加載器夯尽,然后將初始化好的catalinaLoader設(shè)置到當(dāng)前線程瞧壮,接著通過反射調(diào)用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并建立他們之間的關(guān)系蛾娶,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三個屬性酬屉,進(jìn)入CatalinaProperties類中會發(fā)現(xiàn)這三個屬性來自conf/catalina.properties文件。
接著往下createClassLoader會將common.loader,server.loader呐萨,shared.loader三個屬性中的值獲取然后解析成Repository杀饵,然后交給ClassLoaderFactory.createClassLoader方法去創(chuàng)建類加載器,最后可以實(shí)現(xiàn)三個不同的類加載器分別加載不同目錄下的類谬擦。
當(dāng)然要說清楚的是切距,默認(rèn)情況下,catalina.properties中server.loader惨远,shared.loader并沒有配置值谜悟,三個類加載是同一個,默認(rèn)加載{catalina.home}/lib目錄下的類和jar包北秽。
如果想配置對所有web應(yīng)用都可見但對tomcat內(nèi)部服務(wù)器不可見的類葡幸,此時(shí)應(yīng)該在catalina.properties文件中的shared.loader下進(jìn)行配置。
Thread.currentThread().setContextClassLoader(catalinaLoader)將catalinaLoader設(shè)置為Tomcat主線程的線程上下文類加載器贺氓。
3.2蔚叨、SecurityClassLoad.securityClassLoad(catalinaLoader)
SecurityClassLoad.securityClassLoad用于線程安全的加載tomcat容器所需的class。
當(dāng)然辙培,要使這個方法真正起作用蔑水,需要啟動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
一旦啟動了安全管理器尾抑,就會根據(jù)conf/catalina.policy文件定義的提供默認(rèn)的安全策略歇父,securityClassLoad方法中System.getSecurityManager()不再等于null,于是就會去執(zhí)行一系列加載方法蛮穿,將tomcat的class加載進(jìn)來。
想了解to嗎t(yī)omcat的安全策略毁渗,可以參考下這篇博文:
3.3践磅、通過反射實(shí)例化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;
可以看到這部分代碼是通過反射來實(shí)例化catalina,然后反射調(diào)用setParentClassLoader將sharedLoader傳入到catalina實(shí)例中灸异。
為什么不直接通過Bootstrap類直接啟動tomcat府适,而是通過反射生成Catalina實(shí)例啟動?
再查看tomcat目錄結(jié)構(gòu)時(shí)應(yīng)該發(fā)現(xiàn)肺樟,Bootstrap并不在$CATALINA_HOME/lib目錄下檐春,而是在$CATALINA_HOME/bin目錄中,Bootstrap和Catalina松耦合(通過反射調(diào)用Catalina)么伯,它直接依賴JRE運(yùn)行并為Tomcat應(yīng)用服務(wù)器創(chuàng)建共享類加載器疟暖,用于構(gòu)造Catalina和整個tomcat服務(wù)器。實(shí)現(xiàn)了啟動入口和核心環(huán)境的解耦,簡化了啟動俐巴。
四骨望、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);
}
通過反射調(diào)用catalina的setAwait方法,其設(shè)置的值是留給后面用的欣舵,當(dāng)Catalina將Tomcat的所有組件啟動之后擎鸠,會檢查await屬性,如果為true缘圈,會調(diào)用Catalina.await()劣光,而Catalina.await()又會調(diào)用其內(nèi)部的Server的await()。
public void start() {
...
if (await) {
await();
stop();
}
}
Server.await()包含一個while循環(huán)糟把,此循環(huán)用于監(jiān)聽指定socket端口(默認(rèn)為8005)的連接绢涡,當(dāng)某個連接傳入的參數(shù)為”SHUTDOWN”(默認(rèn)為”SHUTDOWN”)時(shí),終止此while循環(huán)(端口號和終止while循環(huán)的參數(shù)糊饱,在server.xml的Server標(biāo)簽設(shè)置)垂寥。
Server.await()用來維持Bootstrap的main方法(main thread)處于運(yùn)行狀態(tài),而線程池中監(jiān)聽http請求的線程是守護(hù)線程(daemon thread)另锋。
當(dāng)Tomcat的指定端口接收到關(guān)閉命令時(shí)滞项,Server.await()內(nèi)的while循環(huán)終止,然后Catalina會調(diào)用stop()方法夭坪,關(guān)閉Tomcat的所有組件文判,最終Bootstrap的main thread終止,Tomcat關(guān)閉室梅。
五戏仓、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);
}
該方法通過反射調(diào)用catalina的load方法。
catalina的load方法首先初始化目錄(initDirs)和初始化命名服務(wù)(initNaming)亡鼠,然后是createStartDigester(要了解這個方法赏殃,應(yīng)該先了解一下Digester,它是apache的一個開源組件间涵,通過它可以很方便的從xml文件生成java對象)仁热,該方法初始化Digester,為Xml的標(biāo)簽即解析模式增加處理規(guī)則rule勾哩。
關(guān)于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,創(chuàng)建一系列解析規(guī)則思劳,然后在load方法中會調(diào)用:
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;
}
這樣便創(chuàng)建出了StandardServer對象,接著便調(diào)用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());
}
}
具體實(shí)現(xiàn)是在子類的initInternal方法中潜叛,在調(diào)用initInternal方法前后都會設(shè)置狀態(tài)秽褒,LifecycleState.INITIALIZING代表正在初始化壶硅,LifecycleState.INITIALIZED表示初始化完成,相應(yīng)會觸發(fā)生命周期事件震嫉。
在StandardServer的initInternal方法中會調(diào)用子組件Services的init方法森瘪,并依次傳遞下去,完成所有組件的init票堵。
可見catalina的load方法主要是根據(jù)conf/server.xml配置文件利用Digester創(chuàng)建服務(wù)器組件扼睬,然后調(diào)用Server的init方法,逐層次的實(shí)現(xiàn)所有組件的初始化悴势。
六窗宇、start
最后看下start方法:
public void start()
throws Exception {
if( catalinaDaemon==null ) init();
Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
method.invoke(catalinaDaemon, (Object [])null);
}
同樣也是通過反射調(diào)用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();
}
}
該方法主要觸發(fā)StandardServer的start方法,StandardServer的start方法同init方法一樣來自LifecycleBase特纤,主要是改變生命周期的狀態(tài)军俊,同時(shí)觸發(fā)相應(yīng)的生命周期時(shí)間,具體的執(zhí)行邏輯交由具體的子類startInternal方法實(shí)現(xiàn):
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方法中會調(diào)用子組件service的start方法捧存,并依次調(diào)用其他組件的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方法主要是實(shí)現(xiàn)各組件的start方法依次調(diào)用昔穴,可以用一張圖來理解:
還應(yīng)該看到Catalina的start方法會使用前面的setAwait方法傳遞的值镰官,為true時(shí),會在8005端口監(jiān)聽吗货,保證主線程一直在運(yùn)行泳唠,直到收到SHUTDOWN命令。