tomcat源碼分析(第二篇 tomcat啓動過程詳解)

你們都知道,每個應用程序都有一個惟一的入口(即main函數),那麼對於Java語言開發的tomcat服務器也不例外,找到這個入口,瞭解各個組件加載的具體過程,對理解整個應用的實現過程有很大的幫助。 tomcat啓動相關的類位於catalina.startup包路徑下,入口是類Bootstrap中的main()函數。Bootstrap啓動類主要完成了三方面的內容,分別以下:java

①在靜態代碼塊中設置catalinaHome和catalinaBase兩個路徑; web

②common、server、shared三個類加載器的初始化; apache

③利用反射機制實例化org.apache.catalina.startup.Catalina類。 bootstrap

1、設置catalinaHome和catalinaBase

catalinaHome是tomcat的安裝目錄,catalinaBase是tomcat的工做目錄;這兩個目錄的主要功能是當在同一臺機器上部署多個tomcat實例時,能夠不用安裝多個tomcat副本,而是經過共享tomcat代碼的方式實現。例如在同一臺機器上部署兩個tomcat實例時,只須要建立兩個base目錄,base1和base2,而後將tomcat安裝目錄下的共享目錄拷貝到這兩個目錄下。分別修改conf目錄下的server.xml文件中的端口號,就能夠同時啓動兩個tomcat服務器,此時tomcat安裝目錄(即tomcatHome)下的bin和lib目錄是共享的。tomcat

代碼清單(Bootstrap中設置catalinaHome和catalinaBase的代碼)
static {
        //獲取tomcat的安裝目錄
        String userDir = System.getProperty("user.dir");
        String home = System.getProperty(Globals.CATALINA_HOME_PROP);
        File homeFile = null;
        if (home != null) {
            File f = new File(home);
            try {
                homeFile = f.getCanonicalFile();
            } catch (IOException ioe) {
                homeFile = f.getAbsoluteFile();
            }
        }

        if (homeFile == null) {
            //bootstrap.jar的根目錄,其實就是tomcat按照路徑下的bin文件夾
            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 = homeFile;
        System.setProperty(
                Globals.CATALINA_HOME_PROP, catalinaHomeFile.getPath());

        // Then base
        String base = System.getProperty(Globals.CATALINA_BASE_PROP);
        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());
    }
複製代碼

2、初始化類加載器 common、server、shared

tomcat自定義的類加載器主要有三類:common、server和shared,用來加載不一樣位置的類和jar文件,實現不一樣web應用程序以及tomcat系統之間類文件的分離和共享。主要解決的問題有:部署在同一個tomcat服務器上多個web應用程序所使用的Java類庫能夠實現相互分離(同一個類庫的不一樣版本);部署在同一個tomcat服務器上多個web應用程序所使用的Java類庫能夠實現相互共享;服務器保證自身的安全不受部署的web應用程序的影響(tomcat系統類只能經過server類加載器加載,web應用程序由shared類加載器下的WebAppClassLoader加載);實現HotSwap功能(經過替換JsperLoader類加載器實現)。安全

commonClassLoader

commonClassLoader在catalina.properties中的定義是:common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar" 能夠看出它加載的類庫是catalina.base和catalina.home下lib中的文件。common是服務器和web應用程序共用的類加載器,也是server和shared的父加載器。在Bootstrap中的實現以下bash

private void initClassLoaders() {
        try {
            commonLoader = createClassLoader("common", null);
            if( commonLoader == null ) {
                commonLoader=this.getClass().getClassLoader();
            }
            catalinaLoader = createClassLoader("server", commonLoader);  //設置commonLoader爲catalinaLoader和sharedLoader的父加載器。
            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 {
        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) {
            try {
                @SuppressWarnings("unused")
                URL url = new URL(repository);
                repositories.add(new Repository(repository, RepositoryType.URL));
                continue;
            } catch (MalformedURLException e) {
                // Ignore
            }
            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);
    }

複製代碼

catalinaClassLoader

catalinaClassLoader在catalina.properties中設置的路徑爲空 server.loader=,因此它的類加載路徑和它的父加載器commonClassLoader同樣。初始化catalinaClassLoader後在init方法中設置爲當前線程的類加載器,而後完成對rg.apache.catalina.startup.Catalina類的加載。架構

Thread.currentThread().setContextClassLoader(catalinaLoader);
 SecurityClassLoad.securityClassLoad(catalinaLoader);
 Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
複製代碼

sharedClassLoader

catalinaClassLoader在catalina.properties中設置的路徑也爲空,因此其默認類加載路徑和它的父加載器commonClassLoader同樣。函數

利用反射機制實例化org.apache.catalina.startup.Catalina類

Catalina類在tomcat啓動中有着比較重要的做用,做爲tomcat生命週期的一個開始類,Catalina主要職責是解析server.xml文件,完成Server,Service,Connector等組件的啓動和關閉,接受tomcat中止指令,關閉tomcat服務器。在Bootstrap中經過反射機制生成org.apache.catalina.startup.Catalina實例,代碼以下

Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
    Object startupInstance = startupClass.getConstructor().newInstance();
複製代碼

在Catalina類中是經過建立Digester的方式解析server.xml而且生成Server,Service,Connector和Container實例的,xml中只配置了Engine和Host兩種Container。

protected String configFile = "conf/server.xml";  //設置server.xml的路徑
...
 protected Digester createStartDigester() {
        long t1=System.currentTimeMillis();
        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);
        digester.addObjectCreate("Server",
                                 "org.apache.catalina.core.StandardServer",
                                 "className");
        digester.addSetProperties("Server");
        digester.addSetNext("Server",
                            "setServer",
                            "org.apache.catalina.Server");
    //省略一大段設置其餘組件的代碼,形式和StandardServer的生成同樣,關於Digester類解析xml生成Java實例可參考《深刻剖析tomcat》第15章。
}
複製代碼

