欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Tomcat学习之二,认识Bootstrap类

程序员文章站 2022-06-20 16:18:24
...
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的超类.

引用

Throwable 类是 Java 语言中所有错误或异常的超类,只有当对象是此类(或其子类之一)的实例时,才能通过 Java 虚拟机或者 Java throw 语句抛出。类似地,只有此类或其子类之一才可以是 catch 子句中的参数类型。


我们看下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值为:
引用

common.loader=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar


 value = replace(value)

上面这一句代码执行过后,value值,在我的系统中变成了如下:
引用

"/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"

上面出现的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数组中的内容如下所示:

引用

[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]


现在我们来仔细看下上面那个复杂的返回语句:
首先了解下AccessController类.它在java.security包下:
jdk_api_1.6对此类的使用目的说明如下:
引用

AccessController 类用于与访问控制相关的操作和决定。

更确切地说,AccessController 类用于以下三个目的:

    基于当前生效的安全策略决定是允许还是拒绝对关键系统资源的访问

    将代码标记为享有“特权”,从而影响后续访问决定,以及

    获取当前调用上下文的“快照”,这样便可以相对于已保存的上下文作出其他上下文的访问控制决定。

如类名所示,AccessController是用来做访问权限控制的.
引用

可以将调用方标记为享有“特权”(请参阅 doPrivileged 及下文)。在做访问控制决定时,如果遇到通过调用不带上下文参数(请参阅下文,以获取关于上下文参数的信息)的 doPrivileged 标记为“特权”的调用方,则 checkPermission 方法将停止检查。如果该调用方的域具有指定的权限,则不进行进一步检查,并且 checkPermission 正常返回,指示允许所请求的访问。如果该域不具有指定的权限,则通常抛出异常。

“特权”功能的标准用法如下所示。如果不需要从“特权”块返回值,则使用以下代码:

   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的文档说明:
引用

public class URLClassLoader
extends SecureClassLoader

该类加载器用于从指向 JAR 文件和目录的 URL 的搜索路径加载类和资源。这里假定任何以 '/' 结束的 URL 都是指向目录的。如果不是以该字符结束,则认为该 URL 指向一个将根据需要打开的 JAR 文件。

创建 URLClassLoader 实例的 AccessControlContext 线程将在后续加载类和资源时使用。

为加载的类默认授予只能访问 URLClassLoader 创建时指定的 URL 的权限。


下面是关于参数为URL[] urls的构造器的文档说明:
引用

URLClassLoader

public URLClassLoader(URL[] urls)

    使用默认的委托父 ClassLoader 为指定的 URL 构造一个新 URLClassLoader。首先在父类加载器中搜索 URL,然后按照为类和资源指定的顺序搜索 URL。这里假定任何以 '/' 结束的 URL 都是指向目录的。如果不是以该字符结束,则认为该 URL 指向一个将根据需要下载和打开的 JAR 文件。

    如果有安全管理器,该方法首先调用安全管理器的 checkCreateClassLoader 方法以确保允许创建类加载器。

    参数:
        urls - 从其位置加载类和资源的 URL
    抛出:
        SecurityException - 如果安全管理器存在并且其 checkCreateClassLoader 方法不允许创建类加载器。
    另请参见:
        SecurityManager.checkCreateClassLoader()


查看源代码如下:
    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();
            }