查看tomcat啓動文件都乾點啥---Bootstrap.java

  在上一章查看tomcat啓動文件都乾點啥---catalina.bat,說了在catalina.bat中都走了什麼流程,最重要的是,咱們得出了以下這段命令:  html

_EXECJAVA=start "Tomcat" "E:\Program Files\Java\jdk1.7.0_40\bin\java" 
JAVA_OPTS= -Djava.util.logging.config.file="F:\apache-tomcat-7.0.8\conf\logging.properties" 
-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager 
CATALINA_OPTS= 
DEBUG_OPTS= 
-Djava.endorsed.dirs="F:\apache-tomcat-7.0.8\endorsed" 
-classpath "F:\apache-tomcat-7.0.8\bin\bootstrap.jar;F:\apache-tomcat-7.0.8\bin\tomcat-juli.jar" 
-Dcatalina.base="F:\apache-tomcat-7.0.8" 
-Dcatalina.home="F:\apache-tomcat-7.0.8" 
-Djava.io.tmpdir="F:\apache-tomcat-7.0.8\temp" 
MAINCLASS=org.apache.catalina.startup.Bootstrap 
CMD_LINE_ARGS= ACTION=start

  其中很重要的一個屬性是:MAINCLASS=org.apache.catalina.startup.Bootstrap,Bootstrap在bootstrap.jar中,咱們看一下Bootstrap的類圖:java

  

  從每一個變量和方法的名字的字面上也能大概看出來變量或者方法的做用。express

  很顯然,程序走到Bootstrap的時候首先調用main方法,main方法是經過腳本啓動tomcat的入口,咱們看一下main方法中實現的內容:  apache

 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);
        }
View Code

  能夠看出main方法主要實現了兩個功能:bootstrap

  (1)初始化一個守護進程變量。api

  (2)加載參數,解析命令,並執行。數組

   下面是初始化守護進程的執行過程:tomcat

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

  能夠看到在bootstrap.init()方法中對bootstrap變量進行初始化,而後將結果返回給daemon。下面看一下init方法中的實現:  app

public void init()
        throws Exception
    {

        // Set Catalina path
        setCatalinaHome();
        setCatalinaBase();

        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.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;

    }
View Code

  init方法中對classLoader進行了初始化,設置了引用的catalinaDaemon變量。less

  對於如何定義catalinaDaemon變量是應用反射機制:  

    Object startupInstance = startupClass.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;

  下面着重說一下關於classLoader。

  在Bootstrap類中,最開始的地方,有三個ClassLoader的定義,內容以下:  

protected ClassLoader commonLoader = null;
protected ClassLoader catalinaLoader = null;
protected ClassLoader sharedLoader = null;

  先不說這三個classLoader之間的關係,先看一下Bootstrap上的註釋也有關於classLoader的說明:  

 * Bootstrap loader for Catalina.  This application constructs a class loader
 * for use in loading the Catalina internal classes (by accumulating all of the
 * JAR files found in the "server" directory under "catalina.home"), and
 * starts the regular execution of the container.  The purpose of this
 * roundabout approach is to keep the Catalina internal classes (and any
 * other classes they depend on, such as an XML parser) out of the system
 * class path and therefore not visible to application level classes.

  翻譯過來就是說:Bootstrap第一個功能是引導Catalina,Bootstrap構造一個class loader來加載Catalina的內部類(全部在catalina.home中的jar文件),第二個功能是啓動container。實現catalina的內部類和系統的class path以及應用程序中的class要區分開不能相互訪問的目的。  

  // Set Catalina path
