zoukankan      html  css  js  c++  java
  • 1.Tomcat组件梳理—Bootstrap启动器

    Tomcat组件梳理—Bootstrap启动器

    一开始是直接从Server开始做梳理的,但是发现有很多东西是从Catalina传输过来的,Catalina又是从Bootstrap启动的,所以还是回过头来从Bootstrap开始梳理吧。

    1.定义和功能

    Bootstrap是Tomcat的入口类,main方法也在这个类中,脚本启动往往也是直接调用这个类。

    该类作为启动整个Tomcat的启动器,有自己的一些特点,该类主要操作对象是Catalina类,但是这两个又是解耦的,解耦的方法是通过反射去获取Catalina的实例和方法,并调用。这样,Bootstrap和Catalina是隔离开的,因此如果有需要,也是可以自己实现一个启动器的。

    Bootstrap是Catalina的装载器,并负责创建Tomcat自用的类加载器。

    2.启动器的属性

    先看Bootstrap的属性有哪些,属性是一个类的基本特征,一个类的具体实现就是由属性和功能方法来实现的。

    public final class Bootstrap {
    
      /**
         * main使用的守护对象
         */
      private static Bootstrap daemon = null;
    
      //CatalinaBase的File对象
      private static final File catalinaBaseFile;
      //CatalinaHome的File对象
      private static final File catalinaHomeFile;
    
    }
    

    需要解释一下CatalinaBase和CatalinaHome

    我们先提出一个需求:如果要在一台机器上部署多个Tomcat,必须要在一个台机器上安装多个tomcat吗?虽然直接解压就能用,但是tomcat有很多压缩包和公用的目录文件,都不能公用吗?

    答案是:能。

    那怎么实现呢?就是先确认tomcat的哪些目录能被共享,哪些目录不能被共享,然后共享的所有tomcat实例都能访问,不被共享的只能被自己访问。那现在来看看哪些能被共享吧。

    tomcat解压后,除了一些文件,有以下目录:

    • bin —运行脚本
    • conf —配置文件
    • lib —核心库文件
    • logs --日志目录
    • temp --临时目录
    • webapps —自动装载的应用程序目录
    • work --JVM临时文件目录(java.io.tmpdir)

    在这些目录中,只有bin和lib目录能被多个tomcat实例公用,于是我们把这两个目录的父级目录叫做安装目录,并用catalina.home来表示。

    在目录中,被tomcat实例所私有的目录为:conf,logs,temp,webapps,work。于是我们把这些目录的父级目录叫做工作目录,并用catalina.base来表示。

    到这里,我们应该已经明白了catalina.homecatalina.base两个参数的作用了。这两个参数只在用这种方法来做单机器多实例的时候才有用,但是现在微服务即使部署多个实例,也会直接放多个tomcat,因为磁盘的价格已经很便宜了,可以不用复杂度来换区空间。

    所以,如果是一个tomcat,这两个属性指向的是相同位置,即都是这些目录的父级目录。

    到这里,上面我们提出的问题,基本就被解决。

    参考了文章:https://blog.csdn.net/duqian94/article/details/52460703

    3.启动器的功能

    3.1.Main方法解析

    还是先看Main方法的代码吧

    /**
     * main方法,整个程序的主入口。
     */
    public static void main(String args[]) {
    
      if (daemon == null) {
    
        //1.new过程中主要执行static中的设置Catalina.home和Catalina.base的值
        Bootstrap bootstrap = new Bootstrap();
        try {
          //2.主要完成类加载器的初始化,包括common,catalina,share三个类加载器,
          // 并设置catalina的父类加载器。
          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];
        }
    
        //3.判断shell传入的值,执行对应的动作
        if (command.equals("startd")) {
          //执行start方法的内容,主要为执行Catalina的load()和start()方法
          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) {
        handleThrowable(t);
        t.printStackTrace();
        System.exit(1);
      }
    
    }
    

    Bootstrap中的main()方法时tomcat软件的入口方法,不管用什么方法启动tomcat,都是在调用这个方法。

    该方法的业务逻辑比较简单,只有三个点:

    • 1.new一个Bootstrap实例,在new的过程中有一段static的代码段会一起初始化,该代码段主要解决catalina.homecatalina.base参数的值,因为代码中会遇到这些变量,所以要去这些变量一定是在的。
    • 2.执行init()方法,该方法主要用来创建common,shared,catalinaLoader三个类加载器,并将对应的jar包加载进来。
    • 3.判断传入的参数是什么,然后通过反射调用Catalina中对应的方法。

    3.2.new方法执行静态代码块

    这里的代码就是在new时,需要初始化的代码。这里的代码逻辑没啥好说的,就是在检查参数,然后重新设值。放一下代码看看。

    static {
            //该段主要设置Catalina.home路径和Catalina.base路径
            // 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.3.init方法创建类加载器

    init()方法主要用来创建三个类加载器,并指定catalina的parent类加载器,看下代码中的处理逻辑。

    public void init() throws Exception {
    
      //1.初始化类加载器:commonLoader,catalinaLoader,sharedLoader
      initClassLoaders();
    
      //2.设置当前线程的类加载器
      Thread.currentThread().setContextClassLoader(catalinaLoader);
    
      //3.设置Java SecutityManager,目前看没啥用?
      SecurityClassLoad.securityClassLoad(catalinaLoader);
    
    
    
      //4.加载Catalina类,并为Catalina设置父类加载器
      Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
      Object startupInstance = startupClass.getConstructor().newInstance();
    
    
      //通过反射调用catalina.setParentClassLoader(ClassLoader classloader)方法,
      // 目的是设置Catalina的父类加载器为sharedLoader类加载器
      String methodName = "setParentClassLoader";
      Class<?> paramTypes[] = new Class[1];
      paramTypes[0] = Class.forName("java.lang.ClassLoader");
      Object paramValues[] = new Object[1];
      paramValues[0] = sharedLoader;
      //反射找到对应的方法时,需要指明两个参数:1.方法名称,2.方法对应的参数类型的列表,数组形式
      Method method =
        startupInstance.getClass().getMethod(methodName, paramTypes);
      method.invoke(startupInstance, paramValues);
    
      //5.把catalinaDaemon的值设置为org.apache.catalina.startup.Catalina
      catalinaDaemon = startupInstance;
    
    }
    
    

    先梳理此处的逻辑:

    • 1.创建三个类加载器:commonLoadercatalinaLoadershareLoader,这三个类加载器的目的不一样。
      • commonLoader:Tomcat最基本的类加载器,直接继承systemLoader,加载路径中的class可以被tomcat容器本身以及各个webapp访问。加载路径为:common.loader,默认执行${catalina.home}/lib下的包。
      • catalinaLoader:tomcat容器私有的类加载器,加载路径中的class对webapp不可见。加载路径为server.loader,默认为空。
      • sharedLoader:各个webapp共享的类加载器,加载路劲中的class对于多有webapp可见,但是对于tomcat容器不可见。加载路径为shared.loader,默认为空。
    • 2.把当前线程的类加载器设置为catalinaLoader.即当前线程只会加载tomcat私有的jar包。当前线程启动所有的组件,然后后面又会被阻塞,用来等待shutdown命令。
    • 3.设置Java 的SecutityManager,这个东西讲不清楚是干嘛,可以先不管。
    • 4.通过反射实例化Catalina类,并调用setParentClassLoader方法,为catalina设置ParentClassLoader属性。

    其实从上面和代码中都可以看到,最重要的是就是第一步,创建三个类加载器了。

    3.4.反射调用catalina的方法

    在main方法的第三步中,根据传入的参数,选择对应的方法,此处虽然调用的是Bootstrap的方法,但是实际上时在通过放射调用Catalina中的方法,可以看一个代码示例:

    private void load(String[] arguments) throws Exception {
      //主要是通过反射调用Catalina的load()方法
    
      // 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);
    
    }
    

    可以看到代码,其实就是通过反射调用Catalina的load()方法。

    3.5.操作Catalina的方法总结

    Bootstrap在代码中对Catalina的操作,我们总结一下看看。

    第一个是实例化Catalina

    //org.apache.catalina.startup.Bootstrap#init()
    //4.加载Catalina类,并为Catalina设置父类加载器
    Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
    Object startupInstance = startupClass.getConstructor().newInstance();
    
    

    第二个是调用方法,设置Catalina的parentClassLoader为sharedLoader

    //通过反射调用catalina.setParentClassLoader(ClassLoader classloader)方法,
    // 目的是设置Catalina的父类加载器为sharedLoader类加载器
    String methodName = "setParentClassLoader";
    Class<?> paramTypes[] = new Class[1];
    paramTypes[0] = Class.forName("java.lang.ClassLoader");
    Object paramValues[] = new Object[1];
    paramValues[0] = sharedLoader;
    //反射找到对应的方法时,需要指明两个参数:1.方法名称,2.方法对应的参数类型的列表,数组形式
    Method method =
      startupInstance.getClass().getMethod(methodName, paramTypes);
    method.invoke(startupInstance, paramValues);
    

    第三个是调用Catalina的其他方法,主要接解决对应的启动或者关闭的命令

    //org.apache.catalina.startup.Bootstrap#start
    public void start() throws Exception {
      //主要调用Catalina的start()方法
      if( catalinaDaemon==null ) {
        init();
      }
    
      Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
      method.invoke(catalinaDaemon, (Object [])null);
    
    }
    

    4.总结

    再明确一次Bootstrap的作用,一个是检查当前环境,二是创建三个类加载器,三是根据用户输入的参数,通过反射调用对应的方法。

    环境检查在3.2中讲解的,类加载器在3.3中讲解的,根据参数执行方法在3.4.中讲解的。

    以上,我们对Tomcat的启动器组件Bootstrap的分析就结束了。

  • 相关阅读:
    频率组件
    Django-admin组件
    Python全栈开发课堂笔记_day03
    python全栈开发day02
    python全栈开发day01
    正确认知自己,做真实的自己
    翻出大学时期收集的文章来看看
    mybatis中的#{}和${}
    Parameter index out of range (2 > number of parameters, which is 1)
    中间件
  • 原文地址:https://www.cnblogs.com/cenyu/p/11072540.html
Copyright © 2011-2022 走看看