美文网首页我爱编程
Tomcat源码分析 -- Tomcat的启动过程(二)

Tomcat源码分析 -- Tomcat的启动过程(二)

作者: w1992wishes | 来源:发表于2018-02-23 13:56 被阅读52次

    本篇结构:

    • 前言
    • 总览一下main方法
    • init
    • setAwait
    • load
    • start

    一、前言

    上一篇中介绍了startup.bat和catalina.bat脚本。了解到日常双击startup.bat启动tomcat,其实是来到catalina.bat脚本中,由catalina.bat脚本去执行org.apache.catalina.startup.Bootstrap这个类中的main方法。

    这里就来看看Bootstrap类的main方法做了些什么。

    二、总览一下main方法

    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;
        } 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) {
                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) {
            // 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对象,并调用init方法,然后赋值给daemon变量,当然如果daemon已经不是空了,说明已经初始化过了,就将daemon.catalinaLoader直接设置到当前线程(daemon.catalinaLoader是用来加载tomcat内部服务器所需类的类加载器)。

    下部分是根据传递进来的参数决定走哪一步,当双击startup.bat时,传进来的是start,所以会来到这段:

    else if (command.equals("start")) {
        daemon.setAwait(true);
        daemon.load(args);
        daemon.start();
    }
    

    这里主要是调用三个方法,setAwait,load和start。

    所以对于Bootstrap重要关注的就是init,setAwait,load和start这四个方法。

    三、init

    public void init() throws Exception {
    
        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.getConstructor().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;
    
    }
    

    init方法调用initClassLoaders初始化类加载器,然后将初始化好的catalinaLoader设置到当前线程,接着通过反射调用org.apache.catalina.startup.Catalina类的setParentClassLoader,将sharedLoader传入。

    3.1、首先看initClassLoaders方法:

    private void initClassLoaders() {
        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,catalinaLoader,sharedLoader并建立他们之间的关系,catalinaLoader和sharedLoader的parent是commonLoader。

    这三个类加载器都是通过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<>();
    
        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);
    }
    

    createClassLoader首先回去读取CatalinaProperties中的common.loader,server.loader,shared.loader三个属性,进入CatalinaProperties类中会发现这三个属性来自conf/catalina.properties文件。

    接着往下createClassLoader会将common.loader,server.loader,shared.loader三个属性中的值获取然后解析成Repository,然后交给ClassLoaderFactory.createClassLoader方法去创建类加载器,最后可以实现三个不同的类加载器分别加载不同目录下的类。

    当然要说清楚的是,默认情况下,catalina.properties中server.loader,shared.loader并没有配置值,三个类加载是同一个,默认加载{catalina.home}/lib目录下的类和jar包。

    如果想配置对所有web应用都可见但对tomcat内部服务器不可见的类,此时应该在catalina.properties文件中的shared.loader下进行配置。

    Thread.currentThread().setContextClassLoader(catalinaLoader)将catalinaLoader设置为Tomcat主线程的线程上下文类加载器。

    3.2、SecurityClassLoad.securityClassLoad(catalinaLoader)

    SecurityClassLoad.securityClassLoad用于线程安全的加载tomcat容器所需的class。

    当然,要使这个方法真正起作用,需要启动tomcat安全管理器,由代码可知:

    public static void securityClassLoad(ClassLoader loader) throws Exception {
        securityClassLoad(loader, true);
    }
    
    
    static void securityClassLoad(ClassLoader loader, boolean requireSecurityManager)
            throws Exception {
    
        if (requireSecurityManager && System.getSecurityManager() == null) {
            return;
        }
    
        loadCorePackage(loader);
        loadCoyotePackage(loader);
        loadLoaderPackage(loader);
        loadRealmPackage(loader);
        loadServletsPackage(loader);
        loadSessionPackage(loader);
        loadUtilPackage(loader);
        loadJavaxPackage(loader);
        loadConnectorPackage(loader);
        loadTomcatPackage(loader);
    }
    

    如果没启用安全管理器,System.getSecurityManager()=null,直接return。

    可以通过命令行的方式启功安全管理器:
    catalina.bat run -security或者startup.bat -security

    一旦启动了安全管理器,就会根据conf/catalina.policy文件定义的提供默认的安全策略,securityClassLoad方法中System.getSecurityManager()不再等于null,于是就会去执行一系列加载方法,将tomcat的class加载进来。

    想了解to吗tomcat的安全策略,可以参考下这篇博文:

    你很少使用的安全管理SecurityManager

    3.3、通过反射实例化catalina

    Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
    Object startupInstance = startupClass.getConstructor().newInstance();
    
    // Set the shared extensions class loader
    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,然后反射调用setParentClassLoader将sharedLoader传入到catalina实例中。

    为什么不直接通过Bootstrap类直接启动tomcat,而是通过反射生成Catalina实例启动?

    再查看tomcat目录结构时应该发现,Bootstrap并不在$CATALINA_HOME/lib目录下,而是在$CATALINA_HOME/bin目录中,Bootstrap和Catalina松耦合(通过反射调用Catalina),它直接依赖JRE运行并为Tomcat应用服务器创建共享类加载器,用于构造Catalina和整个tomcat服务器。实现了启动入口和核心环境的解耦,简化了启动。

    四、setAwait

    来看setAwait:

    public void setAwait(boolean await)
            throws Exception {
    
        Class<?> paramTypes[] = new Class[1];
        paramTypes[0] = Boolean.TYPE;
        Object paramValues[] = new Object[1];
        paramValues[0] = Boolean.valueOf(await);
        Method method =
            catalinaDaemon.getClass().getMethod("setAwait", paramTypes);
        method.invoke(catalinaDaemon, paramValues);
    
    }
    

    通过反射调用catalina的setAwait方法,其设置的值是留给后面用的,当Catalina将Tomcat的所有组件启动之后,会检查await属性,如果为true,会调用Catalina.await(),而Catalina.await()又会调用其内部的Server的await()。

    public void start() {
    
        ...
            
        if (await) {
            await();
            stop();
        }
    }
    

    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关闭。

    五、load

    private void load(String[] arguments)
            throws Exception {
    
        // Call the load() method
        String methodName = "load";
        Object param[];
        Class<?> paramTypes[];
        if (arguments==null || arguments.length==0) {
            paramTypes = null;
            param = null;
        } else {
            paramTypes = new Class[1];
            paramTypes[0] = arguments.getClass();
            param = new Object[1];
            param[0] = arguments;
        }
        Method method =
            catalinaDaemon.getClass().getMethod(methodName, paramTypes);
        if (log.isDebugEnabled())
            log.debug("Calling startup class " + method);
        method.invoke(catalinaDaemon, param);
    
    }
    

    该方法通过反射调用catalina的load方法。

    catalina的load方法首先初始化目录(initDirs)和初始化命名服务(initNaming),然后是createStartDigester(要了解这个方法,应该先了解一下Digester,它是apache的一个开源组件,通过它可以很方便的从xml文件生成java对象),该方法初始化Digester,为Xml的标签即解析模式增加处理规则rule。

    关于Digester,可以参考这篇博文:

    利用Digester解析xml文件

    来看createStartDigester方法:

    protected Digester createStartDigester() {
        long t1=System.currentTimeMillis();
        // Initialize the digester
        Digester digester = new Digester();
        digester.setValidating(false);
        digester.setRulesValidation(true);
        Map<Class<?>, List<String>> fakeAttributes = new HashMap<>();
        List<String> attrs = new ArrayList<>();
        attrs.add("className");
        fakeAttributes.put(Object.class, attrs);
        digester.setFakeAttributes(fakeAttributes);
        digester.setUseContextClassLoader(true);
    
        // Configure the actions we will be using
        digester.addObjectCreate("Server",
                                 "org.apache.catalina.core.StandardServer",
                                 "className");
        digester.addSetProperties("Server");
        digester.addSetNext("Server",
                            "setServer",
                            "org.apache.catalina.Server");
    
        digester.addObjectCreate("Server/GlobalNamingResources",
                                 "org.apache.catalina.deploy.NamingResourcesImpl");
        digester.addSetProperties("Server/GlobalNamingResources");
        digester.addSetNext("Server/GlobalNamingResources",
                            "setGlobalNamingResources",
                            "org.apache.catalina.deploy.NamingResourcesImpl");
    
        digester.addObjectCreate("Server/Listener",
                                 null, // MUST be specified in the element
                                 "className");
        digester.addSetProperties("Server/Listener");
        digester.addSetNext("Server/Listener",
                            "addLifecycleListener",
                            "org.apache.catalina.LifecycleListener");
    
        digester.addObjectCreate("Server/Service",
                                 "org.apache.catalina.core.StandardService",
                                 "className");
        digester.addSetProperties("Server/Service");
        digester.addSetNext("Server/Service",
                            "addService",
                            "org.apache.catalina.Service");
    
        digester.addObjectCreate("Server/Service/Listener",
                                 null, // MUST be specified in the element
                                 "className");
        digester.addSetProperties("Server/Service/Listener");
        digester.addSetNext("Server/Service/Listener",
                            "addLifecycleListener",
                            "org.apache.catalina.LifecycleListener");
    
        //Executor
        digester.addObjectCreate("Server/Service/Executor",
                         "org.apache.catalina.core.StandardThreadExecutor",
                         "className");
        digester.addSetProperties("Server/Service/Executor");
    
        digester.addSetNext("Server/Service/Executor",
                            "addExecutor",
                            "org.apache.catalina.Executor");
    
    
        digester.addRule("Server/Service/Connector",
                         new ConnectorCreateRule());
        digester.addRule("Server/Service/Connector", new SetAllPropertiesRule(
                new String[]{"executor", "sslImplementationName", "protocol"}));
        digester.addSetNext("Server/Service/Connector",
                            "addConnector",
                            "org.apache.catalina.connector.Connector");
    
        digester.addObjectCreate("Server/Service/Connector/SSLHostConfig",
                                 "org.apache.tomcat.util.net.SSLHostConfig");
        digester.addSetProperties("Server/Service/Connector/SSLHostConfig");
        digester.addSetNext("Server/Service/Connector/SSLHostConfig",
                "addSslHostConfig",
                "org.apache.tomcat.util.net.SSLHostConfig");
    
        digester.addRule("Server/Service/Connector/SSLHostConfig/Certificate",
                         new CertificateCreateRule());
        digester.addRule("Server/Service/Connector/SSLHostConfig/Certificate",
                         new SetAllPropertiesRule(new String[]{"type"}));
        digester.addSetNext("Server/Service/Connector/SSLHostConfig/Certificate",
                            "addCertificate",
                            "org.apache.tomcat.util.net.SSLHostConfigCertificate");
    
        digester.addObjectCreate("Server/Service/Connector/SSLHostConfig/OpenSSLConf",
                                 "org.apache.tomcat.util.net.openssl.OpenSSLConf");
        digester.addSetProperties("Server/Service/Connector/SSLHostConfig/OpenSSLConf");
        digester.addSetNext("Server/Service/Connector/SSLHostConfig/OpenSSLConf",
                            "setOpenSslConf",
                            "org.apache.tomcat.util.net.openssl.OpenSSLConf");
    
        digester.addObjectCreate("Server/Service/Connector/SSLHostConfig/OpenSSLConf/OpenSSLConfCmd",
                                 "org.apache.tomcat.util.net.openssl.OpenSSLConfCmd");
        digester.addSetProperties("Server/Service/Connector/SSLHostConfig/OpenSSLConf/OpenSSLConfCmd");
        digester.addSetNext("Server/Service/Connector/SSLHostConfig/OpenSSLConf/OpenSSLConfCmd",
                            "addCmd",
                            "org.apache.tomcat.util.net.openssl.OpenSSLConfCmd");
    
        digester.addObjectCreate("Server/Service/Connector/Listener",
                                 null, // MUST be specified in the element
                                 "className");
        digester.addSetProperties("Server/Service/Connector/Listener");
        digester.addSetNext("Server/Service/Connector/Listener",
                            "addLifecycleListener",
                            "org.apache.catalina.LifecycleListener");
    
        digester.addObjectCreate("Server/Service/Connector/UpgradeProtocol",
                                  null, // MUST be specified in the element
                                  "className");
        digester.addSetProperties("Server/Service/Connector/UpgradeProtocol");
        digester.addSetNext("Server/Service/Connector/UpgradeProtocol",
                            "addUpgradeProtocol",
                            "org.apache.coyote.UpgradeProtocol");
    
        // Add RuleSets for nested elements
        digester.addRuleSet(new NamingRuleSet("Server/GlobalNamingResources/"));
        digester.addRuleSet(new EngineRuleSet("Server/Service/"));
        digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"));
        digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Host/"));
        addClusterRuleSet(digester, "Server/Service/Engine/Host/Cluster/");
        digester.addRuleSet(new NamingRuleSet("Server/Service/Engine/Host/Context/"));
    
        // When the 'engine' is found, set the parentClassLoader.
        digester.addRule("Server/Service/Engine",
                         new SetParentClassLoaderRule(parentClassLoader));
        addClusterRuleSet(digester, "Server/Service/Engine/Cluster/");
    
        long t2=System.currentTimeMillis();
        if (log.isDebugEnabled()) {
            log.debug("Digester for server.xml created " + ( t2-t1 ));
        }
        return digester;
    
    }
    

    该方法初始化digester,创建一系列解析规则,然后在load方法中会调用:

    digester.parse(inputSource);
    

    可见digester解析的源是inputSource,而inputSource是来自于conf/server.xml:

    file = configFile();
    inputStream = new FileInputStream(file);
    inputSource = new InputSource(file.toURI().toURL().toString());
    

    configFile:

     protected File configFile() {
    
        //protected String configFile = "conf/server.xml";
        File file = new File(configFile);
        if (!file.isAbsolute()) {
            file = new File(Bootstrap.getCatalinaBase(), configFile);
        }
        return file;
    
    }
    

    这样便创建出了StandardServer对象,接着便调用getServer().init();

    init方法来自StandardServer的父类LifecycleBase:

    public final synchronized void init() throws LifecycleException {
        if (!state.equals(LifecycleState.NEW)) {
            invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
        }
    
        try {
            setStateInternal(LifecycleState.INITIALIZING, null, false);
            initInternal();
            setStateInternal(LifecycleState.INITIALIZED, null, false);
        } catch (Throwable t) {
            handleSubClassException(t, "lifecycleBase.initFail", toString());
        }
    }
    

    具体实现是在子类的initInternal方法中,在调用initInternal方法前后都会设置状态,LifecycleState.INITIALIZING代表正在初始化,LifecycleState.INITIALIZED表示初始化完成,相应会触发生命周期事件。

    在StandardServer的initInternal方法中会调用子组件Services的init方法,并依次传递下去,完成所有组件的init。

    可见catalina的load方法主要是根据conf/server.xml配置文件利用Digester创建服务器组件,然后调用Server的init方法,逐层次的实现所有组件的初始化。

    六、start

    最后看下start方法:

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

    同样也是通过反射调用catalina的start方法:

    public void start() {
    
        if (getServer() == null) {
            load();
        }
    
        if (getServer() == null) {
            log.fatal("Cannot start server. Server instance is not configured.");
            return;
        }
    
        long t1 = System.nanoTime();
    
        // Start the new server
        try {
            getServer().start();
        } catch (LifecycleException e) {
            log.fatal(sm.getString("catalina.serverStartFail"), e);
            try {
                getServer().destroy();
            } catch (LifecycleException e1) {
                log.debug("destroy() failed for failed Server ", e1);
            }
            return;
        }
    
        long t2 = System.nanoTime();
        if(log.isInfoEnabled()) {
            log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");
        }
    
        // Register shutdown hook
        if (useShutdownHook) {
            if (shutdownHook == null) {
                shutdownHook = new CatalinaShutdownHook();
            }
            Runtime.getRuntime().addShutdownHook(shutdownHook);
    
            // If JULI is being used, disable JULI's shutdown hook since
            // shutdown hooks run in parallel and log messages may be lost
            // if JULI's hook completes before the CatalinaShutdownHook()
            LogManager logManager = LogManager.getLogManager();
            if (logManager instanceof ClassLoaderLogManager) {
                ((ClassLoaderLogManager) logManager).setUseShutdownHook(
                        false);
            }
        }
    
        if (await) {
            await();
            stop();
        }
    }
    

    该方法主要触发StandardServer的start方法,StandardServer的start方法同init方法一样来自LifecycleBase,主要是改变生命周期的状态,同时触发相应的生命周期时间,具体的执行逻辑交由具体的子类startInternal方法实现:

    public final synchronized void start() throws LifecycleException {
    
        if (LifecycleState.STARTING_PREP.equals(state) || LifecycleState.STARTING.equals(state) ||
                LifecycleState.STARTED.equals(state)) {
            return;
        }
    
        if (state.equals(LifecycleState.NEW)) {
            init();
        } else if (state.equals(LifecycleState.FAILED)) {
            stop();
        } else if (!state.equals(LifecycleState.INITIALIZED) &&
                !state.equals(LifecycleState.STOPPED)) {
            invalidTransition(Lifecycle.BEFORE_START_EVENT);
        }
    
        try {
            setStateInternal(LifecycleState.STARTING_PREP, null, false);
            startInternal();
            if (state.equals(LifecycleState.FAILED)) {
                // This is a 'controlled' failure. The component put itself into the
                // FAILED state so call stop() to complete the clean-up.
                stop();
            } else if (!state.equals(LifecycleState.STARTING)) {
                // Shouldn't be necessary but acts as a check that sub-classes are
                // doing what they are supposed to.
                invalidTransition(Lifecycle.AFTER_START_EVENT);
            } else {
                setStateInternal(LifecycleState.STARTED, null, false);
            }
        } catch (Throwable t) {
            // This is an 'uncontrolled' failure so put the component into the
            // FAILED state and throw an exception.
            handleSubClassException(t, "lifecycleBase.startFail", toString());
        }
    }
    

    在StandardServer的startInternal方法中会调用子组件service的start方法,并依次调用其他组件的start方法。

    protected void startInternal() throws LifecycleException {
    
        fireLifecycleEvent(CONFIGURE_START_EVENT, null);
        setState(LifecycleState.STARTING);
    
        globalNamingResources.start();
    
        // Start our defined Services
        synchronized (servicesLock) {
            for (int i = 0; i < services.length; i++) {
                services[i].start();
            }
        }
    }
    

    所以同load方法很相似,start方法主要是实现各组件的start方法依次调用,可以用一张图来理解:

    还应该看到Catalina的start方法会使用前面的setAwait方法传递的值,为true时,会在8005端口监听,保证主线程一直在运行,直到收到SHUTDOWN命令。

    相关文章

      网友评论

        本文标题:Tomcat源码分析 -- Tomcat的启动过程(二)

        本文链接:https://www.haomeiwen.com/subject/cdkxxftx.html