Tomcat學習之二,認識Bootstrap類

Bootstrap類全稱org.apache.catalina.startup.Bootstrap
整個類加上註釋和空白也就559行.代碼寫得很規整.到底人家是世界級的代碼嘛.或者java的代碼格式很容易寫清楚.
整個類中有22個方法,六個成員變量 ,還有一個日誌成員變量 .可見平均下來類中的方法也就10多行代碼這個樣子.我喜歡類中方法分類得當的.太長方法說明設計不當或者實在是邏輯很複雜.
這個類註釋如下:

/**
* Bootstrap loader for Catalina. This application constructs a class loader
* for use in loading the Catalina internal classes (by accumulating all of the
* JAR files found in the "server" directory under "catalina.home"), and
* starts the regular execution of the container. The purpose of this
* roundabout approach is to keep the Catalina internal classes (and any
* other classes they depend on, such as an XML parser) out of the system
* class path and therefore not visible to application level classes.

* -------------------------我的翻譯如下:-----------------------------------------
* Catalina的Bootstrap加載器(loader).此程序構造了一個類加載器(class loader)用來在加載Catalina的內部類(internal classes)(即彙集在"catalina.home"下"server"目錄中的所有JAR文件,和啓動容器的常規執行(regular execution).這樣繞着彎子的做法是爲了保持Catalina的內部類(和它依賴的任何類,例如XMP解析器)不在系統中類路徑(system class path)中,從而對應用程序的類就不可見了.
*/

我們來看下Bootstrap的main方法(不知道爲什麼,對於程序自從C開始,我就首先去找main函數,找不到我就有點不太爽.)主要就是初始爲類加載器並啓動此類.如下:

/**
* Main method, used for testing only.
*
* @param args Command line arguments to be processed
*/
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;
}
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) {
handleThrowable(t);
// Unwrap the Exception for clearer error reporting
if (t instanceof InvocationTargetException &&
t.getCause() != null) {
t = t.getCause();
}
t.printStackTrace();
System.exit(1);
}

}


