首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

Tomcat源码解析(二): Bootstrap和Catalina

  • 25-03-03 07:42
  • 4386
  • 9647
blog.csdn.net

Tomcat源码系列文章

Tomcat源码解析(一): Tomcat整体架构

Tomcat源码解析(二): Bootstrap和Catalina


文章目录

  • 前言
  • 一、启动类Bootstrap
    • 1、main
    • 2、init
    • 3、load与start
  • 二、加载Catalina
    • 1、load
    • 2、start
      • 2.1、注册shutdown钩子
      • 2.2、阻塞tomcat主线程
      • 2.3、停止Tomcat
  • 三、总结


前言

  • 在tomcat的bin目录下有两个启动tomcat的文件
    • 一个是startup.bat,它用于windows环境下启动tomcat
    • 另一个是startup.sh,它用于linux环境下启动tomcat
  • 两个文件中的逻辑是一样的, 我们只分析其中的startup.bat
  • 而startup.bat文件实际上就做了一件事情:启动catalina.bat
  • catalina.bat中下面这段指定了tomcat的启动类为Bootstrap这个类,catalina.bat最终执行了Bootstrap类中的main方法来启动tomcat
set _EXECJAVA=%_RUNJAVA%
set MAINCLASS=org.apache.catalina.startup.Bootstrap
set ACTION=start
  • 1
  • 2
  • 3

一、启动类Bootstrap

  • 首先来看下整个启动过程,我们可以看到Bootstrap作为启动入口首先进行了初始化方法init然后load方法加载了Catalina

在这里插入图片描述

1、main

  • Bootstrap的main方法首先会创建一个Bootstrap对象,调用它的init方法初始化
  • 然后根据启动参数,调用Bootstrap对象的不同方法,默认模式为start,该模式下将会先后调用load与start方法
private static final Object daemonLock = new Object();
private static volatile Bootstrap daemon = null;

