大家都知道,每一個應用程式都有一個唯一的入口(即main函式),那麼對於Java語言開發的tomcat伺服器也不例外,找到這個入口,瞭解各個元件載入的具體過程,對理解整個應用的實現過程有很大的幫助。 tomcat啟動相關的類位於catalina.startup包路徑下,入口是類Bootstrap中的main()函式。Bootstrap啟動類主要完成了三方面的內容,分別如下:
①在靜態程式碼塊中設定catalinaHome和catalinaBase兩個路徑;
②common、server、shared三個類載入器的初始化;
③利用反射機制例項化org.apache.catalina.startup.Catalina類。
一、設定catalinaHome和catalinaBase
catalinaHome是tomcat的安裝目錄,catalinaBase是tomcat的工作目錄;這兩個目錄的主要功能是當在同一臺機器上部署多個tomcat例項時,可以不用安裝多個tomcat副本,而是通過共享tomcat程式碼的方式實現。例如在同一臺機器上部署兩個tomcat例項時,只需要建立兩個base目錄,base1和base2,然後將tomcat安裝目錄下的共享目錄拷貝到這兩個目錄下。分別修改conf目錄下的server.xml檔案中的埠號,就可以同時啟動兩個tomcat伺服器,此時tomcat安裝目錄(即tomcatHome)下的bin和lib目錄是共享的。
static {
//獲取tomcat的安裝目錄
String userDir = System.getProperty("user.dir");
String home = System.getProperty(Globals.CATALINA_HOME_PROP);
File homeFile = null;
if (home != null) {
File f = new File(home);
try {
homeFile = f.getCanonicalFile();
} catch (IOException ioe) {
homeFile = f.getAbsoluteFile();
}
}
if (homeFile == null) {
//bootstrap.jar的根目錄,其實就是tomcat按照路徑下的bin資料夾
File bootstrapJar = new File(userDir, "bootstrap.jar");
if (bootstrapJar.exists()) {
File f = new File(userDir, "..");
try {
homeFile = f.getCanonicalFile();
} catch (IOException ioe) {
homeFile = f.getAbsoluteFile();
}
}
}
if (homeFile == null) {
// Second fall-back. Use current directory
File f = new File(userDir);
try {
homeFile = f.getCanonicalFile();
} catch (IOException ioe) {
homeFile = f.getAbsoluteFile();
}
}
catalinaHomeFile = homeFile;
System.setProperty(
Globals.CATALINA_HOME_PROP, catalinaHomeFile.getPath());
// Then base
String base = System.getProperty(Globals.CATALINA_BASE_PROP);
if (base == null) {
catalinaBaseFile = catalinaHomeFile;
} else {
File baseFile = new File(base);
try {
baseFile = baseFile.getCanonicalFile();
} catch (IOException ioe) {
baseFile = baseFile.getAbsoluteFile();
}
catalinaBaseFile = baseFile;
}
System.setProperty(
Globals.CATALINA_BASE_PROP, catalinaBaseFile.getPath());
}
複製程式碼
二、初始化類載入器 common、server、shared
tomcat自定義的類載入器主要有三類:common、server和shared,用來載入不同位置的類和jar檔案,實現不同web應用程式以及tomcat系統之間類檔案的分離和共享。主要解決的問題有:部署在同一個tomcat伺服器上多個web應用程式所使用的Java類庫可以實現相互分離(同一個類庫的不同版本);部署在同一個tomcat伺服器上多個web應用程式所使用的Java類庫可以實現相互共享;伺服器保證自身的安全不受部署的web應用程式的影響(tomcat系統類只能通過server類載入器載入,web應用程式由shared類載入器下的WebAppClassLoader載入);實現HotSwap功能(通過替換JsperLoader類載入器實現)。
commonClassLoader
commonClassLoader在catalina.properties中的定義是:common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
可以看出它載入的類庫是catalina.base和catalina.home下lib中的檔案。common是伺服器和web應用程式共用的類載入器,也是server和shared的父載入器。在Bootstrap中的實現如下
private void initClassLoaders() {
try {
commonLoader = createClassLoader("common", null);
if( commonLoader == null ) {
commonLoader=this.getClass().getClassLoader();
}
catalinaLoader = createClassLoader("server", commonLoader); //設定commonLoader為catalinaLoader和sharedLoader的父載入器。
sharedLoader = createClassLoader("shared", commonLoader);
} catch (Throwable t) {
handleThrowable(t);
log.error("Class loader creation threw exception", t);
System.exit(1);
}
}
複製程式碼
繼續跟蹤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) {
try {
@SuppressWarnings("unused")
URL url = new URL(repository);
repositories.add(new Repository(repository, RepositoryType.URL));
continue;
} catch (MalformedURLException e) {
// Ignore
}
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);
}
複製程式碼
catalinaClassLoader
catalinaClassLoader在catalina.properties中設定的路徑為空 server.loader=
,所以它的類載入路徑和它的父載入器commonClassLoader一樣。初始化catalinaClassLoader後在init方法中設定為當前執行緒的類載入器,然後完成對rg.apache.catalina.startup.Catalina類的載入。
Thread.currentThread().setContextClassLoader(catalinaLoader);
SecurityClassLoad.securityClassLoad(catalinaLoader);
Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
複製程式碼
sharedClassLoader
catalinaClassLoader在catalina.properties中設定的路徑也為空,所以其預設類載入路徑和它的父載入器commonClassLoader一樣。
利用反射機制例項化org.apache.catalina.startup.Catalina類
Catalina類在tomcat啟動中有著比較重要的作用,作為tomcat生命週期的一個開始類,Catalina主要職責是解析server.xml檔案,完成Server,Service,Connector等元件的啟動和關閉,接受tomcat停止指令,關閉tomcat伺服器。在Bootstrap中通過反射機制生成org.apache.catalina.startup.Catalina例項,程式碼如下
Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.getConstructor().newInstance();
複製程式碼
在Catalina類中是通過建立Digester的方式解析server.xml並且生成Server,Service,Connector和Container例項的,xml中只配置了Engine和Host兩種Container。
protected String configFile = "conf/server.xml"; //設定server.xml的路徑
...
protected Digester createStartDigester() {
long t1=System.currentTimeMillis();
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);
digester.addObjectCreate("Server",
"org.apache.catalina.core.StandardServer",
"className");
digester.addSetProperties("Server");
digester.addSetNext("Server",
"setServer",
"org.apache.catalina.Server");
//省略一大段設定其他元件的程式碼,形式和StandardServer的生成一樣,關於Digester類解析xml生成Java例項可參考《深入剖析tomcat》第15章。
}
複製程式碼
Catalina類的入口同樣是start方法,在Bootstrap中通過反射的方式呼叫了Catalina的start方法
public void start()
throws Exception {
if( catalinaDaemon==null ) init();
Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
method.invoke(catalinaDaemon, (Object [])null);
}
複製程式碼
start方法中又呼叫load方法進行建立Digester,生成各個元件;load類的定義如下:
public void load() {
if (loaded) {
return;
}
loaded = true;
long t1 = System.nanoTime();
initDirs();
// Before digester - it may be needed
initNaming();
// Create and execute our Digester
Digester digester = createStartDigester();
InputSource inputSource = null;
InputStream inputStream = null;
File file = null;
try {
try {
file = configFile(); //獲取到server.xml 檔案並解析
inputStream = new FileInputStream(file);
inputSource = new InputSource(file.toURI().toURL().toString());
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("catalina.configFail", file), e);
}
}
if (inputStream == null) {
try {
inputStream = getClass().getClassLoader()
.getResourceAsStream(getConfigFile());
inputSource = new InputSource
(getClass().getClassLoader()
.getResource(getConfigFile()).toString());
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("catalina.configFail",
getConfigFile()), 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 (log.isDebugEnabled()) {
log.debug(sm.getString("catalina.configFail",
"server-embed.xml"), e);
}
}
}
if (inputStream == null || inputSource == null) {
if (file == null) {
log.warn(sm.getString("catalina.configFail",
getConfigFile() + "] or [server-embed.xml]"));
} else {
log.warn(sm.getString("catalina.configFail",
file.getAbsolutePath()));
if (file.exists() && !file.canRead()) {
log.warn("Permissions incorrect, read permission is not allowed on the file.");
}
}
return;
}
try {
inputSource.setByteStream(inputStream); //對server.xml資料流解析
digester.push(this);
digester.parse(inputSource);
} catch (SAXParseException spe) {
log.warn("Catalina.start using " + getConfigFile() + ": " +
spe.getMessage());
return;
} catch (Exception e) {
log.warn("Catalina.start using " + getConfigFile() + ": " , e);
return;
}
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
// Ignore
}
}
}
getServer().setCatalina(this); //配置server,啟動類為當前Catalina類,Home和base在Bootstrap中定義
getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());
// Stream redirection
initStreams();
// 啟動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("Catalina.start", e);
}
}
long t2 = System.nanoTime();
if(log.isInfoEnabled()) {
log.info("Initialization processed in " + ((t2 - t1) / 1000000) + " ms");
}
}
複製程式碼
tomcat啟動是通過server.xml配置檔案實現Server、Service、Connector和Container元件,那麼這些元件具體是如何實現的,後面幾篇文章將繼續跟進tomcat各元件的具體實現。
tomcat原始碼分析(第一篇 tomcat原始碼分析(第一篇 從整體架構開始))
tomcat原始碼分析(第三篇 tomcat請求原理解析--Connector原始碼分析)
tomcat原始碼分析(第四篇 tomcat請求處理原理解析--Container原始碼分析)