Catalina類的入口一樣是start方法,在Bootstrap中經過反射的方式調用了Catalina的start方法

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

    }
複製代碼

start方法中又調用load方法進行建立Digester,生成各個組件;load類的定義以下:

public void load() {
        if (loaded) {
            return;
        }
        loaded = true;
        long t1 = System.nanoTime();
        initDirs();

        // Before digester - it may be needed
        initNaming();

        // Create and execute our Digester
        Digester digester = createStartDigester();

        InputSource inputSource = null;
        InputStream inputStream = null;
        File file = null;
        try {
            try {
                file = configFile();      //獲取到server.xml 文件並解析
                inputStream = new FileInputStream(file);
                inputSource = new InputSource(file.toURI().toURL().toString());
            } catch (Exception e) {
                if (log.isDebugEnabled()) {
                    log.debug(sm.getString("catalina.configFail", file), e);
                }
            }
            if (inputStream == null) {
                try {
                    inputStream = getClass().getClassLoader()
                        .getResourceAsStream(getConfigFile());
                    inputSource = new InputSource
                        (getClass().getClassLoader()
                         .getResource(getConfigFile()).toString());
                } catch (Exception e) {
                    if (log.isDebugEnabled()) {
                        log.debug(sm.getString("catalina.configFail",
                                getConfigFile()), e);
                    }
                }
            }

            // This should be included in catalina.jar
            // Alternative: don't bother with xml, just create it manually. if (inputStream == null) { try { inputStream = getClass().getClassLoader() .getResourceAsStream("server-embed.xml"); inputSource = new InputSource (getClass().getClassLoader() .getResource("server-embed.xml").toString()); } catch (Exception e) { if (log.isDebugEnabled()) { log.debug(sm.getString("catalina.configFail", "server-embed.xml"), e); } } } if (inputStream == null || inputSource == null) { if (file == null) { log.warn(sm.getString("catalina.configFail", getConfigFile() + "] or [server-embed.xml]")); } else { log.warn(sm.getString("catalina.configFail", file.getAbsolutePath())); if (file.exists() && !file.canRead()) { log.warn("Permissions incorrect, read permission is not allowed on the file."); } } return; } try { inputSource.setByteStream(inputStream); //對server.xml數據流解析 digester.push(this); digester.parse(inputSource); } catch (SAXParseException spe) { log.warn("Catalina.start using " + getConfigFile() + ": " + spe.getMessage()); return; } catch (Exception e) { log.warn("Catalina.start using " + getConfigFile() + ": " , e); return; } } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { // Ignore } } } getServer().setCatalina(this); //配置server,啓動類爲當前Catalina類,Home和base在Bootstrap中定義 getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile()); getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile()); // Stream redirection initStreams(); // 啓動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("Catalina.start", e); } } long t2 = System.nanoTime(); if(log.isInfoEnabled()) { log.info("Initialization processed in " + ((t2 - t1) / 1000000) + " ms"); } } 複製代碼

tomcat啓動是經過server.xml配置文件實現Server、Service、Connector和Container組件,那麼這些組件具體是如何實現的,後面幾篇文章將繼續跟進tomcat各組件的具體實現。

tomcat源碼分析(第一篇 tomcat源碼分析(第一篇 從總體架構開始))
tomcat源碼分析(第三篇 tomcat請求原理解析--Connector源碼分析)
tomcat源碼分析(第四篇 tomcat請求處理原理解析--Container源碼分析)

相關文章
相關標籤/搜索