setCatalinaHome();
setCatalinaBase();
initClassLoaders();
Thread.currentThread().setContextClassLoader(catalinaLoader);
SecurityClassLoad.securityClassLoad(catalinaLoader);

  設置當前的線程的class loader爲catalinaLoader。使用catalinaLoader加載catalina類,實現反射,定義catalina的反射。而後重點看一下 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);
        }

    在看一下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<Repository>();

        StringTokenizer tokenizer = new StringTokenizer(value, ",");
        while (tokenizer.hasMoreElements()) {
            String repository = tokenizer.nextToken().trim();
            if (repository.length() == 0) {
                continue;
            }

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

        ClassLoader classLoader = ClassLoaderFactory.createClassLoader
            (repositories, parent);

        // Retrieving MBean server
        MBeanServer mBeanServer = null;
        if (MBeanServerFactory.findMBeanServer(null).size() > 0) {
            mBeanServer = MBeanServerFactory.findMBeanServer(null).get(0);
        } else {
            mBeanServer = ManagementFactory.getPlatformMBeanServer();
        }

        // Register the server classloader
        ObjectName objectName =
            new ObjectName("Catalina:type=ServerClassLoader,name=" + name);
        mBeanServer.registerMBean(classLoader, objectName);

        return classLoader;

    }
View Code

  在該方法中引入了CatalinaProperties類,下面看一下CatalinaProperties的內容:

  

  這個類主要是加載catalina.properties配置文件,而後將其中的內容保存到當前環境中,首先查看$CATALINA_BASE/conf/catalina.propertie是否存在,若是不存在的話去讀取Bootstrap.jar中的catalina.propertie的文件,若是沒有在$CATALINA_BASE/conf/中配置catalina.propertie文件,那麼catalina.propertie內容以下所示(tomcat版本大於5.x):  

# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License.  You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

#
# List of comma-separated packages that start with or equal this string
# will cause a security exception to be thrown when
# passed to checkPackageAccess unless the
# corresponding RuntimePermission ("accessClassInPackage."+package) has
# been granted.
package.access=sun.,org.apache.catalina.,org.apache.coyote.,org.apache.tomcat.,org.apache.jasper.
#
# List of comma-separated packages that start with or equal this string
# will cause a security exception to be thrown when
# passed to checkPackageDefinition unless the
# corresponding RuntimePermission ("defineClassInPackage."+package) has
# been granted.
#
# by default, no packages are restricted for definition, and none of
# the class loaders supplied with the JDK call checkPackageDefinition.
#
package.definition=sun.,java.,org.apache.catalina.,org.apache.coyote.,org.apache.tomcat.,org.apache.jasper.

