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

Tomcat源码分析三:Tomcat启动加载过程(一)的源码解析

程序员文章站 2022-06-19 19:18:15
Tomcat启动加载过程(一)的源码解析 今天,我将分享用源码的方式讲解Tomcat启动的加载过程,关于Tomcat的架构请参阅《Tomcat源码分析二:先看看Tomcat的整体架构》一文。 先看看应用情况 在《Servlet与Tomcat运行示例》一文中,我详细的记录了Tomcat是如何启动一个S ......

tomcat启动加载过程(一)的源码解析

今天,我将分享用源码的方式讲解tomcat启动的加载过程,关于tomcat的架构请参阅《tomcat源码分析二:先看看tomcat的整体架构》一文。

先看看应用情况

在《servlet与tomcat运行示例》一文中,我详细的记录了tomcat是如何启动一个servlet的程序的步骤。其中,第6步骤是启动tomcat,也就是在windows系统上执行startup.bat, 在linux操作系统上执行startup.sh的脚本。那么,我们就从这个脚本出发,走进tomcat,看看它是如何启动的?这里,我们以startup.sh为例,windows端的startup.bat类似。

startup.sh的内容是什么?

我们先看看tomcat的启动脚本startup.sh的内容是什么,先看看其脚本内容(省略部分注释),如下:

#!/bin/sh

# -----------------------------------------------------------------------------
# start script for the catalina server
# -----------------------------------------------------------------------------

# better os/400 detection: see bugzilla 31132
os400=false
case "`uname`" in
os400*) os400=true;;
esac

# resolve links - $0 may be a softlink
prg="$0"

while [ -h "$prg" ] ; do
  ls=`ls -ld "$prg"`
  link=`expr "$ls" : '.*-> \(.*\)$'`
  if expr "$link" : '/.*' > /dev/null; then
    prg="$link"
  else
    prg=`dirname "$prg"`/"$link"
  fi
done

prgdir=`dirname "$prg"`
executable=catalina.sh

# check that target executable exists
if $os400; then
  # -x will only work on the os400 if the files are:
  # 1. owned by the user
  # 2. owned by the primary group of the user
  # this will not work if the user belongs in secondary groups
  eval
else
  if [ ! -x "$prgdir"/"$executable" ]; then
    echo "cannot find $prgdir/$executable"
    echo "the file is absent or does not have execute permission"
    echo "this file is needed to run this program"
    exit 1
  fi
fi

exec "$prgdir"/"$executable" start "$@"

提取其中主要的几句:

prgdir=`dirname "$prg"`
executable=catalina.sh
exec "$prgdir"/"$executable" start "$@"

简而概之,该脚本的执行内容为:调用catalina.sh脚本。下面,我们继续来看下catalina.sh脚本的内容

catalina.sh脚本

由于catalina.sh脚本内容比较多,这里提取一些重要的内容,然后解释其用途:

Tomcat源码分析三:Tomcat启动加载过程(一)的源码解析

Tomcat源码分析三:Tomcat启动加载过程(一)的源码解析

再简要的描述下在catalina.sh中作用:完成环境检查、环境初始化、参数初始化、启动操作步骤。注意一下上图中被绿色框出来的内容,可以看到其调用执行的是org.apache.catalina.startup.bootstrap类,并且传输过去的command指令为start。

回归java代码

bootstrap类进行了什么操作呢?

接下来,我们带着这几个问题来去探索一下bootstrap类:

  • bootstrap类在接收到start指令后要去干什么?
  • bootstrap类在启动过程中的职责是什么?

下面,我们带着上面的几个问题来具体的探讨一下tomcat的源码。先来看看bootstrap类的main方法:

    public static void main(string args[]) {

        synchronized (daemonlock) {
            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 {
                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();
                if (null == daemon.getserver()) {
                    system.exit(1);
                }
            } 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);
        }
    }

从这段代码中,可以看出,其主要实现了两个功能:

  • 初始化一个守护进程变量daemon
  • 加载catalina.sh传递过来的参数,解析catalina.sh传递过来的指令,并按照指令执行程序,控制守护进程daemon的启停等操作

