tomcat 启动流程

上一篇文章分析了通过startup.bat启动Tomcat相当于执行如下代码,即运行Bootstrap start


start "Tomcat" "C:\Program Files\Java\jdk1.7.0_51\bin\java"
-Djava.util.logging.config.file="D:\Program Files\apache-tomcat-8.0.3\conf\logging.properties"
-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
-Djava.endorsed.dirs="D:\Program Files\apache-tomcat-8.0.3\endorsed"
-classpath "D:\Program Files\apache-tomcat-8.0.3\bin\bootstrap.jar;D:\Program Files\apache-tomcat-8.0.3\bin\tomcat-juli.jar"
-Dcatalina.base="D:\Program Files\apache-tomcat-8.0.3"
-Dcatalina.home="D:\Program Files\apache-tomcat-8.0.3"
-Djava.io.tmpdir="D:\Program Files\apache-tomcat-8.0.3\temp"
org.apache.catalina.startup.Bootstrap start
今天来看看Bootstrap的执行流程

1. 执行static块

为什么没有执行main方法,而先执行static块呢?

原来一个类的运行,JVM做会以下几件事情:类装载、链接、初始化、实例化,而初始化阶段做的事情是初始化静态变量和执行静态方法等

static块的作用是设置catalinaBaseFile、catalinaHomeFile

static块:


static {
        // Will always be non-null
        //System.getProperty("user.dir"),获取当前目录
        //由于是在$CATALINA_HOME\bin下运行的Bootstrap,所以userDir为$CATALINA_HOME\bin
        String userDir = System.getProperty("user.dir");
 
        // Home first
        //Globals是存放全局常量的类
        //Globals.CATALINA_HOME_PROP = "catalina.home"
        //catalina.home在运行Bootstrap时已设置(Tomcat的根目录)
        String home = System.getProperty(Globals.CATALINA_HOME_PROP);
        File homeFile = null;
        
        //获取Tomcat的绝对路径
        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");
 
            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
        catalinaHomeFile = homeFile;
        System.setProperty(
                Globals.CATALINA_HOME_PROP, catalinaHomeFile.getPath());
 
        // Then base
        String base = System.getProperty(Globals.CATALINA_BASE_PROP);
        //设置catalinaBaseFile
        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());
    }
对比getAbsoluteFile()、getCanonicalFile()

getAbsoluteFile获取绝对文件(如果文件路径中包含.和..,不解析)

getCanonicalFile获取经典文件(如果文件路径中包含.和..,解析)

创建Test.java


import java.io.File;
import java.io.IOException;
 
 
public class Test {
    public static void main(String[] args) throws IOException {
        File f = new File("D://Program Files//apache-tomcat-8.0.3");
        File aFile = f.getAbsoluteFile();
        File bFile = f.getCanonicalFile();
        System.out.println("文件路径不包含.或..");
        System.out.println("getAbsoluteFile()--->" + aFile.toString());
        System.out.println("getCanonicalFile()--->" + bFile.toString());
        
        File f1 = new File("D://Program Files//apache-tomcat-8.0.3//..");
        File aFile1 = f1.getAbsoluteFile();
        File bFile1 = f1.getCanonicalFile();
        System.out.println("文件路径包含.或..");
        System.out.println("getAbsoluteFile()--->" + aFile1.toString());
        System.out.println("getCanonicalFile()--->" + bFile1.toString());
    }
}
输出:


文件路径不包含.或..
getAbsoluteFile()--->D:\Program Files\apache-tomcat-8.0.3
getCanonicalFile()--->D:\Program Files\apache-tomcat-8.0.3
文件路径包含.或..
getAbsoluteFile()--->D:\Program Files\apache-tomcat-8.0.3\..
getCanonicalFile()--->D:\Program Files

接写来将执行main函数(以下以首次运行Bootstrap start进行解读)

2. 执行main函数

main函数:


public static void main(String args[]) {
 
        if (daemon == null) {
            // Don't set daemon until init() has completed
    //***2.1***
            Bootstrap bootstrap = new Bootstrap();
            try {
    //***2.2***
                bootstrap.init();
            } 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);
        }
            
    //***2.3***
        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")) {
    //***2.4***
                daemon.setAwait(true);
    //***2.5***
                daemon.load(args);
    //***2.6***
                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) {
            // Unwrap the Exception for clearer error reporting
            if (t instanceof InvocationTargetException &&
                    t.getCause() != null) {
                t = t.getCause();
            }
            handleThrowable(t);
            t.printStackTrace();
            System.exit(1);
        }
 
 }
2.1 创建Bootstrap

2.2 调用Bootstrap.init()

init方法:


public void init() throws Exception {
        
    //创建commonLoader、catalinaLoader、sharedLoader
        initClassLoaders();
        
    //为当前线程设置ClassLoader
        Thread.currentThread().setContextClassLoader(catalinaLoader);
        
    //设置SecurityClassLoad。具体作用还不清楚。。。
        SecurityClassLoad.securityClassLoad(catalinaLoader);
 
        // Load our startup class and call its process() method
        if (log.isDebugEnabled())
            log.debug("Loading startup class");
    //通过反射实例化Catalina
        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;
        //通过反射设置Catalina的parentClassLoader
        Method method =
            startupInstance.getClass().getMethod(methodName, paramTypes);
        method.invoke(startupInstance, paramValues);
        
    //将实例化的Catalina赋值给catalinaDaemon
        catalinaDaemon = startupInstance;
 
   }
initClassLoaders方法:


