tomcat7源碼閱讀(一)tomcat啓動

先從tomcat啓動腳本開始,我們可以使用startup.sh啓動tomcat

startup.sh腳本分析

  1. 先判斷操作系統(os400是 IBM的AIX、darwin是MacOSX 操作環境的操作系統成份、Darwin是windows平臺上運行的類UNIX模擬環境)

  2. 獲取catalina.sh的真實路徑,並判斷是否有可執行權限。調用catalina.sh腳本

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 "$@"

 catalina.sh

  1. 和start.sh類似,先檢測操作系統

  2. 獲取catalina.sh真實路徑,並設置環境變量CATALINA_HOME、CATALINA_BASE。一般情況下兩者都是tomcat根目錄

  3. 調用setenv.sh設置classpath環境變量,但是這個文件是不存在的,如果想要額外的classpath,可以新建,感覺這麼設計爲了擴展性

  4. 在clsspath後追加Bootstrap.jar、Tomcat-juli.jar

  5. 解析腳本參數,執行java類Bootsrap的main方法,將start作爲參數傳入,也就是tomcat的入口

和tomcat啓動主要的是兩個類:Bootstrap和Catalina類

Bootstarp.java類

main方法

1.加鎖新建一個Bootstrap對象,調用init初始化並且賦值給了私有靜態對象daemon

2.調用了daemon對象load和start方法,而load和start方法裏面利用了反射調用了catalinaDeamon的load和start方法

可以看到該類有一個Object作爲鎖,新建了一個爲null的私有靜態Bootstrap對象。

/**
 * Daemon object used by main.
 */
private static final Object daemonLock = new Object();
private static volatile Bootstrap daemon = null;

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

 deamon.start方法代碼,調用了catelinaDaemon的start方法,之前的load方法也類似

public void start() throws Exception {
        if (catalinaDaemon == null) {
            init();
        }

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

 Catalina的start方法,啓動服務器實例,註冊Hook(用於資源的回收的組件的stop)

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

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

        if (getServer() == null) {
            log.fatal("Cannot start server. Server instance is not configured.");
            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("Server startup in " + ((t2 - t1) / 1000000) + " ms");
        }

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

 

 init方法

1.設置了catelina.home和catalina.base

2.使用initClassLoaders()創建了commonLoader、catalinaLoader和sharedLoader

3.利用反射新建一個org.apache.catalina.startup.Catalina類對象,賦值給了catalinaDeamon。

private Object catalinaDaemon = null;

ClassLoader commonLoader = null;
ClassLoader catalinaLoader = null;
ClassLoader sharedLoader = null; 
public void init() throws Exception {

        // Set Catalina path
        setCatalinaHome();
        setCatalinaBase();
        //tomcat 違背了雙親委派原則
        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;
    }

initClassLoaders創建了3個類加載器

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、sharedLoader 的父類加載器都爲commonLoader
            catalinaLoader = createClassLoader("server", commonLoader);
            sharedLoader = createClassLoader("shared", commonLoader);
        } catch (Throwable t) {
            handleThrowable(t);
            log.error("Class loader creation threw exception", t);
            System.exit(1);
        }
    }

看到裏邊其實會有很多疑問:

爲啥tomcat要破壞雙親委派原則?

先簡單說說雙親委派原則,所有的加載器加載類的時候都會有父類加載器先去加載,父類加載不了才子類加載。因爲jvm中的類的唯一性是由類加載器和類的全限定名來標識的,這種模型保證了類的唯一性。

但是tomcat是web服務器,需要解決幾個問題:

  1. 一個web容器可能會部署兩個應用程序,不同應用程序會依賴不同版本的類庫,因此要保證每個應用程序的類庫是獨立的、互相隔離的
  2. web容器也有依賴的類庫,不能和應用程序類混淆,要讓容器類庫和程序的類庫隔離開來

很明顯雙親委派模型解決不了問題,看看tomcat的加載器模型設計:

由WebappClassLoader去加載Webapp/WEB-INF/*的java類庫,每個應用程序都有不同的WebAppClassLoader加載,這樣實現了隔離。那麼很顯然,tomcat破壞了雙親違背原則,每個webappClassloade加載各自項目下的class文件,不會傳遞給父類去加載。

 

爲啥catalinaDeam要用反射加載,調用它的start和load方法爲什麼還要用反射?

大家都知道,反射是會降低性能的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章