(#q1)上面那句註釋中說的僅用於測試,難道正確的啓動就不用了嗎?
還有對於下面的代碼:

String command = "start";
if (args.length > 0) {
command = args[args.length - 1];
}


(#q2) 爲什麼判斷了args.length之後,是用args[args.length - 1]而不是用args[0]呢?

從此代碼中,我還了解到了一點,tomcat7源代碼中的try{}catch(Throwable t){}
catch子句中,catch的都是Throwable.之前我一直覺得Exception是根本,思考了之下.
我看了下jdk.原來Exception還有一個Throwable超類.同時Throwable也是Errors的超類.

[quote]
Throwable 類是 Java 語言中所有錯誤或異常的超類,只有當對象是此類(或其子類之一)的實例時,才能通過 Java 虛擬機或者 Java throw 語句拋出。類似地,只有此類或其子類之一纔可以是 catch 子句中的參數類型。
[/quote]

我們看下main中調用的初始化方法即bootstrap.init().
看下初始化方法都做了哪些事件先:

/**
* Initialize daemon.
*/
public void init()
throws Exception
{

// Set Catalina path
setCatalinaHome();
setCatalinaBase();

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.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;

}

上面的方法也比較清晰易懂:
先是設置Catalina的路徑.
(吐槽下啊.爲什麼叫catalina這個名字呢?我google了下,沒有找到什麼好的解釋:
不過google給出的第一個網站的說明是:
If you're planning a trip to Catalina Island, this should be your first stop. The Catalina Island Guide includes packages, activities, lodging, transportation, local ...

是不是因爲tomcat的開發者喜歡這個Catalina Island啊?
)
設置路徑之後,調用初始化類加載器的方法即initClassLoaders()
如下:
 
private void initClassLoader(){
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.
然後以commonLoader爲父類加載器.創標籤名爲"server"的catalinaLoader類加載器,和標籤名爲"shared"的sharedLoader類加載器.
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<Repository>();
StringTokenizer tokenizer = new StringTokenizer(value,",");
while(tokenizer.hasMoreElements()){
String repository = tokenizer.nextToken().trim();
if(repository.length() == 0){
continue;
}

//check for a jar URL repository
try{
@SupperssWarning("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 Reposiotry(repository,RepositoryType.GLOB);
}else if(repository.endsWith(".jar")){
repositories.add(new Reposiotry(repository,RepositoryType.JAR);
}else {
repositories.add(new Reposiotry(repository,RepositoryType.DIR);
}

}

ClassLoader classLoader = ClassLoaderFactory.createClassLoader(repositories,parent);

// Retrieving MBean server
MBeanServer mBeanServer = null;
if(MBeanServerFactory.findMBeanServer(null).size() > 0) {
mBeanServer = MBeanServerFactory.findMBeanServer(null).get(0);
}else{
mBeanServer = ManagementFactory.getPlatformMBeanServer();
}

// Register the server classLoader
ObjectName objectName = new ObjectName("Catalina:type=ServerClassLoader,name="+name);
mBeanServer.registerMBean(classLoader,objectName);

return classLoader;

}

上面的方法中第一句代碼:
 String value = CatalinaProperties.getProperty(name+".loader");

CatalinaProperties就是指tomcat/conf目錄下的catalina.properties文件的配置.
common.loader的value值爲:
[quote]
common.loader=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar
[/quote]

 value = replace(value)

上面這一句代碼執行過後,value值,在我的系統中變成了如下:
[quote]
"/home/banxi1988/work/tomcat7/tomcat-7.0.x/output/build/lib,/home/banxi1988/work/tomcat7/tomcat-7.0.x/output/build/lib/*.jar,/home/banxi1988/work/tomcat7/tomcat-7.0.x/output/build/lib,/home/banxi1988/work/tomcat7/tomcat-7.0.x/output/build/lib/*.jar"
[/quote]
上面出現的Repository是ClassLoaderFactory的靜態內部類,
RepositoryType是ClassLoaderFactory的靜態內部枚舉.
代碼如下:

public static enum RepositoryType {
DIR,
GLOB,
JAR,
URL
}

public static class Repository {
private String location;
private RepositoryType type;

public Repository(String location, RepositoryType type) {
this.location = location;
this.type = type;
}

public String getLocation() {
return location;
}

public RepositoryType getType() {
return type;
}
}

在確定的類資源的倉庫之後,接下來就到了tomcat加載類最主要的部分了.

ClassLoader classLoader = ClassLoaderFactory.createClassLoader(repositories,parent);

在ClassLoaderFactory的createClassLoader方法中,先是將repositories中路徑,變成一個
包含倉庫中jar包文件的URL路徑的數組:
然後以此數組爲參數返回一個標準的類加載器(StandardClassLoader).
此方法中的關鍵代碼如下:

// Construct the class loader itself
final URL[] array = set.toArray(new URL[set.size()]);
if (log.isDebugEnabled())
for (int i = 0; i < array.length; i++) {
log.debug(" location " + i + " is " + array[i]);
}

return AccessController.doPrivileged(
new PrivilegedAction<StandardClassLoader>() {
@Override
public StandardClassLoader run() {
if (parent == null)
return new StandardClassLoader(array);
else
return new StandardClassLoader(array, parent);
}
});

接我上面環境.此代碼執行過程,array數組中的內容如下所示:

[quote]
[file:/home/banxi1988/work/tomcat7/tomcat-7.0.x/output/build/lib/, file:/home/banxi1988/work/tomcat7/tomcat-7.0.x/output/build/lib/jasper-el.jar, file:/home/banxi1988/work/tomcat7/tomcat-7.0.x/output/build/lib/tomcat-jdbc.jar, file:/home/banxi1988/work/tomcat7/tomcat-7.0.x/output/build/lib/tomcat-api.jar, file:/home/banxi1988/work/tomcat7/tomcat-7.0.x/output/build/lib/tomcat-i18n-ja.jar, file:/home/banxi1988/work/tomcat7/tomcat-7.0.x/output/build/lib/catalina-ha.jar, file:/home/banxi1988/work/tomcat7/tomcat-7.0.x/output/build/lib/tomcat-dbcp.jar, file:/home/banxi1988/work/tomcat7/tomcat-7.0.x/output/build/lib/ecj-3.7.jar, file:/home/banxi1988/work/tomcat7/tomcat-7.0.x/output/build/lib/tomcat-coyote.jar, file:/home/banxi1988/work/tomcat7/tomcat-7.0.x/output/build/lib/tomcat-i18n-fr.jar, file:/home/banxi1988/work/tomcat7/tomcat-7.0.x/output/build/lib/jsp-api.jar, file:/home/banxi1988/work/tomcat7/tomcat-7.0.x/output/build/lib/tomcat-i18n-es.jar, file:/home/banxi1988/work/tomcat7/tomcat-7.0.x/output/build/lib/el-api.jar, file:/home/banxi1988/work/tomcat7/tomcat-7.0.x/output/build/lib/jasper.jar, file:/home/banxi1988/work/tomcat7/tomcat-7.0.x/output/build/lib/catalina.jar, file:/home/banxi1988/work/tomcat7/tomcat-7.0.x/output/build/lib/catalina-tribes.jar, file:/home/banxi1988/work/tomcat7/tomcat-7.0.x/output/build/lib/servlet-api.jar, file:/home/banxi1988/work/tomcat7/tomcat-7.0.x/output/build/lib/tomcat-util.jar, file:/home/banxi1988/work/tomcat7/tomcat-7.0.x/output/build/lib/annotations-api.jar, file:/home/banxi1988/work/tomcat7/tomcat-7.0.x/output/build/lib/catalina-ant.jar]
[/quote]

現在我們來仔細看下上面那個複雜的返回語句:
首先了解下AccessController類.它在java.security包下:
jdk_api_1.6對此類的使用目的說明如下:
[quote]
AccessController 類用於與訪問控制相關的操作和決定。

更確切地說,AccessController 類用於以下三個目的:

基於當前生效的安全策略決定是允許還是拒絕對關鍵系統資源的訪問

將代碼標記爲享有“特權”,從而影響後續訪問決定,以及

獲取當前調用上下文的“快照”,這樣便可以相對於已保存的上下文作出其他上下文的訪問控制決定。
[/quote]
如類名所示,AccessController是用來做訪問權限控制的.
[quote]
可以將調用方標記爲享有“特權”(請參閱 doPrivileged 及下文)。在做訪問控制決定時,如果遇到通過調用不帶上下文參數(請參閱下文,以獲取關於上下文參數的信息)的 doPrivileged 標記爲“特權”的調用方,則 checkPermission 方法將停止檢查。如果該調用方的域具有指定的權限,則不進行進一步檢查,並且 checkPermission 正常返回,指示允許所請求的訪問。如果該域不具有指定的權限,則通常拋出異常。

“特權”功能的標準用法如下所示。如果不需要從“特權”塊返回值,則使用以下代碼:
[/quote]

somemethod() {
...normal code here...
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
// privileged code goes here, for example:
System.loadLibrary("awt");
return null; // nothing to return
}
});
...normal code here...
}

關於AccessController的具體詳細使用的話,在此我也暫時無法分析清楚.
下面再分析下返回的標籤類加載器.
return new StandardClassLoader(array);
這個類是在org.apache.catalina.loader包下.
整個類如下:

public class StandardClassLoader extends URLClassLoader implements StandardClassLoaderMBean{
public StandardClassLoader(URL repositories[]){
super(repositories);
}
public StandardClassLoader(URL repositories[],ClassLoader parent){
super(repositories,parent);
}

}

上面的類中,繼承的類URLClassLoader在java.net包中.
而StandardClassLoaderMBean則是一個標記類如下:

package org.apache.catalina.loader;

/**
* MBean interface for StandardClassLoader, to allow JMX remote management.
*
* @author Remy Maucherat
* @version $Id: StandardClassLoaderMBean.java 988225 2010-08-23 17:38:41Z markt $
*/
public interface StandardClassLoaderMBean {
// Marker interface
}

下面我們來重點了解下URLClassLoader類.
下面是JDK_API_1.6的文檔說明:
[quote]
public class URLClassLoader
extends SecureClassLoader

該類加載器用於從指向 JAR 文件和目錄的 URL 的搜索路徑加載類和資源。這裏假定任何以 '/' 結束的 URL 都是指向目錄的。如果不是以該字符結束,則認爲該 URL 指向一個將根據需要打開的 JAR 文件。

創建 URLClassLoader 實例的 AccessControlContext 線程將在後續加載類和資源時使用。

爲加載的類默認授予只能訪問 URLClassLoader 創建時指定的 URL 的權限。
[/quote]

下面是關於參數爲URL[] urls的構造器的文檔說明:
[quote]
URLClassLoader

public URLClassLoader(URL[] urls)

使用默認的委託父 ClassLoader 爲指定的 URL 構造一個新 URLClassLoader。首先在父類加載器中搜索 URL,然後按照爲類和資源指定的順序搜索 URL。這裏假定任何以 '/' 結束的 URL 都是指向目錄的。如果不是以該字符結束,則認爲該 URL 指向一個將根據需要下載和打開的 JAR 文件。

如果有安全管理器,該方法首先調用安全管理器的 checkCreateClassLoader 方法以確保允許創建類加載器。

參數:
urls - 從其位置加載類和資源的 URL
拋出:
SecurityException - 如果安全管理器存在並且其 checkCreateClassLoader 方法不允許創建類加載器。
另請參見:
SecurityManager.checkCreateClassLoader()

[/quote]
查看源代碼如下:

public URLClassLoader(URL[] urls) {
super();
// this is to make the stack depth consistent with 1.1
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkCreateClassLoader();
}
ucp = new URLClassPath(urls);
acc = AccessController.getContext();
}

關於這個類加載器的創建過程就暫時分析到這裏,下一往篇博文中將介紹下面提到的東西 .
上面main方法中關於command的if else 一堆.我們先來關注,正常情況下的啓動過程:

else if (command.equals("start")) {
daemon.setAwait(true);
daemon.load(args);
daemon.start();
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章