private void initClassLoaders() {
        try {
            //创建commonLoader
            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、sharedLoader
            catalinaLoader = createClassLoader("server", commonLoader);
            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 {
    //CatalinaProperties解析$CATALINA_HOME\conf\catalina.properties,
    //并将catalina.properties内的属性存为系统属性
    //catalina.properties内common.loader="${catalina.base}/lib",
    //"${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
        //读取common.loader
        String value = CatalinaProperties.getProperty(name + ".loader");
        if ((value == null) || (value.equals("")))
            return parent;
    //将${catalina.base},${catalina.home}替换为Tomcat的绝对路径
        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));
            }
        }
        
    //ClassLoaderFactory依据repositories的内容创建ClassLoader
        return ClassLoaderFactory.createClassLoader(repositories, parent);
    }
ClassLoaderFactory依据repositories的内容创建ClassLoader时,repositories包含的四个值均是$CATALINA_HOME\lib这个路径。那么创建的ClassLoader会不会有重复的jar呢?

查看ClassLoaderFactory.createClassLoader方法,即可找到答案


public static ClassLoader createClassLoader(List<Repository> repositories,
                                                final ClassLoader parent)
        throws Exception {
 
        if (log.isDebugEnabled())
            log.debug("Creating new class loader");
 
        // Construct the "class path" for this class loader
        Set<URL> set = new LinkedHashSet<>();
 
        if (repositories != null) {
            for (Repository repository : repositories)  {
                if (repository.getType() == RepositoryType.URL) {
                    URL url = new URL(repository.getLocation());
                    if (log.isDebugEnabled())
                        log.debug("  Including URL " + url);
                    set.add(url);
                } else if (repository.getType() == RepositoryType.DIR) {
                    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);
                } else if (repository.getType() == RepositoryType.JAR) {
                    File file=new File(repository.getLocation());
                    file = file.getCanonicalFile();
                    if (!validateFile(file, RepositoryType.JAR)) {
                        continue;
                    }
                    URL url = file.toURI().toURL();
                    if (log.isDebugEnabled())
                        log.debug("  Including jar file " + url);
                    set.add(url);
                } else if (repository.getType() == RepositoryType.GLOB) {
                    File directory=new File(repository.getLocation());
                    directory = directory.getCanonicalFile();
                    if (!validateFile(directory, RepositoryType.GLOB)) {
                        continue;
                    }
                    if (log.isDebugEnabled())
                        log.debug("  Including directory glob "
                            + directory.getAbsolutePath());
                    String filenames[] = directory.list();
                    for (int j = 0; j < filenames.length; j++) {
                        String filename = filenames[j].toLowerCase(Locale.ENGLISH);
                        if (!filename.endsWith(".jar"))
                            continue;
                        File file = new File(directory, filenames[j]);
                        file = file.getCanonicalFile();
                        if (!validateFile(file, RepositoryType.JAR)) {
                            continue;
                        }
                        if (log.isDebugEnabled())
                            log.debug("    Including glob jar file "
                                + file.getAbsolutePath());
                        URL url = file.toURI().toURL();
                        set.add(url);
                    }
                }
            }
        }
ClassLoaderFactory在遍历repositories时,将jar文件的URL放在LinkedHashSet里,而LinkedHashSet里不会添加重复的数据。因此,创建的ClassLoader不会有重复的jar

2.3 解析参数

2.4 调用Bootstrap.setAwait(true)

Bootstrap.setAwait(true)内部通过反射,设置Catalina的await属性(默认为false)为true

启动Catalina过程中,当Catalina将Tomcat的所有组件启动之后,会检查await属性,如果为true,会调用Catalina.await(),而Catalina.await()又会调用其内部的Server的await()


if (await) {
            await();
            stop();
        }
public void await() {
 
        getServer().await();
 
    }
Server.await()包含一个while循环,此循环用于监听指定socket端口(默认为8005)的连接,当某个连接传入的参数为”SHUTDOWN”(默认为”SHUTDOWN”)时,终止此while循环(端口号和终止while循环的参数,在server.xml的Server标签设置)

Server.await()用来维持Bootstrap的main方法(main thread)处于运行状态,而线程池中监听http请求的线程是守护线程(daemon thread)

当Tomcat的指定端口接收到关闭命令时,Server.await()内的while循环终止,然后Catalina会调用stop()方法,关闭Tomcat的所有组件,最终Bootstrap的main thread终止,Tomcat关闭

2.5 调用Bootstrap.load(args)

Bootstrap.load(args)内部通过反射调用Catalina.load(args),Catalina将利用Digest(Digest详解)解析server.xml,创建相应组件的实例,之后调用Server.init(),Server初始化时,又会调用其内部的Service的init方法,即调用某一组件的init方法时,将触发其子组件的init方法

执行Bootstrap.load(args)将触发的动作

2.6 调用Bootstrap.start()

执行Bootstrap.start()将触发的动作

 

 

 Bootstrap   
    private Object catalinaDaemon = Catalina;
    ClassLoader commonLoader = URLClassloader;
    ClassLoader catalinaLoader = URLClassloader;
    ClassLoader sharedLoader = URLClassloader;
    init()
       initClassLoaders();
       -Djava.security.manager
    daemon = bootstrap   
       public void org.apache.catalina.startup.Catalina.setParentClassLoader(java.lang.ClassLoader)
       
       
       
    org.apache.tomcat.util.digester.Digester
    
    
    getServer().init();
    
    //before_init resister event to listener
    setStateInternal(LifecycleState.INITIALIZING, null, false);
    //jmxbeanServer
    initInternal();
    ///after_init resister event to listener
    setStateInternal(LifecycleState.INITIALIZED, null, false);


带注释的 Bootstrap.java文件下载地址:

http://download.csdn.net/detail/flyliuweisky547/7179505
--------------------- 
作者:flyliuweisky547 
来源:CSDN 
原文:https://blog.csdn.net/flyliuweisky547/article/details/23464245 
版权声明:本文为博主原创文章,转载请附上博文链接!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值