bootstrap.init();有什么操作呢?

针对上面的两个功能,我们进入到 init()方法看下有什么操作,先看下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()的作用是初始化三个类加载器,代码如下:

    /**
     * daemon reference.
     */
    private object catalinadaemon = null;

    classloader commonloader = null;
    classloader catalinaloader = null;
    classloader sharedloader = null;

    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);
        }
    }
        
    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);
    }
//  catalina.properties
common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
  • commonloader: 根据common.loader属性的配置(通过代码catalinaproperties.getproperty(name + ".loader");读取:catalina.properties), 创建commonloader类加载器, 默认情况下顺序加载 ${catalina.base}/lib, ${catalina.base}/lib/.jar, ${catalina.home}/lib, ${catalina.home}/lib/.jar 四个目录下的class和jar.
  • catalinaloader: 根据server.loader属性的配置, 创建catalinaloader类加载器,其父类加载其为commonloader, 默认server.loader属性为空, 直接使用commonloader.
  • sharedloader:根据shared.loader属性配置,创建sharedloader类加载器,其父类加载其为commonloader, 默认shared.loader属性为空, 直接使用commonloader.

当执行完initclassloaders()方法之后,调用thread.currentthread().setcontextclassloader(catalinaloader);设置上下文类加载器为catalinaloader,从上面解析的情况看,其实设置的上下文类加载器为catalinaloader的父类commonloader。

securityclassload.securityclassload(catalinaloader) 的作用是如果有securitymanager,提前加载部分类。

之后,通过使用catalinaloader加载org.apache.catalina.startup.catalina类,创建实例catalina并利用反射调用方法setparentclassloader(),设置catalina实例的parentclassloader属性为sharedloader类加载器(也就是commonloader)。

最后,设置daemon为新创建的实例bootstrap。接下来,看一下main()方法下的指令处理。

传递过来的command指令是如何处理的呢?

我们观察一下main()方法的后半段,这里贴一下代码:

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();
            if (null == daemon.getserver()) {
                    system.exit(1);
            }
    } 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) {
    // ...... 省略
}

可以看到,其默认指令为start, 然后,其根据接收到的参数区分为startd、stopd、start、stop、configtest和其他6种指令情况。这里我们主要看一下start指令的执行逻辑。

  • daemon.setawait(true) :这句代码有什么含义呢,下面我们来具体的分析一下:
    /**
     * set flag.
     * @param await <code>true</code> if the daemon should block
     * @throws exception reflection error
     */
    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(true),主要目的是当启动完成后, 阻塞main线程,等待stop命令到来。 如果不设置daemon.setawait(true), 则main线程执行完之后就 直接退出了。

  • daemon.load(args)
    daemon.load(args);其实是最终执行的catalina.load(),在catalina.load()方法中,主要功能是首先初始化temp目录,然后再初始化naming的一些系统属性,然后获取server.xml配置文件, 创建digester实例, 开始解析server.xml的操作。
    /**
     * start a new server instance.
     */
    public void load() {

        if (loaded) {
            return;
        }
        loaded = true;

        long t1 = system.nanotime();

        initdirs();

        // before digester - it may be needed
        initnaming();

        // set configuration source
        configfileloader.setsource(new catalinabaseconfigurationsource(bootstrap.getcatalinabasefile(), getconfigfile()));
        file file = configfile();

        // create and execute our digester
        digester digester = createstartdigester();

        try (configurationsource.resource resource = configfileloader.getsource().getserverxml()) {
            inputstream inputstream = resource.getinputstream();
            inputsource inputsource = new inputsource(resource.geturi().tourl().tostring());
            inputsource.setbytestream(inputstream);
            digester.push(this);
            digester.parse(inputsource);
        } catch (exception e) {
            log.warn(sm.getstring("catalina.configfail", file.getabsolutepath()), e);
            if (file.exists() && !file.canread()) {
                log.warn(sm.getstring("catalina.incorrectpermissions"));
            }
            return;
        }

        getserver().setcatalina(this);
        getserver().setcatalinahome(bootstrap.getcatalinahomefile());
        getserver().setcatalinabase(bootstrap.getcatalinabasefile());

        // stream redirection
        initstreams();

        // start the new server
        try {
            getserver().init();
        } catch (lifecycleexception e) {
            if (boolean.getboolean("org.apache.catalina.startup.exit_on_init_failure")) {
                throw new java.lang.error(e);
            } else {
                log.error(sm.getstring("catalina.initerror"), e);
            }
        }

        long t2 = system.nanotime();
        if(log.isinfoenabled()) {
            log.info(sm.getstring("catalina.init", long.valueof((t2 - t1) / 1000000)));
        }
    }
  • daemon.start(): 启动tomcat

