Tomcat源码分析三:Tomcat启动加载过程(一)的源码解析
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脚本内容比较多,这里提取一些重要的内容,然后解释其用途:
再简要的描述下在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()都分别启动了什么?敬请期待!
微信公众号: 源码湾
欢迎关注本人微信公众号: 源码湾。 本公众号将不定期进行相关源码及相关开发技术的分享,共同成长,共同进步~
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 (欢迎沟通交流)
推荐阅读
-
Tomcat源码分析 (六)----- Tomcat 启动过程(一)
-
SpringBoot内置tomcat启动原理、以及SpringBoot初始化Servlet的源码分析
-
Tomcat8源码分析系列-启动分析(一) Lifecycle
-
深入理解 Tomcat(五)源码剖析Tomcat 启动过程----类加载过程
-
Mybaits 源码解析 (七)----- Select 语句的执行过程分析(下篇)全网最详细,没有之一
-
Tomcat源码分析 (八)----- HTTP请求处理过程(一)
-
SpringBoot 源码解析 (六)----- Spring Boot的核心能力 - 内置Servlet容器源码分析(Tomcat)
-
Tomcat的类加载机制流程及源码解析
-
一次tomcat源码启动控制台中文乱码的调试过程记录
-
Tomcat 源码分析(三)-(二)-WEB应用中的Listener、Filter、Servlet 的加载和调用