#
#
# List of comma-separated paths defining the contents of the "common" 
# classloader. Prefixes should be used to define what is the repository type.
# Path may be relative to the CATALINA_HOME or CATALINA_BASE path or absolute.
# If left as blank,the JVM system loader will be used as Catalina's "common" 
# loader.
# Examples:
#     "foo": Add this folder as a class repository
#     "foo/*.jar": Add all the JARs of the specified folder as class 
#                  repositories
#     "foo/bar.jar": Add bar.jar as a class repository
common.loader=${catalina.home}/lib,${catalina.home}/lib/*.jar

#
# List of comma-separated paths defining the contents of the "server" 
# classloader. Prefixes should be used to define what is the repository type.
# Path may be relative to the CATALINA_HOME or CATALINA_BASE path or absolute.
# If left as blank, the "common" loader will be used as Catalina's "server" 
# loader.
# Examples:
#     "foo": Add this folder as a class repository
#     "foo/*.jar": Add all the JARs of the specified folder as class 
#                  repositories
#     "foo/bar.jar": Add bar.jar as a class repository
server.loader=

#
# List of comma-separated paths defining the contents of the "shared" 
# classloader. Prefixes should be used to define what is the repository type.
# Path may be relative to the CATALINA_BASE path or absolute. If left as blank,
# the "common" loader will be used as Catalina's "shared" loader.
# Examples:
#     "foo": Add this folder as a class repository
#     "foo/*.jar": Add all the JARs of the specified folder as class 
#                  repositories
#     "foo/bar.jar": Add bar.jar as a class repository 
# Please note that for single jars, e.g. bar.jar, you need the URL form
# starting with file:.
shared.loader=

#
# String cache configuration.
tomcat.util.buf.StringCache.byte.enabled=true
#tomcat.util.buf.StringCache.char.enabled=true
#tomcat.util.buf.StringCache.trainThreshold=500000
#tomcat.util.buf.StringCache.cacheSize=5000

  在聯繫在Bootstrap.java中的這段代碼:  

String value = CatalinaProperties.getProperty(name + ".loader");
if ((value == null) || (value.equals("")))
return parent;

  有沒有感受很奇怪,shared.loader,server.loader這兩個配置都爲空,因此經過initClassLoaders方法能夠得出結論,catalinaLoader,sharedLoader均指向commonLoader的引用,因此在apache網站上的classLoader樹以下展現:

  

  可是拿兩個classLoader爲何會定義呢,那就要看一下在tomcat5.x時代時候的catalina.propertie內容:  

server.loader=${catalina.home}/server/classes,${catalina.home}/server/lib/*.jar
shared.loader=${catalina.base}/shared/classes,${catalina.base}/shared/lib/*.jar

  catalinaLoader,sharedLoader這可能就是歷史遺留的問題了。關於這兩個classLoader的內容請在tomcat5.x找答案。

  OK,下面咱們只須要關注commonLoader變量了,在catalina.properties配置文件中能夠得出

common.loader=${catalina.home}/lib,${catalina.home}/lib/*.jar

  下面相信看一下tomcat如何處理commonLoader:

  當createClassLoader的參數爲common,null的時候,

String value = CatalinaProperties.getProperty(name + ".loader");

  此時的value爲配置文件中的值(在個人調試環境中,下同):

${catalina.home}/lib,${catalina.home}/lib/*.jar

  這個變量多是不能解析的,須要將${catalina.home}替換爲系統的據對路徑,經過replace方法替換,能夠看一下replace的定義:  

protected String replace(String str) {
        // Implementation is copied from ClassLoaderLogManager.replace(),
        // but added special processing for catalina.home and catalina.base.
        String result = str;
        int pos_start = str.indexOf("${");
        if (pos_start >= 0) {
            StringBuilder builder = new StringBuilder();
            int pos_end = -1;
            while (pos_start >= 0) {
                builder.append(str, pos_end + 1, pos_start);
                pos_end = str.indexOf('}', pos_start + 2);
                if (pos_end < 0) {
                    pos_end = pos_start - 1;
                    break;
                }
                String propName = str.substring(pos_start + 2, pos_end);
                String replacement;
                if (propName.length() == 0) {
                    replacement = null;
                } else if (Globals.CATALINA_HOME_PROP.equals(propName)) {
                    replacement = getCatalinaHome();
                } else if (Globals.CATALINA_BASE_PROP.equals(propName)) {
                    replacement = getCatalinaBase();
                } else {
                    replacement = System.getProperty(propName);
                }
                if (replacement != null) {
                    builder.append(replacement);
                } else {
                    builder.append(str, pos_start, pos_end + 1);
                }
                pos_start = str.indexOf("${", pos_end + 1);
            }
            builder.append(str, pos_end + 1, str.length());
            result = builder.toString();
        }
        return result;
    }
View Code

  經過replace返回的結果爲:  

  value=F:\ITSM_V3.1\tomcatSource/lib,F:\ITSM_V3.1\tomcatSource/lib/*.jar

  而後順序執行,很容易得出結論,在執行  

ClassLoader classLoader = ClassLoaderFactory.createClassLoader(repositories, parent);

  時候的repositories的值爲:

  

  調用ClassLoaderFactory類中的靜態方法createClassLoader,其定義以下:  

 public static ClassLoader createClassLoader(List<Repository> repositories,
                                                final ClassLoader parent)
        throws Exception {

        if (log.isDebugEnabled())
            log.debug("Creating new class loader");

        // Construct the "class path" for this class loader
        Set<URL> set = new LinkedHashSet<URL>();

        if (repositories != null) {
            for (Repository repository : repositories)  {
                if (repository.getType() == RepositoryType.URL) {
                    URL url = new URL(repository.getLocation());
                    if (log.isDebugEnabled())
                        log.debug("  Including URL " + url);
                    set.add(url);
                } else if (repository.getType() == RepositoryType.DIR) {
                    File directory = new File(repository.getLocation());
                    directory = directory.getCanonicalFile();
                    if (!validateFile(directory, RepositoryType.DIR)) {
                        continue;
                    }
                    URL url = directory.toURI().toURL();
                    if (log.isDebugEnabled())
                        log.debug("  Including directory " + url);
                    set.add(url);
                } else if (repository.getType() == RepositoryType.JAR) {
                    File file=new File(repository.getLocation());
                    file = file.getCanonicalFile();
                    if (!validateFile(file, RepositoryType.JAR)) {
                        continue;
                    }
                    URL url = file.toURI().toURL();
                    if (log.isDebugEnabled())
                        log.debug("  Including jar file " + url);
                    set.add(url);
                } else if (repository.getType() == RepositoryType.GLOB) {
                    File directory=new File(repository.getLocation());
                    directory = directory.getCanonicalFile();
                    if (!validateFile(directory, RepositoryType.GLOB)) {
                        continue;
                    }
                    if (log.isDebugEnabled())
                        log.debug("  Including directory glob "
                            + directory.getAbsolutePath());
                    String filenames[] = directory.list();
                    for (int j = 0; j < filenames.length; j++) {
                        String filename = filenames[j].toLowerCase(Locale.ENGLISH);
                        if (!filename.endsWith(".jar"))
                            continue;
                        File file = new File(directory, filenames[j]);
                        file = file.getCanonicalFile();
                        if (!validateFile(file, RepositoryType.JAR)) {
                            continue;
                        }
                        if (log.isDebugEnabled())
                            log.debug("    Including glob jar file "
                                + file.getAbsolutePath());
                        URL url = file.toURI().toURL();
                        set.add(url);
                    }
                }
            }
        }

        // Construct the class loader itself
        final URL[] array = set.toArray(new URL[set.size()]);
        if (log.isDebugEnabled())
            for (int i = 0; i < array.length; i++) {
                log.debug("  location " + i + " is " + array[i]);
            }

        return AccessController.doPrivileged(
                new PrivilegedAction<StandardClassLoader>() {
                    @Override
                    public StandardClassLoader run() {
                        if (parent == null)
                            return new StandardClassLoader(array);
                        else
                            return new StandardClassLoader(array, parent);
                    }
                });
    }
View Code

  最後返回的 return new StandardClassLoader(array);其中array是一個URL類型的數組,看結果的時候要對照前面的內容file:/F:/ITSM_V3.1/tomcatSource/lib/, file:/F:/ITSM_V3.1/tomcatSource/lib/annotations-api.jar.......(file:/F:/ITSM_V3.1/tomcatSource/lib/下的全部jar文件)。

  而後將classLoader註冊到MBeanServer中,而後返回classLoader。而後用返回的classLoader加載org.apache.catalina.startup.Catalina,而後進行反射。調用對應的方法。

  下面說一下main方法的第二個做用,加載參數,解析命令,並執行  

           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.");
            }

    其中load方法是將命令行定義的參數傳遞給經過反射調用的catalinaDaemon.load方法。而後執行的方法也就是在實現的時候調用catalinaDaemon中對應的方法,例如:  

  /**
     * Stop the Catalina Daemon.
     */
    public void stop()
        throws Exception {

        Method method = catalinaDaemon.getClass().getMethod("stop", (Class [] ) null);
        method.invoke(catalinaDaemon, (Object [] ) null);

    }

  在此文中咱們得出在(tomcat7.0版本中):

  (1)Bootstrap中如何經過建立的commonLoader=catalinaLoader=sharedLoader來加載類。

  (2)在Bootstrap中使用反射機智來加載來調用catalinaDaemon中的方法。

  (3)如何獲取catalina.properties配置文件。 

 

  若是有疑問或者不對的地方,請指出。謝謝。

相關文章
相關標籤/搜索