您的位置:首页 > 运维架构 > Tomcat

Tomcat源码阅读之Bootstrap启动流程与classLoader设计

2014-02-27 12:09 543 查看
嗯,以前大体上看过jetty6.0的源码,算是对java EE应用容器有了一定的了解,但是在实际的线上环境中应用的最多的应该还是tomcat,而且据说性能方面tomcat也略好一些。。。那么就趁着现在还比较闲就看看它的源码吧。。。

首先在实际开始之前先来说说tomcat对ClassLoader方面的处理。。。当然这部分是参考了网络上其他的人的博客。。。

一般情况下java应用程序的classLoader父子关系大概如下:



具体他们之间的关系以及作用我前面的博客都有说过。。。 那么作为应用容器,因为可能要同时容纳多个应用,为了实现各个应用之间的隔离,那么就需要由专门的classLoader来加载个个应用自己的代码,这部分jetty的处理方式是为每一个web应用程序都创建一个自己的classLoader,这个web应用程序需要用到的所有代码都有这个classLoader来处理。。。其实还蛮简单的。。。但是有一个缺点就是一些公有的代码每个web应用程序都会重复加载,会造成一些浪费吧。。。。例如defaultservlet啥的。。。但是优点就是简单。。。

在这方面,tomcat的设计就稍微复杂了一点点。。。父子关系如下图:



这里可以每个web应用程序也有自己专属的classLoader,也就是最下层的AppClassLoader。。。

这里可以看到总的来说classLoader分成了两条线,左边那条线是tomcat服务器用的,右边的则是web应用程序用的。。。。。嗯,其实也不复杂。。。。

好了,上面的内容算是对tomcat的classLoader有了比较简单的了解。。那么接下来来看看整个tomcat服务器的启动类Bootstrap类型是怎么工作的吧。。。。

先来看看它的几个静态属性的定义: private static Bootstrap daemon = null; //当前类型的一个引用

//这两个其实一般都被赋值为tomcat的根路径
private static final File catalinaBaseFile;
private static final File catalinaHomeFile;

private static final Pattern PATH_PATTERN = Pattern.compile("(\".*?\")|(([^,])*)"); //正则表达式验证
前面其实就是一个本身类型对象的一个引用,后面两个file对象其实一般情况下就是tomcat服务器的根路径,如果在启动的时候没有特别指定的话。。。。

接下来来看看一段静态代码块: static { //这里主要是进行一些路径的初始化
// Will always be non-null
String userDir = System.getProperty("user.dir"); //当前tomcat的用户路径,说白了就是应用程序起点路径

// Home first
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) {
// First fall-back. See if current directory is a bin directory
// in a normal Tomcat install
File bootstrapJar = new File(userDir, "bootstrap.jar"); //启动jar包,在eclipse里面用源码运行的时候没有它

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(); //将路径保存到homeFile
} 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());
}
其实这里的处理基本上就是设置一些路径方面的类容,也就是在类型加载的时候这部分就设置好了。。。

好啦,。接下来来看看几个属性的定义: private Object catalinaDaemon = null; //当前tomcat的后台,org.apache.catalina.startup.Catalinad对象,它才是用于负责具体的server的启动

//3个层级的classLoader ,commonloader是下面两个loader的父loader,嗯,他们3个甚至可能引用的都是同一个classLoader
protected ClassLoader commonLoader = null; //tomcat与app都能见
protected ClassLoader catalinaLoader = null; //只有tomcat能见
protected ClassLoader sharedLoader = null; //只有app们可以看见,tomcat看不到
嗯,他们具体是干嘛的应该很清楚了吧。。。注释应该就说的比较清楚了。。。

嗯,接下来来看整个tomcat的入口吧,main函数: public static void main(String args[]) {

if (daemon == null) {
// Don't set daemon until init() has completed
Bootstrap bootstrap = new Bootstrap(); //这里创建当前Bootstarp类型的对象
try {
bootstrap.init(); //初始化,其实这里主要是创建org.apache.catalina.startup.Catalina对象并调用setParentClassLoader设置classLoader,用的是shareLoader
} 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) { //这里有可能是其他的参数,但是默认命令是start
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(); //启动当前bootstrap对象,其实主要是调用前面生成的org.apache.catalina.startup.Catalina的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类型的对象,如果只是启动的话,这里首先载入启动时候的参数,然后在执行start方法,那么接下来来看看start方法: //这里就是真正的启动tomcat
public void start()
throws Exception {
if( catalinaDaemon==null ) init(); //初始化catalinaDaemon,其实主要是初始化org.apache.catalina.startup.Catalina对象

Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
//调用org.apache.catalina.startup.Catalina对象的start方法
method.invoke(catalinaDaemon, (Object [])null);
}
这个好像没啥意思。。。这里说白了就是先创建和初始化org.apache.catalina.startup.Catalina对象,接着在调用它的start方法。。。那么来看看这个init方法都做了啥吧: //初始化当前的tomcat后台,主要是创建org.apache.catalina.startup.Catalina对象,并且设置它的classLoader为catalinaLoader
public void init() throws Exception {

initClassLoaders(); //先初始化classLoader

Thread.currentThread().setContextClassLoader(catalinaLoader); //设置当前线程classLoader

SecurityClassLoad.securityClassLoad(catalinaLoader); //安全classLoader?

// 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"); //获取org.apache.catalina.startup.Catalina类型
Object startupInstance = startupClass.newInstance(); //创建org.apache.catalina.startup.Catalina对象

// 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; //传进这个classLoader
Method method =
startupInstance.getClass().getMethod(methodName, paramTypes);
method.invoke(startupInstance, paramValues); //调用刚刚创建的org.apache.catalina.startup.Catalina对象的setParentClassLoader设置classLoader,shareloader

catalinaDaemon = startupInstance; //将这个启动的实例保存起来

}
嗯,首先初始化了classLoader,也就是上面提到的那三个classLoader,然后这里可以看到将当前的线程classLoader设置成了catalinaLoader,也就是专属于tomcat使用的。。。接着创建了org.apache.catalina.startup.Catalina对象,然后调用了它的setParentClassLoader方法,这里可以看到设置的classLoader是sharedLoader,前面已经提到它是属于web程序才能访问的。。。那么这里为啥分别这样设置这两个classLoader基本上猜也能猜出来原因了吧。。。哈哈。。。。。。

好啦,剩下来的事情就是调用org.apache.catalina.startup.Catalina对象的start方法来启动服务器了。。。

下一篇文章再来分析。。。。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