tomcat8源码阅读(二)--启动一

一,前言

在本文中,我将主要介绍tomcat的启动阶段,包括main方法的运行,配置文件的加载,对象的实例化关联等。

二,Bootstrap的mian

当我们调试tomcat的时候,是从Bootstrap的main方法开始启动的。那这个main方法主要做了哪些工作,我们看看内部实现。

Bootstrap    
public static void main(String args[]) {
     Bootstrap bootstrap = new Bootstrap();
     //初始化
     bootstrap.init();
     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 other ...                 
    } catch (Throwable t) {
        ...//异常
        System.exit(1);
    }

}

代码很简单,创建一个Bootstrap,然后初始化init,最后调用start方法启动。

三,init方法

那么初始化方法做了什么呢?我们接下来看看:

Bootstrap
public void init() throws Exception {
    //初始化类加载器
    initClassLoaders();
    //设置当前上下文类加载器为catalinaLoader
    Thread.currentThread().setContextClassLoader(catalinaLoader);

    ...//略
    Class<?> startupClass =
        catalinaLoader.loadClass
        ("org.apache.catalina.startup.Catalina");
    //加载类Catalina,并实例化
    Object startupInstance = startupClass.newInstance();
    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);
    //调用Catalina的setParentClassLoader方法设置父类加载器为catalinaLoader
    method.invoke(startupInstance, paramValues);

    catalinaDaemon = startupInstance;
}

初始化做了2件事,第一件事是初始化类加载器,并设置了线程上下文类加载器。第二件事是初始化Catalina,并设置它的父类加载器为当前线程上下文类加载器。

3.1,初始化类加载器

接下来我们就看看类加载器是如何初始化的。

Bootstrap
private void initClassLoaders() {
    try {
        commonLoader = createClassLoader("common", null);
        if( commonLoader == null ) {
            commonLoader=this.getClass().getClassLoader();
        }
        catalinaLoader = createClassLoader("server", commonLoader);
        sharedLoader = createClassLoader("shared", commonLoader);
    } catch (Throwable t) {
        //....异常
        System.exit(1);
    }
}

上面代码创建了3个类加载器,至于这3个的用处,以后再说,我们看是如何实现的。

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);
}

我们看上面代码第一处,从属性文件中获取值。
我们打开conf/catalina.properties文件,发现

catalina.properties
common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
server.loader=
shared.loader=

只有common.loader才有值,表示加载的是tomcatlib目录下的jar包。由于server.loader和shared.loader为空,catalinaLoader和sharedLoader都会引用的commonLoader。
最后我们看具体的创建过程,即ClassLoaderFactory.createClassLoader(repositories, parent)方法。

    public static ClassLoader createClassLoader(List<Repository> repositories,
                                            final ClassLoader parent)
    throws Exception {
    Set<URL> set = new LinkedHashSet<>();
    if (repositories != null) {
        for (Repository repository : repositories)  {
            ...//略去其他类型
            File directory = new File(repository.getLocation());
            directory = directory.getCanonicalFile();
            if (!validateFile(directory, RepositoryType.DIR)) {
                continue;
            }
            URL url = directory.toURI().toURL();
            if (log.isDebugEnabled())
                log.debug("  Including directory " + url);
            set.add(url);
        }
    }

    // Construct the class loader itself
    final URL[] array = set.toArray(new URL[set.size()]);

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

最后我们从代码中可以看到,生成了一个URLClassLoader加载器。记得tomcat6的时候这里生成的StandardClassLoader,看来StandardClassLoader没啥作用,在后面的tomcat中移除了。

3.2,初始化Catalina

初始化Catalina用反射即可,不多说了。

四,start启动

初始化完成之后,就可以启动了。我们看看启动start方法。

Bootstrap
//设置等待标志,为关闭做准备,暂时不关注
daemon.setAwait(true);
//加载参数,无参数,暂时不关注
daemon.load(args);
//启动
daemon.start();

public void start()
    throws Exception {
    if( catalinaDaemon==null ) init();
    Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
    method.invoke(catalinaDaemon, (Object [])null);
}

start方法很简单,利用反射调用了catalina的start方法。
Catalina的start方法做了哪些事情呢?我们下文继续。