zoukankan      html  css  js  c++  java
  • Tomcat组件梳理--Catalina

    Tomcat组件梳理--Catalina

    1.定义和功能

    Catalina是Tomcat的核心组件,是Servlet容器,Catalina包含了所有的容器组件,其他模块均为Catalina提供支撑。通过Coyote模块提供连接通信,Jasper模块提供JSP引擎,Naming提供JNDI服务,Juli提供日志服务。结构如下:

    主要的功能包括接收请求,处理请求,返回结果。但是这些具体的实现是在catalina里面的子容器里面,我们在对应的文章里面讲解,此处聚焦在Catalina的源代码提供的功能上。

    处理上面这些,Catalina还提供启动入口,关闭入口等。

    2.属性

    //org.apache.catalina.startup.Catalina
    /**
     * 用于await的flag
     */
    protected boolean await = false;
    
    /**
     * Server配置的文件路
     */
    protected String configFile = "conf/server.xml";
    
    /**
     * The shared extensions class loader for this server.
     * 此server的shared 类加载器
     */
    protected ClassLoader parentClassLoader =
      Catalina.class.getClassLoader();
    
    /**
     * Server组件
     */
    protected Server server = null;
    
    /**
     * 使用shutdown钩子的flag
     */
    protected boolean useShutdownHook = true;
    
    /**
     * Shutdown钩子实例
     */
    protected Thread shutdownHook = null;
    
    /**
     * 默认需要开启Naming
     */
    protected boolean useNaming = true;
    
    /**
     * 预防重复加载的标记字段
     */
    protected boolean loaded = false;
    

    有几个主要的属性。

    • Catalina的子组件Server,通过digster工具解析server.xml文件构造该对象。
    • 用户shutdown时的钩子,是否使用以及调用的线程
    • 是否需要启动JNDI的标识
    • Server.xml配置文件的地址
    • stop用的await标记
    • 父类加载器。

    3.操作

    Catalina的操作有比较明显的区分,因为主要是处理来自shell的不同命令,所以,根据shell的传入的命令行,我们可以看到Catalina主要处理来自shell的start和stop命令。下面来解析start命令和stop命令的背后,以及Tomcat中提供的一个对xml解析很有用的库Digester。

    再看一下Bootstrap类中的main方法中对shell命令的处理,可以看到start时主要调用load(args)和start()方法,stop时主要调用stopServer(args)方法。

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

    3.1.执行shell的start命令

    3.1.1.调用load(args)方法

    现在我们知道Shell的start命令交给Catalina处理时,实际上调用了load(args)和start()方法。我们先看load(args)方法

    //org.apache.catalina.startup.Catalina#load(java.lang.String[])
    /*
     * 使用参数进行load
     */
    public void load(String args[]) {
    
        try {
            //1.根据传入的参数设置Catalina的一些属性的值
            if (arguments(args)) {
              //2.调用无参的load()方法
                load();
            }
        } catch (Exception e) {
            e.printStackTrace(System.out);
        }
    }
    

    可以看到这里有两个业务逻辑:

    • 1.根据传入的参数设置Catalina的一些属性,这些属性主要就是Naming等一些值。
    • 2.调用午餐的load()方法

    再看load()方法,去掉里面的异常和环境检查,可以看到主要逻辑如下:

    //org.apache.catalina.startup.Catalina#load()
    
    /**
     * 准备好环境,解析好server.xml文件生成好对象。server对象也准备好,然后调用server的init方法
     */
    public void load() {
    
        //1.检查java.io.tmpdir是有有效
        initDirs();
    
        //2.设置catalina.useNaming的系统参数
        initNaming();
    
        //3.用digester解析server.xml文件,把配置文件中的配置解析成java对象
        //3.1.准备好用来解析server.xml文件需要用的digester。
        Digester digester = createStartDigester();
        //3.2.server.xml文件作为一个输入流传入
        File file = configFile();
        InputStream inputStream = new FileInputStream(file);
        //3.3.使用inputStream构造一个sax的inputSource
        InputSource inputSource = new InputSource(file.toURI().toURL().toString());
        inputSource.setByteStream(inputStream);
        //3.4.把当前类压入到digester的栈顶,用来作为digester解析出来的对象的一种引用
        digester.push(this);
        //3.5.调用digester的parse()方法进行解析。
        digester.parse(inputSource);
    
        //4.为子组件Server设置一些值
        getServer().setCatalina(this);
        getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
        getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());
        
        //执行server的init方法,start方法的准备方法
        getServer().init();
    
    }
    

    分析一下主要逻辑

    • 1.检查java.io.tmpdir是有有效,这个没啥好说的。

    • 2.设置catalina.useNaming的系统参数,这个也没啥好多的,等主要流程梳理完,咱们做一个JDNI的源码解析。

    • 3.用digester解析server.xml文件,把配置文件中的配置解析成java对象。该过程需要经过5个步骤才可以。

      • 3.1.准备好用来解析server.xml文件需要用的digester。
      • 3.2.读取server.xml文件作为一个输入流。
      • 3.3.使用inputStream构造一个sax的inputSource,因为digester底层用的是sax去解析的。
      • 3.4.把当前类压入到digester的栈顶,用来作为digester解析出来的对象的一种引用,digester自带一个栈的结构。
      • 3.5.调用digester的parse()方法进行解析。前面几步都是在准备环境,这里才是正真的去解析了。
    • 4.为子组件Server设置一些值,并调用server的init方法,start方法的准备方法。

    这里面最值得说的应该就是第3步,通过Digester去解析xml文件,每遇到一个匹配的节点,都可以添加一个对应的事件。对Digester的操作都是一些普通的API操作,这里就不解释了,可以查看官方文档,或者查看博客,API挺简单的。

    3.1.2.调用start()方法

    start()方法主要作用就是调用Server的start()方法,并将一个shutdown的钩子加到JVM中。

    主要的逻辑如下:

    • 1.执行Server的start()方法,如果执行失败,就调用Server的destroy()方法
    • 2.注册一个shutdown的钩子
    • 3.等待处理如何停止的问题

    比较有意思的是3,如何停止一个服务,这种方法比较有意思,不过我们放在Server组件中去讲,因为实现是放在Server中的。

    //org.apache.catalina.startup.Catalina#start
    
    public void start() {
    
        //1.执行Server的start()方法,如果执行失败,就调用Server的destroy()方法
        try {
            //执行server的start方法
            getServer().start();
        } catch (LifecycleException e) {
            try {
                //如果start失败,就调用server的destroy方法
                getServer().destroy();
            } catch (LifecycleException e1) {
                log.debug("destroy() failed for failed Server ", e1);
            }
            return;
        }
    
        //2.注册一个shutdown的钩子
        if (useShutdownHook) {
            if (shutdownHook == null) {
                shutdownHook = new CatalinaShutdownHook();
            }
            Runtime.getRuntime().addShutdownHook(shutdownHook);
            
        }
    
        //3.等待处理如何停止的问题
        if (await) {
            await();
            stop();
        }
    }
    

    在第2步的逻辑中,new了一个类去注册,该类的主要处理逻辑如下:

    protected class CatalinaShutdownHook extends Thread {
    
        @Override
        public void run() {
            try {
                if (getServer() != null) {
                    //1.调用Catalina的stop()方法
                    Catalina.this.stop();
                }
            } catch (Throwable ex) {
                ExceptionUtils.handleThrowable(ex);
                log.error(sm.getString("catalina.shutdownHookFail"), ex);
            } finally {
                // If JULI is used, shut JULI down *after* the server shuts down
                // so log messages aren't lost
                LogManager logManager = LogManager.getLogManager();
                if (logManager instanceof ClassLoaderLogManager) {
                    ((ClassLoaderLogManager) logManager).shutdown();
                }
            }
        }
    }
    

    可以看到,只是去调用Catalina的stop()方法。不过这总调用方法也是比较奇特的,通过Catalina.this.stop()方法,不知道这种是不是可以跨线程调用一个类的实例,如果能,那将是一个很棒。

    [补充]:Catalina.this方法是内部类调用外部类的方法。可以用来解决线程之间传递示例的问题。

    3.2.执行shell的stop命令

    shell的stop命令落在Catalina上只是去调用stopServer(args)方法,具体方法实现如下:

    public void stopServer(String[] arguments) {
    
        //1.参数设置
        if (arguments != null) {
            arguments(arguments);
        }
    
        Server s = getServer();
        //2.如果Server存在,就调用Server的stop和destroy方法进行关闭,
        if (s == null) {
            // Create and execute our Digester
            Digester digester = createStopDigester();
            File file = configFile();
            try (FileInputStream fis = new FileInputStream(file)) {
                InputSource is =
                    new InputSource(file.toURI().toURL().toString());
                is.setByteStream(fis);
                digester.push(this);
                digester.parse(is);
            } catch (Exception e) {
                log.error("Catalina.stop: ", e);
                System.exit(1);
            }
        } else {
            // Server object already present. Must be running as a service
            try {
                s.stop();
                s.destroy();
            } catch (LifecycleException e) {
                log.error("Catalina.stop: ", e);
            }
            return;
        }
    
        // 3.如果Server不存在,就重新解析server.xml文件构造server,然后通过socket发送shutdown命令关闭
        s = getServer();
        if (s.getPort()>0) {
            try (Socket socket = new Socket(s.getAddress(), s.getPort());
                    OutputStream stream = socket.getOutputStream()) {
                String shutdown = s.getShutdown();
                for (int i = 0; i < shutdown.length(); i++) {
                    stream.write(shutdown.charAt(i));
                }
                stream.flush();
            } catch (Exception ce) {
                ce.printStackTrace();
            }
                
        } else {
            log.error(sm.getString("catalina.stopServer"));
            System.exit(1);
        }
    }
    

    主要的业务逻辑有三个:

    • 1.参数设置
    • 2.如果Server存在,就调用Server的stop和destroy方法进行关闭
    • 3.如果Server不存在,就重新解析server.xml文件构造server,然后通过socket发送shutdown命令关闭

    关闭Server的具体实现,我们放在Server组件中。

    4.总结

    我们根据用户的调用,梳理了Catalina对start和stop命令行的相应方法。其中Digester对xml文件的解析时值得注意的,停止Server的方式也比较有意思,但是我们放在Server中解析。

  • 相关阅读:
    进程间通信
    图形的保存与重绘
    mysql记录1
    文件操作
    多线程及聊天室程序
    健康是成功之本
    文档与串行化
    HTML网页制作基础
    Hook和数据库访问
    C++基础笔记1
  • 原文地址:https://www.cnblogs.com/cenyu/p/11072543.html
Copyright © 2011-2022 走看看