Tomcat啓動加載過程(一)的源碼解析
今天,我將分享用源碼的方式講解Tomcat啓動的加載過程,關於Tomcat的架構請參閱《Tomcat源碼分析二:先看看Tomcat的總體架構》一文。java
先看看應用狀況
在《Servlet與Tomcat運行示例》一文中,我詳細的記錄了Tomcat是如何啓動一個Servlet的程序的步驟。其中,第6步驟是啓動Tomcat,也就是在windows系統上執行startup.bat, 在linux操做系統上執行startup.sh的腳本。那麼,咱們就從這個腳本出發,走進Tomcat,看看它是如何啓動的?這裏,咱們以startup.sh爲例,windows端的startup.bat相似。linux
startup.sh的內容是什麼?
咱們先看看tomcat的啓動腳本startup.sh的內容是什麼,先看看其腳本內容(省略部分註釋),以下:apache
#!/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 "$@"
提取其中主要的幾句:bootstrap
PRGDIR=`dirname "$PRG"` EXECUTABLE=catalina.sh exec "$PRGDIR"/"$EXECUTABLE" start "$@"
簡而概之,該腳本的執行內容爲:調用catalina.sh腳本。下面,咱們繼續來看下catalina.sh腳本的內容windows
catalina.sh腳本
因爲catalina.sh腳本內容比較多,這裏提取一些重要的內容,而後解釋其用途:tomcat
再簡要的描述下在catalina.sh中做用:完成環境檢查、環境初始化、參數初始化、啓動操做步驟。注意一下上圖中被綠色框出來的內容,能夠看到其調用執行的是org.apache.catalina.startup.Bootstrap類,而且傳輸過去的command指令爲start。微信
迴歸Java代碼
Bootstrap類進行了什麼操做呢?
接下來,咱們帶着這幾個問題來去探索一下Bootstrap類:架構
- Bootstrap類在接收到start指令後要去幹什麼?
- Bootstrap類在啓動過程當中的職責是什麼?
下面,咱們帶着上面的幾個問題來具體的探討一下Tomcat的源碼。先來看看Bootstrap類的main方法:app
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()都分別啓動了什麼?敬請期待!
微信公衆號: 源碼灣
歡迎關注本人微信公衆號: 源碼灣。 本公衆號將不按期進行相關源碼及相關開發技術的分享,共同成長,共同進步~