通过调用daemon.start()启动tomcat,其内容如下:

    /**
     * start the catalina daemon.
     * @throws exception fatal start error
     */
    public void start() throws exception {
        if (catalinadaemon == null) {
            init();
        }

        method method = catalinadaemon.getclass().getmethod("start", (class [])null);
        method.invoke(catalinadaemon, (object [])null);
    }

程序通过反射的方式调用catalina.start()方式启动tomcat,下面,我们看下catalina.start()方法的实现逻辑:

    /**
     * start a new server instance.
     */
    public void start() {

        if (getserver() == null) {
            load();
        }

        if (getserver() == null) {
            log.fatal(sm.getstring("catalina.noserver"));
            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(sm.getstring("catalina.startup", long.valueof((t2 - t1) / 1000000)));
        }

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

可以看出,程序调用getserver().start()启动,getserver()方法返回的是一个standardserver类,继而其调用的是standardserver.startinternal()方法,在standardserver中,又调用到standardservice.startinternal()方法。

    // standardserver.java
    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();
            }
        }
        // ......省略部分代码
    }

    protected void startinternal() throws lifecycleexception {

        if(log.isinfoenabled())
            log.info(sm.getstring("standardservice.start.name", this.name));
        setstate(lifecyclestate.starting);

        // start our defined container first
        if (engine != null) {
            synchronized (engine) {
                engine.start();
            }
        }

        synchronized (executors) {
            for (executor executor: executors) {
                executor.start();
            }
        }

        mapperlistener.start();

        // start our defined connectors second
        synchronized (connectorslock) {
            for (connector connector: connectors) {
                // if it has already failed, don't try and start it
                if (connector.getstate() != lifecyclestate.failed) {
                    connector.start();
                }
            }
        }
    }

注意,这里为什么不是start()方法,而是startinternal()方法呢?原因是standardserver和standservice类都继承了lifecyclembeanbase类,而lifecyclembeanbase类又继承了lifecyclebase类。下面看下lifecyclebase类的start()方法:

    public final synchronized void start() throws lifecycleexception {

        if (lifecyclestate.starting_prep.equals(state) || lifecyclestate.starting.equals(state) ||
                lifecyclestate.started.equals(state)) {

            if (log.isdebugenabled()) {
                exception e = new lifecycleexception();
                log.debug(sm.getstring("lifecyclebase.alreadystarted", tostring()), e);
            } else if (log.isinfoenabled()) {
                log.info(sm.getstring("lifecyclebase.alreadystarted", tostring()));
            }

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

可以看出,调用start()方法,最终都会调用到startinternal()方法。在下篇文章中,我们将详细看下standardservice.java中的engine.start()、executor.start()、connector.start()都分别启动了什么?敬请期待!

微信公众号: 源码湾

欢迎关注本人微信公众号: 源码湾。 本公众号将不定期进行相关源码及相关开发技术的分享,共同成长,共同进步~

Tomcat源码分析三:Tomcat启动加载过程(一)的源码解析


blog:

  • 简书: https://www.jianshu.com/u/91378a397ffe
  • csdn: https://blog.csdn.net/zhiyouwu
  • 开源中国: https://my.oschina.net/u/3204088
  • 掘金: https://juejin.im/user/5b5979efe51d451949094265
  • 博客园: https://www.cnblogs.com/zhiyouwu/
  • 微信公众号: 源码湾
  • 微信: wzy1782357529 (欢迎沟通交流)