(1) 从Tomcat启动开始的源码分析--Bootstrap初始化容器

Tomcat启动分析

从启动脚本开始进入Tomcat的世界。

1.startup.sh

开源框架源码剖析从tomcat架构开始:

  1. 获取bin目录路径 ,在bin目录下执行脚本;
  2. 调用 catalina.sh 的start语句块。
exec "$PRGDIR"/"$EXECUTABLE" start "$@"

2.catalina.sh

脚本根据传参调用Bootstrap类main方法,执行start语句块

org.apache.catalina.startup.Bootstrap "$@" start \

catalina.sh还有其他命令选项,本文围绕启动做源码分析,不是我们此次的重点.

Using CATALINA_BASE:   /home/kali/Software/apache-tomcat-9.0.20
Using CATALINA_HOME:   /home/kali/Software/apache-tomcat-9.0.20
Using CATALINA_TMPDIR: /home/kali/Software/apache-tomcat-9.0.20/temp
Using JRE_HOME:        /home/kali/Software/jdk-12.0.1
Using CLASSPATH:       /home/kali/Software/apache-tomcat-9.0.20/bin/bootstrap.jar:/home/kali/Software/apache-tomcat-9.0.20/bin/tomcat-juli.jar
Usage: catalina.sh ( commands ... )
commands:
  debug             Start Catalina in a debugger
  debug -security   Debug Catalina with a security manager
  jpda start        Start Catalina under JPDA debugger
  run               Start Catalina in the current window
  run -security     Start in the current window with security manager
  start             Start Catalina in a separate window
  start -security   Start in a separate window with security manager
  stop              Stop Catalina, waiting up to 5 seconds for the process to end
  stop n            Stop Catalina, waiting up to n seconds for the process to end
  stop -force       Stop Catalina, wait up to 5 seconds and then use kill -KILL if still running
  stop n -force     Stop Catalina, wait up to n seconds and then use kill -KILL if still running
  configtest        Run a basic syntax check on server.xml - check exit code for result
  version           What version of tomcat are you running?

3.org.apache.catalina.startup.Bootstrap.class

(1) start命令的流程图

这里只列出start命令的流程图,其他的stop和configtest等命令暂不分析.

Created with Raphaël 2.2.0 开始 初始化Bootstrap实例: daemon对象 命令是start? 调用daemon对象的setAwait方法 调用daemon对象的load方法 调用daemon对象的start方法 server实例是否存在? 结束 调用System的exit(1)方法 其他命令... 执行... 命令不存在 yes no yes no yes no

(2) 初始化全局的静态变量

Bootstrap类的静态方法块主要用来初始化全局的静态变量catalinaBaseFile和catalinaHomeFile,
这两个就是执行catalina.sh命令打印出来的tomcat家目录:
Using CATALINA_BASE: /home/kali/Software/apache-tomcat-9.0.20
Using CATALINA_HOME: /home/kali/Software/apache-tomcat-9.0.20
CATALINA_HOME是Tomcat的安装目录,CATALINA_BASE是Tomcat的工作目录。
多实例tomcat与多版本tomcat运行的时候,需要考虑区别设置.

    private static final File catalinaBaseFile;
    private static final File catalinaHomeFile;
    
    static {
        // Will always be non-null
        String userDir = System.getProperty("user.dir");

        // Home first
        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) {
            // First fall-back. See if current directory is a bin directory
            // in a normal Tomcat install
            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());
    }

(3) main方法

说起main方法,初学Java的时候用的很多,第一次就是打印Hello,World!. 后来WEB项目中实际开发很少用的到main,写单元测试用例都是Junit.分析Tomcat源码遇到老朋友很开心的说,赶紧贴上它:

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

(4) 初始化Bootstrap实例: daemon对象

main方法开头就是先使用同步代码块对daemon对象进行同步操作,锁对象为daemonLock,进入同步代码块前要获得daemonLock对象的锁. 在同步代码块中判断daemon对象是否为null,如果已经初始化过了,就把daemon对象的catalinaLoader属性放到当前线程的上下文中.如果daemon对象为null,就需要执行init方法,进行初始化.

    /**
     * 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);
        }
    }
    
    /**
     * Initialize daemon.
     * @throws Exception Fatal initialization error
     */
    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方法做了那些事呢?

  1. 首先是创建类加载器commonLoader,catalinaLoader和sharedLoader
    Tomcat 5.X 有3组目录(common,server,shared)可以存放Java类库,三组目录的可见性不一样.
    Tomcat 6.X 以后就没有common,server,shared目录了,只有在catalina.properties中设置了server.loader和share.loader项之后才会建立catalinaLoader和sharedLoader的实例,否则会用到这两个类加载器的地方都会用commonLoader实例代替,从initClassLoaders()方法中可以明显地看到.
  2. 利用Java反射机制,调用了org.apache.catalina.startup.Catalina的setParentClassLoader方法,传递的参数是sharedLoader类加载器.
    为什么要用反射机制而不直接创建Catalina对象来调用setParentClassLoader方法呢?

(5) 调用daemon对象的setAwait方法

利用Java反射机制,调用了org.apache.catalina.startup.Catalina的setAwait方法,传递的参数是布尔类型await.

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

(6) 调用daemon对象的load方法

利用Java反射机制,调用了org.apache.catalina.startup.Catalina的load方法,传递的参数是字符数组类型arguments.

    /**
     * Load daemon.
     */
    private void load(String[] arguments) throws Exception {

        // Call the load() method
        String methodName = "load";
        Object param[];
        Class<?> paramTypes[];
        if (arguments==null || arguments.length==0) {
            paramTypes = null;
            param = null;
        } else {
            paramTypes = new Class[1];
            paramTypes[0] = arguments.getClass();
            param = new Object[1];
            param[0] = arguments;
        }
        Method method =
            catalinaDaemon.getClass().getMethod(methodName, paramTypes);
        if (log.isDebugEnabled()) {
            log.debug("Calling startup class " + method);
        }
        method.invoke(catalinaDaemon, param);
    }

(7) 调用daemon对象的start方法

利用Java反射机制,调用了org.apache.catalina.startup.Catalina的start方法

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

(8) 调用daemon对象的getServer方法判断实例是否创建成功

利用Java反射机制,调用了org.apache.catalina.startup.Catalina的getServer方法获取server实例

    /**
     * getServer() for configtest
     */
    private Object getServer() throws Exception {

        String methodName = "getServer";
        Method method = catalinaDaemon.getClass().getMethod(methodName);
        return method.invoke(catalinaDaemon);
    }

如果server实例创建成功则启动成功;
如果server实例创建失败则失败调用System.exit(1);非正常退出程序.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值