// Bootstrap类的main方法
public static void main(String args[]) {
    // 创建一个 Bootstrap 对象
    synchronized (daemonLock) {
        if (daemon == null) {
            Bootstrap bootstrap = new Bootstrap();
            try {
                // 调用init方法初始化
                bootstrap.init();
            } catch (Throwable t) {
                handleThrowable(t);
                t.printStackTrace();
                return;
            }
            daemon = bootstrap;
        } else {
            Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
        }
    }

    // 根据启动参数,分别调用 Bootstrap 对象的不同方法
    try {
        // 默认参数为start
        String command = "start"; 
        if (args.length > 0) {
            command = args[args.length - 1];
        }

        if (command.equals("startd")) {
            ...
        } else if (command.equals("stopd")) {
            ...
        } 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")) {
           ...
        } else if (command.equals("configtest")) {
            ...                    
        } else {
            log.warn("Bootstrap: command \"" + command + "\" does not exist.");
        }
    } catch (Throwable t) {
        if (t instanceof InvocationTargetException &&
                t.getCause() != null) {
            t = t.getCause();
        }
        handleThrowable(t);
        t.printStackTrace();
        System.exit(1);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59

2、init

  • 本文对类加载器内容不做分析,后续看情况单独讲
  • 简单来说init就是反射实例化Catalina对象
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");
        
    // 通过catalinaLoader加载Catalina,反射实例化Catalina对象
    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;
   // 反射将sharedLoader设置为catalinaLoader的父类加载器,本文不做分析
    Method method =
        startupInstance.getClass().getMethod(methodName, paramTypes);
    method.invoke(startupInstance, paramValues);
	
	// 将catalina实例引用赋值
    catalinaDaemon = startupInstance;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

3、load与start

  • load与start都是通过上一步获取到的catalinaDaemon对象反射调用catalina类的load与start方法
  • 这两个过程我们会在下面的Catalina内容中介绍

load方法:

private void load(String[] arguments) throws Exception {
    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); 
    // 反射调用catalina的load方法,参数为null
    method.invoke(catalinaDaemon, param);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

start方法:

 public void start()
     throws Exception {
     if( catalinaDaemon==null ) init();
	 // 反射调用catalina的start方法,参数为null
     Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
     method.invoke(catalinaDaemon, (Object [])null);
 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

二、加载Catalina

  • 上文中Bootstrap类的load与start方法实质上就是反射调用catalina类的load与start方法

1、load

  • 创建Digester对象,解析conf/server.xml文件
  • 核心内容调用Server实现类StandardServer的init方法来初始化组件(下篇文章单独讲)
public void load() {
	// 如果已经加载则退出,默认false,下面会置为true
    if (loaded) {
        return;
    }
    loaded = true;

    initDirs();
    initNaming();

    // 创建Digester对象,用来解析server.xml文件
    Digester digester = createStartDigester();

    InputSource inputSource = null;
    InputStream inputStream = null;
    File file = null;

     try {
     	 // 加载conf目录下的server.xml文件
         file = configFile();
         inputStream = new FileInputStream(file);
         inputSource = new InputSource(file.toURI().toURL().toString());
     } catch (Exception e) {
		...
     }

     try {
         inputSource.setByteStream(inputStream);
         digester.push(this);
         // 开始解析conf/server.xml文件
         digester.parse(inputSource);
     } catch (SAXParseException spe) {
		...
     } 

	// server和catalina之间建立关联
	// Server接口实现类StandardServer是在解析server.xml文件时候创建
	// 当时StandardServer对象set到Catalina
	// 此时又将Catalinaset到StandardServer对象中
	// 形成:你中有我,我中有你
    getServer().setCatalina(this);
    getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
    getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());

	...

    // 初始化server,后面另开一篇单独讲
    try {
        getServer().init();
    } catch (LifecycleException e) {
		...
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53

Digester对象解析server.xml文件

  • 解析server.xml文件,根据和标签内容,创建Server和Service是实现类StandardServer和StandardService

在这里插入图片描述

2、start

  • 再来看下整个启动过程
  • Catalina的load方法最后一步getServer().init(),就是Server、Service、Engine等一系列组件的初始化

在这里插入图片描述

  • 核心内容调用server实现类StandardServer的start方法来启动服务器(下篇文章单独讲)
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();

    // 调用server的start方法来启动服务器
    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");
    }

    // 注册关闭钩子
    if (useShutdownHook) {
        if (shutdownHook == null) {
            shutdownHook = new CatalinaShutdownHook();
        }
        // 向addShutdownHook方法传入的Thread,其run方法即为自定义的shutdown时清理逻辑
        Runtime.getRuntime().addShutdownHook(shutdownHook);

        LogManager logManager = LogManager.getLogManager();
        if (logManager instanceof ClassLoaderLogManager) {
            ((ClassLoaderLogManager) logManager).setUseShutdownHook(
                    false);
        }
    }
	// 进入等待状态
	// 启动类Bootstrap默认调start方法设置await=true
    if (await) {
    	// main线程等待,等待接收shutdown命令,接受到则跳出阻塞
        await();
        // 跳出阻塞,执行Server.stop();
        stop();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55

2.1、注册shutdown钩子

  • 向addShutdownHook方法传入的Thread线程任务,其run方法即为自定义的shutdown时清理逻辑
  • JDK内部,是通过一个Map来保存所有添加的多个ShutdownHook,在被触发时执行
  • 那这个shutdownHook一般是在什么时候会被调用呢?
    • 程序正常退出,这发生在最后的非守护线程退出时,或者在调用exit(等同于 System.exit)方法时
    • 为响应用户中断而终止虚拟机,如键入^C或发生系统事件,比如用户注销或系统关闭
  • 在Java虚拟机退出的时候,这些设置的shutdownHook会被并行的调用
  • 相当于此线程等待着虚拟机正常退出,就会执行Catalina#stop方法
  • 整体逻辑下方Socket监听SHUTDOWN响应然后调用Catalina#stop方法一样

ps:对于非正常方式退出Java虚拟机,例如杀进程,系统断电等,这些情况下,shutdownHook不会被执行

protected class CatalinaShutdownHook extends Thread {
    @Override
    public void run() {
        try {
            if (getServer() != null) {
                Catalina.this.stop();
            }
        } catch (Throwable ex) {
			...
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

2.2、阻塞tomcat主线程

server.xml开头内容

在这里插入图片描述

  • Catalina的await方法实际是调用Server实现类StandardServer的await方法
public void await() {
    getServer().await();
}
  • 1
  • 2
  • 3
  • 阻塞tomcat主线程,只要stopAwait不为true, tomcat主线程在此无限循环
  • 监听到客户端发起SHUTDOWN命令后,退出阻塞,往下执行stop方法

在这里插入图片描述

// StandardServer类方法

private int port = 8005;

private String shutdown = "SHUTDOWN";

private volatile ServerSocket awaitSocket = null;

@Override
public void await() {
    // shutdown端口配置为-2,启动完Server直接再终止Server
    if( port == -2 ) {
        return;
    }
    // 配置为-1,则不再监听shutdown端口
    if( port==-1 ) {
        try {
            awaitThread = Thread.currentThread();
            while(!stopAwait) {
                try {
                    Thread.sleep( 10000 );
                } catch( InterruptedException ex ) {
                    // continue and check the flag
                }
            }
        } finally {
            awaitThread = null;
        }
        return;
    }

    // 开启socket监听server.xml中的shutdown端口
    // 创建socket服务端
    try {
        awaitSocket = new ServerSocket(port, 1,
                InetAddress.getByName(address));
    } catch (IOException e) {
        return;
    }
    
    // 默认false,进入while循环
    while (!stopAwait) {
        ServerSocket serverSocket = awaitSocket;
        if (serverSocket == null) {
            break;
        }

        // Wait for the next connection
        Socket socket = null;
        StringBuilder command = new StringBuilder();
        InputStream stream;
        try {
        	// accept阻塞监听端口
            socket = serverSocket.accept();
            // 设置阻塞超时时间10秒,如果超时抛异常,catch捕捉到重新进入while循环
            socket.setSoTimeout(10 * 1000);  
            stream = socket.getInputStream();
        } catch (SocketTimeoutException ste) {
            continue;
        }
		
		// 从流中读取字符串
		...
                
        // 如果读取到字符串命令是"SHUTDOWN"则,跳出循环,开始终止服务器
        // shutdown变量是取server.xml中Server的shutdown属性
        boolean match = command.toString().equals(shutdown);
        if (match) {
            log.info(sm.getString("standardServer.shutdownViaPort"));
            break;
        } else
            log.warn("StandardServer.await: Invalid command '"
                    + command.toString() + "' received");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75

2.3、停止Tomcat

  • 最终调用Server的stop和destroy方法(下篇文章单独讲)
public void stop() {
    try {
        if (useShutdownHook) {
        	// 移除shutdown钩子,这个stop方法会停止server,不需要钩子再次执行
            Runtime.getRuntime().removeShutdownHook(shutdownHook);
            
            LogManager logManager = LogManager.getLogManager();
            if (logManager instanceof ClassLoaderLogManager) {
                ((ClassLoaderLogManager) logManager).setUseShutdownHook(
                        true);
            }
        }
    } catch (Throwable t) {
        ExceptionUtils.handleThrowable(t);
    }

    // 调用Server的stop和destroy方法
    try {
        Server s = getServer();
        LifecycleState state = s.getState();
        if (LifecycleState.STOPPING_PREP.compareTo(state) <= 0
                && LifecycleState.DESTROYED.compareTo(state) >= 0) {
            // Nothing to do. stop() was already called
        } else {
            s.stop();
            s.destroy();
        }
    } catch (LifecycleException e) {
        log.error("Catalina.stop", e);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

三、总结

  • Bootstrap是一个启动引导类,本身没有太多启动关闭细节的实现
  • 而是通过加载Catalina,对Catalina发号施令,调用start、stop等方法

在这里插入图片描述

文章知识点与官方知识档案匹配,可进一步学习相关知识
Java技能树首页概览147154 人正在系统学习中
注:本文转载自blog.csdn.net的冬天vs不冷的文章"https://blog.csdn.net/qq_35512802/article/details/135490769"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top