• Tomcat中的服务器组件和 服务组件


      开始学习Tocmat时,都是学习如何通过实例化一个连接器 和 容器 来获得一个Servlet容器,并将连接器  和 servlet容器相互关联,但是之前学习的都只有一个连接器可以使用,该连接器服务8080端口上的HTTP请求,无法添加另一个连接器来服务 诸如 HTTPS之类的其他请求,而且前面所有学的示例,都缺少一种启动或者 关闭servlet容器的机制,那么下面学习一下提供这两种机制的特性的组件,分别是服务器组件 和 服务组件。

      服务器组件.

      org.apahce.catalina.Server接口的实例 表示Catalina的整个Servlet引擎,囊括了所有的组件,服务器组件是非常有用的,因为它使用了一种方法很容易的就启动/关闭整个系统,不需要对连接器 和 servlet容器 分别操作。

    下面来说明一下启动/关闭机制的具体工作原理,当启动服务器组件是,它会启动它囊括的所有组件,然后它就无期限的等待关闭命令,如果想要关闭系统,可以向指定的端口发送一条关闭命令,服务器组件接收到关闭命令之后,就会关闭其中所有的组件。

      服务器组件使用了另一个组件(即服务组件)来包含其他组件,如一个容器组件 和 一个或者多个连接器组件,

    下面先来看一下服务器组件的接口定义

      1 package org.apache.catalina;
      2 
      3 import org.apache.catalina.deploy.NamingResources;
      4 
      5 /**
      6  * 
      7  * <p>
      8  * <b>Title:Server.java</b>
      9  * </p>
     10  * <p>
     11  * Copyright:ChenDong 2018
     12  * </p>
     13  * <p>
     14  * Company:仅学习时使用
     15  * </p>
     16  * <p>
     17  * 类功能描述:Server元素表示整个Catalina
     18  * servlet容器。它的属性代表了servlet容器的整体特性。服务器可以包含一个或多个服务以及顶级命名资源集。
     19  * 
     20  * 
     21  * 
     22  * 通常,该接口的实现还将实现生命周期,这样当调用start()和stop()方法时,所有定义的服务也会启动或停止。
     23  * 
     24  * 
     25  * 
     26  * 在这两者之间,实现必须打开端口属性指定的端口号上的服务器套接字。当接受连接时,读取第一行并与指定的关闭命令进行比较。如果命令匹配,则启动服务器关闭。
     27  * </p>
     28  * 
     29  * @author 
     30  * @date 2018年12月17日 下午8:04:51
     31  * @version 1.0
     32  */
     33 
     34 public interface Server {
     35 
     36     // ------------------------------------------------------------- Properties
     37 
     38     /**
     39      * 
     40      * 
     41      * <p>
     42      * Title: getInfo
     43      * </p>
     44      * 
     45      * @date 2018年12月17日 下午8:05:15
     46      * 
     47      *       <p>
     48      *       功能描述:返回该类的实现信息
     49      *       </p>
     50      * 
     51      * @return
     52      */
     53     public String getInfo();
     54 
     55     /**
     56      * 返回全局命名资源。
     57      */
     58     public NamingResources getGlobalNamingResources();
     59 
     60     /**
     61      * 
     62      * 设置全局命名资源
     63      * 
     64      * @param namingResources
     65      *            新的全局命名资源
     66      */
     67     public void setGlobalNamingResources(NamingResources globalNamingResources);
     68 
     69     /**
     70      * 
     71      * 
     72      * <p>
     73      * Title: getPort
     74      * </p>
     75      * 
     76      * @date 2018年12月17日 下午8:06:42
     77      * 
     78      *       <p>
     79      *       功能描述:返回我们用来监听关闭命令的端口号
     80      *       </p>
     81      * 
     82      * @return
     83      */
     84     public int getPort();
     85 
     86     /**
     87      * 
     88      * 
     89      * <p>
     90      * Title: setPort
     91      * </p>
     92      * 
     93      * @date 2018年12月17日 下午8:07:27
     94      * 
     95      *       <p>
     96      *       功能描述:设置我们用来监听关闭命令的端口号
     97      *       </p>
     98      * 
     99      * @param port
    100      *            新的监听端口号
    101      */
    102     public void setPort(int port);
    103 
    104     /**
    105      * 返回我们正在等待的关闭命令字符串。
    106      * 
    107      * 
    108      */
    109     public String getShutdown();
    110 
    111     /**
    112      * 设置我们正在等待的关闭命令字符串
    113      *
    114      * @param shutdown
    115      *            新的关闭命令字符串
    116      */
    117     public void setShutdown(String shutdown);
    118 
    119     // --------------------------------------------------------- Public Methods
    120 
    121     /**
    122      * 向定义的服务集添加新服务。
    123      *
    124      * @param service
    125      *            要被添加的新服务
    126      */
    127     public void addService(Service service);
    128 
    129     /**
    130      * 等待直到收到正确的关闭命令,然后返回。
    131      */
    132     public void await();
    133 
    134     /**
    135      * 
    136      * 
    137      * <p>
    138      * Title: findService
    139      * </p>
    140      * 
    141      * @date 2018年12月17日 下午8:10:42
    142      * 
    143      *       <p>
    144      *       功能描述:返回指定的服务(如果存在);否则返回<code>null</code>
    145      *       </p>
    146      * 
    147      * @param name
    148      * @return
    149      */
    150     public Service findService(String name);
    151 
    152     /**
    153      * 
    154      * 
    155      * <p>
    156      * Title: findServices
    157      * </p>
    158      * 
    159      * @date 2018年12月17日 下午8:11:18
    160      * 
    161      *       <p>
    162      *       功能描述:返回在此服务器中定义的服务集。
    163      *       </p>
    164      * 
    165      * @return
    166      */
    167     public Service[] findServices();
    168 
    169     /**
    170      * 
    171      * 
    172      * <p>
    173      * Title: removeService
    174      * </p>
    175      * 
    176      * @date 2018年12月17日 下午8:12:02
    177      * 
    178      *       <p>
    179      *       功能描述:从该服务器 定义的服务集中删除指定的服务
    180      *       </p>
    181      * 
    182      * @param service
    183      */
    184     public void removeService(Service service);
    185 
    186     /**
    187      * 
    188      * 
    189      * <p>
    190      * Title: initialize
    191      * </p>
    192      * 
    193      * @date 2018年12月17日 下午8:12:58
    194      * 
    195      *       <p>
    196      *       功能描述:调用启动前的初始化。这用于允许连接器绑定到Unix操作环境中的受限端口。当然只要是在系统执行前 你爱写啥写啥
    197      *       </p>
    198      * 
    199      * @throws LifecycleException
    200      */
    201     public void initialize() throws LifecycleException;
    202 }

      shutdown属性保存了必须发送给Server实例用来关闭整个系统的关闭命令,port属性定义了服务器组件会从哪一个端口获取关闭命令,可以调用其addService方法为服务器组件添加服务组件,或者通过removeService方法来删除某个服务组件,findService方法返回添加到此服务器组件中的服务集合,initialize方法包含了在系统启动之前要执行的一些代码

      在Catalina中 server同样有其标准实现类,StandardServer

    StandardServer类

      org.apahce.catalina.core.StandardServer类是Server接口的标准实现,为什么要说下这个类,主要是对其中的关闭机制 也就上文提到的一个关闭所有组件的机制感兴趣,而这也是这个类最重要的特性,该类中的许多方法都与新server.xml文件中的服务器配置的存储相关,但这些并不是下面要说的重点,

      一个服务器组件可以有0个或者多个服务组件,StandardServer类提供了addService方法、removeService方法、和findService方法的实现。

      StandardServer类有四个与生命周期相关的方法,分别是initialize方法、start方法、stop方法,就像其他组件一样,可以初始化并且启动服务器组件。也可以调用 await方法 和 stop方法,调用await方法会一直阻塞住,直到它从定义 的 8005;端口(也可以自己配置其他端口)上接收到关闭命令,当await方法返回的时候,会运行stop方法来关闭其下的所有服务组件。

    下面会分别讨论上面的 四个与生命周期相关的方法。

    initialize方法

      方法主要是用于初始化添加到StandardServer的服务组件,

     1 /**
     2      * 调用启动前的初始化。
     3      */
     4     public void initialize() throws LifecycleException {
     5         if (initialized)
     6             throw new LifecycleException(sm.getString("standardServer.initialize.initialized"));
     7         initialized = true;
     8 
     9         // 初始化我们定义的服务集中的每一个服务
    10         for (int i = 0; i < services.length; i++) {
    11             services[i].initialize();
    12         }
    13     }

    start方法

      start方法用于启动服务器组件,在StandardServer类的start方法的实现中,它会启动器所有的服务组件,逐个启动所有的组件,如连接器组件 和 Servlet容器,

     1 /**
     2      * 该服务器组件的启动方法,它会启动器所有的服务组件,逐个启动所有的组件,
     3      */
     4 
     5     public void start() throws LifecycleException {
     6 
     7         // 验证和更新当前组件状态 如果已经启动直接报错
     8         if (started)
     9             throw new LifecycleException(sm.getString("standardServer.start.started"));
    10         // 向监听器发送 BEFORE_START_EVENT事件 与 START_EVENT事件
    11         lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);
    12 
    13         lifecycle.fireLifecycleEvent(START_EVENT, null);
    14         started = true;
    15 
    16         // 逐个启动当前定义的服务集合中的每一个服务
    17         synchronized (services) {
    18             for (int i = 0; i < services.length; i++) {
    19                 if (services[i] instanceof Lifecycle)
    20                     ((Lifecycle) services[i]).start();
    21             }
    22         }
    23 
    24         // 向监听器发送 AFTER_START_EVENT事件
    25         lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);
    26 
    27     }

    StandardServer为了防止服务器组件重复启动,在start中设置为true 在stop方法中 重置为false。

    stop方法

      stop方法用于关闭服务器组件,在方法中会关闭服务器中的额所有组件

     1     /**
     2      * 优雅地终止该组件的公共方法的主动使用。此方法应该是调用该组件的给定实例的最后一个方法。
     3      * 它还应该向任何注册的监听器发送STOP_EVENT类型的停止事
     4      */
     5     public void stop() throws LifecycleException {
     6 
     7         // 如果还没有启动则 直接抛出错误
     8         if (!started)
     9             throw new LifecycleException(sm.getString("standardServer.stop.notStarted"));
    10 
    11         // 向监听器发送监听事件
    12         lifecycle.fireLifecycleEvent(BEFORE_STOP_EVENT, null);
    13 
    14         lifecycle.fireLifecycleEvent(STOP_EVENT, null);
    15         // 将started标志 置为false 这样才可以再次启动
    16         started = false;
    17 
    18         // 逐个停止我们定义服务集中的每一个服务
    19         for (int i = 0; i < services.length; i++) {
    20             if (services[i] instanceof Lifecycle)
    21                 ((Lifecycle) services[i]).stop();
    22         }
    23 
    24         // 发送监听事件
    25         lifecycle.fireLifecycleEvent(AFTER_STOP_EVENT, null);
    26 
    27     }

    调用stop方法hi关闭所有的服务组件并重置布尔变量started,这样才可以再次启动服务器组件。

    await方法

      await方法负责等待关闭整个Tomcat部署的命令。

     1     /**
     2      * 等待直到收到正确的关闭命令,然后返回。
     3      */
     4     public void await() {
     5 
     6         // 设置要等待的服务器套接字
     7         ServerSocket serverSocket = null;
     8         try {
     9             // 利用咱们自己定义的port(默认 8005 )端口号来 初始化服务器套接字
    10             serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));
    11         } catch (IOException e) {
    12             System.err.println("StandardServer.await: create[" + port + "]: " + e);
    13             e.printStackTrace();
    14             System.exit(1);
    15         }
    16 
    17         // 循环等待连接和有效命令
    18         while (true) {
    19 
    20             // 等待下一个连接
    21             Socket socket = null;
    22             InputStream stream = null;
    23             try {
    24                 socket = serverSocket.accept();//
    25                 socket.setSoTimeout(10 * 1000); // 接收到Socket之后 read方法仅在十秒钟之内有效
    26                                                 // 超时 将会抛出
    27                                                 // java.net.SocketTimeoutException
    28                 stream = socket.getInputStream();
    29             } catch (AccessControlException ace) {
    30                 System.err.println("StandardServer.accept security exception: " + ace.getMessage());
    31                 continue;
    32             } catch (IOException e) {
    33                 System.err.println("StandardServer.await: accept: " + e);
    34                 e.printStackTrace();
    35                 System.exit(1);
    36             }
    37 
    38             // 从套接字中读取一组字符
    39             StringBuffer command = new StringBuffer();
    40             int expected = 1024; // 切断以避免DoS攻击
    41             while (expected < shutdown.length()) {
    42                 if (random == null)
    43                     random = new Random(System.currentTimeMillis());
    44                 expected += (random.nextInt() % 1024);
    45             }
    46             while (expected > 0) {
    47                 int ch = -1;
    48                 try {
    49                     ch = stream.read();
    50                 } catch (IOException e) {
    51                     System.err.println("StandardServer.await: read: " + e);
    52                     e.printStackTrace();
    53                     ch = -1;
    54                 }
    55                 if (ch < 32) // 控制字符或EOF终止循环
    56                     break;
    57                 command.append((char) ch);
    58                 expected--;
    59             }
    60 
    61             // 既然我们用完了,就把socket关上。
    62             try {
    63                 socket.close();
    64             } catch (IOException e) {
    65                 ;
    66             }
    67 
    68             // 匹配命令字符串
    69             boolean match = command.toString().equals(shutdown);
    70             if (match) {
    71                 //匹配到就 跳出循环
    72                 break;
    73             } else
    74                 System.err.println("StandardServer.await: Invalid command '" + command.toString() + "' received");
    75 
    76         }
    77 
    78         //关闭服务器套接字并返回
    79         try {
    80             serverSocket.close();
    81         } catch (IOException e) {
    82             ;
    83         }
    84 
    85     }

       await方法创建一个ServerSocket对象,监听8005端口,并在while循环中调用它的accpect方法挡在指定端口上接收到消息时,才会从accept方法中返回一个socket,然后将接收到的消息与关闭命令字符串做比较,相同的话就跳出循环,关闭ServerSocke,否则再次循环。接续等待消息。

    Service

      服务组件是org.apche.catalina.Service接口的实例,一个服务组件可以有一个servlet容器和 一个或者多个连接器实例,可以自由的把连接器实例添加到服务组件中,所有的连接器都会与这个servlet容器相关联,

    package org.apache.catalina;
    
    /**
     * 
     * <p>
     * <b>Title:Service.java</b>
     * </p>
     * <p>
     * Copyright:ChenDong 2018
     * </p>
     * <p>
     * Company:仅学习时使用
     * </p>
     *
     * 
     * <p>
     * 服务组件是org.apache.catalina.Service接口的实例,一个服务组件可以有一个servlet容器和
     * 多个连接器实例,可以自由的把连接器实例添加到服务组件中的,所有的 连接器都会与这个servlet容器相关联
     * </p>
     * 
     * @author 
     * @date 2018年12月17日 下午8:02:49
     * @version 1.0
     */
    
    public interface Service {
    
        // ------------------------------------------------------------- Properties
    
        /**
         * 
         * 
         * <p>
         * Title: getContainer
         * </p>
         * 
         * @date 2018年12月19日 下午7:47:23
         * 
         *       <p>
         *       功能描述: 返回处理与此服务关联的所有<code>Connectors</code> 请求的servlet容器。
         *       </p>
         * 
         * @return
         */
        public Container getContainer();
    
        /**
         * 
         * 
         * <p>
         * Title: setContainer
         * </p>
         * 
         * @date 2018年12月19日 下午7:48:10
         * 
         *       <p>
         *       功能描述:设置处理此服务中所有连接器的servlet容器
         *       </p>
         * 
         * @param container
         *            被设置的servlet容器
         */
        public void setContainer(Container container);
    
        /**
         * 
         * 
         * <p>
         * Title: getInfo
         * </p>
         * 
         * @date 2018年12月19日 下午7:49:07
         * 
         *       <p>
         *       功能描述:返回该类的实现信息
         *       </p>
         * 
         * @return
         */
        public String getInfo();
    
        /**
         * 返回此服务的名称.
         */
        public String getName();
    
        /**
         * 设置此服务的名称
         *
         * @param name
         *            新的服务名
         */
        public void setName(String name);
    
        /**
         * Return the <code>Server</code> with which we are associated (if any).
         */
        /**
         * 
         * 
         * <p>
         * Title: getServer
         * </p>
         * 
         * @date 2018年12月19日 下午7:51:23
         * 
         *       <p>
         *       功能描述:返回与该服务关联的服务器组件
         *       </p>
         * 
         * @return
         */
        public Server getServer();
    
        /**
         * 
         * 
         * <p>
         * Title: setServer
         * </p>
         * 
         * @date 2018年12月19日 下午7:52:01
         * 
         *       <p>
         *       功能描述: 设置拥有该服务的服务器组件
         *       </p>
         * 
         * @param server
         *            与该服务关联的服务器组件
         */
        public void setServer(Server server);
    
        // --------------------------------------------------------- Public Methods
    
        /**
         * 
         * 
         * <p>
         * Title: addConnector
         * </p>
         * 
         * @date 2018年12月19日 下午7:53:50
         * 
         *       <p>
         *       功能描述:向当前定义的 连接器集合 中添加新的 连接器,并将其与该服务中唯一的一个Servlet容器相关联
         *       </p>
         * 
         * @param connector
         *            添加的连接器
         */
        public void addConnector(Connector connector);
    
        /**
         * 
         * 
         * <p>
         * Title: findConnectors
         * </p>
         * 
         * @date 2018年12月19日 下午7:57:20
         * 
         *       <p>
         *       功能描述:查找并返回该服务中定义的连连接器集合
         *       </p>
         * 
         * @return
         */
        public Connector[] findConnectors();
    
        /**
         * Remove the specified Connector from the set associated from this Service.
         * The removed Connector will also be disassociated from our Container.
         *
         * @param connector
         *            The Connector to be removed
         */
        /**
         * 
         * 
         * <p>
         * Title: removeConnector
         * </p>
         * 
         * @date 2018年12月19日 下午8:00:09
         * 
         *       <p>
         *       功能描述:从此服务中的连接器集合中移除指定的连接器,并将其与服务中唯一的一个servlet容器分离
         *       </p>
         * 
         * @param connector
         */
        public void removeConnector(Connector connector);
    
        
    
        /**
         * 
         * 
         * <p>
         * Title: initialize
         * </p>
         * 
         * @date 2018年12月19日 下午8:01:45
         * 
         *       <p>
         *       功能描述:初始化方法
         *       </p>
         * 
         * @throws LifecycleException
         */
        public void initialize() throws LifecycleException;
    
    }

    在Catalina中,StandardService类是其标准实现

    StandardSetvice

      org.apahce.catalina.core.StandardService类是 服务组件Service接口的标准实现,StandardService类的initialize方法用于初始化添加到其中所有的连接器,此外,StandardService类还实现了Service以及org.apache.catalina.Lifecycle声明周期接口,然后,它的start方法也可以启动连接器和所有的servlet容器。

      connector 和 container

    StandardService类有两种组件,分别是连接器和 servlet容器,其中servlet容器只能有一个,而连接器可以有多个,多个连接器使Tomcat可以为多种不同的请求协议提供服务,例如,一个连接器处理HTTP请求,而另外一个连接器处理HTTPS请求。

    StandardService类使用变量Container 来指向一个Container接口的实例,使用数组connectors来保存所有连接器的引用。

    1 /**
    2      * 保存服务中所有连接器引用的数组
    3      */
    4     private Connector connectors[] = new Connector[0];
    5 
    6     /**
    7      * 保存服务中唯一的一个Servlet容器
    8      */
    9     private Container container = null;

      需要调用setContainer方法将servlet容器与服务组件相关联,

     1 /**
     2      * 
     3      * 设置处理此服务中所有连接器的servlet容器
     4      * 
     5      * @param container
     6      *            新的servlet容器
     7      */
     8     public void setContainer(Container container) {
     9         // 取出当前服务中唯一的servlet容器
    10         Container oldContainer = this.container;
    11         // 如果取出的servlet容器不为空 且为Engine级别的servlet容器
    12         if ((oldContainer != null) && (oldContainer instanceof Engine))
    13             // 将调用其setSevice方法 断掉与服务的关联
    14             ((Engine) oldContainer).setService(null);
    15         // 设置新的servlet容器
    16         this.container = container;
    17         // 如果新的servlet容器 不为空 且 是Engine级别的容器
    18         if ((this.container != null) && (this.container instanceof Engine))
    19             // 将该服务实例传入容器 与之关联
    20             ((Engine) this.container).setService(this);
    21         // 如果该服务已经启动 且 服务中的容器实例不为空 且 这个容器实例是一个生命周期的实现类
    22         if (started && (this.container != null) && (this.container instanceof Lifecycle)) {
    23             try {
    24                 // 调用其start方法
    25                 ((Lifecycle) this.container).start();
    26             } catch (LifecycleException e) {
    27                 ;
    28             }
    29         }
    30         // 逐个调用连接器集合中的每一个连接器的setContainer方法 与服务中 的容器相挂关联
    31         synchronized (connectors) {
    32             for (int i = 0; i < connectors.length; i++)
    33                 connectors[i].setContainer(this.container);
    34         }
    35         // 如果服务已经启动且旧的容器不为空 且是一个生命周期实现 则 调用旧容器的stop方法
    36         if (started && (oldContainer != null) && (oldContainer instanceof Lifecycle)) {
    37             try {
    38                 ((Lifecycle) oldContainer).stop();
    39             } catch (LifecycleException e) {
    40                 ;
    41             }
    42         }
    43 
    44         // 通知属性变更监听器
    45         support.firePropertyChange("container", oldContainer, this.container);
    46 
    47     }

      与服务组件相关联的servlet容器的实例将被传给每个连接器对象的setContainer方法中,这样在服务组件中就可以形成每个连接器和servlet容器之间的关联关系。

      可以调用addConnector方法将连接器添加到服组件中,调用removeConnectoe方法将某个连接器移除,

     1     /**
     2      * 
     3      * 
     4      * 
     5      * 添加一个新的连接器到连接器集合中 并且将之与服务中的servlet容器相关联
     6      * 
     7      * @param connector
     8      *            要被添加连接器
     9      */
    10 
    11     public void addConnector(Connector connector) {
    12 
    13         synchronized (connectors) {
    14             // 添加
    15             connector.setContainer(this.container);
    16             connector.setService(this);
    17             Connector results[] = new Connector[connectors.length + 1];
    18             System.arraycopy(connectors, 0, results, 0, connectors.length);
    19             results[connectors.length] = connector;
    20             connectors = results;
    21 
    22             // 若该服务已经初始化过了
    23             if (initialized) {
    24                 try {
    25                     // 则初始化这个新加的连接器
    26                     connector.initialize();
    27                 } catch (LifecycleException e) {
    28                     e.printStackTrace(System.err);
    29                 }
    30             }
    31 
    32             // 若服务已经被启动则将新添加的连接器启动
    33             if (started && (connector instanceof Lifecycle)) {
    34                 try {
    35                     ((Lifecycle) connector).start();
    36                 } catch (LifecycleException e) {
    37                     ;
    38                 }
    39             }
    40 
    41             // Report this property change to interested listeners
    42             support.firePropertyChange("connector", null, connector);
    43         }
    44 
    45     }

    removeConnector

     1 /**
     2      * 从该服务组件的连接器集合中移除指定的连接器,
     3      * 
     4      * @param connector
     5      *            被指定移除的连接器
     6      */
     7     public void removeConnector(Connector connector) {
     8 
     9         synchronized (connectors) {
    10             // 被移除的连接器在集合中的索引位置
    11             int j = -1;
    12             for (int i = 0; i < connectors.length; i++) {
    13                 // 找到被移除的连接器在数组中的索引之后 赋值给j 并跳出循环
    14                 if (connector == connectors[i]) {
    15                     j = i;
    16                     break;
    17                 }
    18             }
    19             // 如果j小于0 说明 被指定要移除的连接器 并不存在于连接器数组中
    20             if (j < 0)
    21                 return;
    22             // 如果服务已经被启动过了,且被删除的连接器 又是一个声明周期接口的实现
    23             if (started && (connectors[j] instanceof Lifecycle)) {
    24                 try {
    25                     // 调用被删除连接器的stop方法
    26                     ((Lifecycle) connectors[j]).stop();
    27                 } catch (LifecycleException e) {
    28                     ;
    29                 }
    30             }
    31             // 并向被被删除的连接器setContainer方法中传入null,清除与该服务中唯一的servlet容器的关联关系
    32             connectors[j].setContainer(null);
    33             // 去掉与该服务的关联关系
    34             connector.setService(null);
    35             int k = 0;
    36             // 重新组合新的数组 去掉 被删除的连接器
    37             Connector results[] = new Connector[connectors.length - 1];
    38             for (int i = 0; i < connectors.length; i++) {
    39                 if (i != j)
    40                     results[k++] = connectors[i];
    41             }
    42             connectors = results;
    43 
    44             // 并触发属性改变监听器
    45             support.firePropertyChange("connector", connector, null);
    46         }
    47 
    48     }

     与生命周期有关的方法

      与生命周期有关的方法包括从Lifecycle接口中实现的start和stop方法,在加上initialize方法,其中initialize方法会调用该服务组件中所有连接器的initialize方法,

     1 /**
     2      * 该服务的初始化方法 将会调用该服务组件中连接器集合中的每一个连接器的初始化方法
     3      */
     4     public void initialize() throws LifecycleException {
     5         if (initialized)
     6             throw new LifecycleException(sm.getString("standardService.initialize.initialized"));
     7         initialized = true;
     8 
     9         //逐个调用连接器集合中的每个连接器的初始化方法
    10         synchronized (connectors) {
    11             for (int i = 0; i < connectors.length; i++) {
    12                 connectors[i].initialize();
    13             }
    14         }
    15     }

    start方法负责启动被添加到该服务组件中的连接器和 servlet容器,

     1 /**
     2      * 该服务组件的声明周期接口 开始方法的实现,其内部会将调用该 服务中的所有连接器和 servlet容器的start方法 并通知相关监听事件
     3      */
     4     public void start() throws LifecycleException {
     5 
     6         // 这个组件是否已经被启动过了
     7         if (started) {
     8             throw new LifecycleException(sm.getString("standardService.start.started"));
     9         }
    10 
    11         // 向声明周期监听器发送 BEFORE_START_EVENT事件
    12         lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);
    13 
    14         System.out.println(sm.getString("standardService.start.name", this.name));
    15         // 向生命周期监听器 发送START_EVENT事件
    16         lifecycle.fireLifecycleEvent(START_EVENT, null);
    17         // 将启动标志置为 true 表示已经启动
    18         started = true;
    19 
    20         // 首先启动服务组件中定义的唯一的一个servlet容器
    21         if (container != null) {
    22             synchronized (container) {
    23                 if (container instanceof Lifecycle) {
    24                     ((Lifecycle) container).start();
    25                 }
    26             }
    27         }
    28 
    29         // 第二步 启动我们定义的连接器集合中的每一个连接器
    30         synchronized (connectors) {
    31             for (int i = 0; i < connectors.length; i++) {
    32                 if (connectors[i] instanceof Lifecycle)
    33                     ((Lifecycle) connectors[i]).start();
    34             }
    35         }
    36 
    37         // 向生命周期监听器发送AFTER_START_EVENT事件
    38         lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);
    39 
    40     }

    stop方法 用于关闭与该服务组件相关联的servlet容器和所有的连接器,

    /**
         * 生命周期接口的停止方法的实现,会将该服务中唯一一个 servlet容器 和 被添加到该服务组件中的连接器 关闭
         */
        public void stop() throws LifecycleException {
    
            // 验证状态
            if (!started) {
                throw new LifecycleException(sm.getString("standardService.stop.notStarted"));
            }
    
            // 向生命周期监听器发送 BEFORE_STOP_EVENT事件
            lifecycle.fireLifecycleEvent(BEFORE_STOP_EVENT, null);
    
            // 向生命周期监听器STOP_EVENT 事件
            lifecycle.fireLifecycleEvent(STOP_EVENT, null);
    
            System.out.println(sm.getString("standardService.stop.name", this.name));
            // 将启动标志置为 false 这样才可以继续启动
            started = false;
    
            // 第一步 停止服务组件中的所有连接器
            synchronized (connectors) {
                for (int i = 0; i < connectors.length; i++) {
                    if (connectors[i] instanceof Lifecycle)
                        ((Lifecycle) connectors[i]).stop();
                }
            }
    
            // 第二步 停止服务组件中唯一一个servlet容器
            if (container != null) {
                synchronized (container) {
                    if (container instanceof Lifecycle) {
                        ((Lifecycle) container).stop();
                    }
                }
            }
    
            // 向声明周期监听器 发送 AFTER_STOP_EVENT事件
            lifecycle.fireLifecycleEvent(AFTER_STOP_EVENT, null);
    
        }

    那上面就大概的将 Tomcat中的服务和 服务器组件都大致的展示了一下,下面我们来写一个简单的应用程序来实际操作一下。

    下面的 应用程序重在说明如何使用服务器组件 和 服务组件,特别是在StandardServer中如何利用启动 和 关闭机制。

      1 package myex14.pyrmont.startup;
      2 
      3 import org.apache.catalina.Connector;
      4 import org.apache.catalina.Context;
      5 import org.apache.catalina.Engine;
      6 import org.apache.catalina.Host;
      7 import org.apache.catalina.Lifecycle;
      8 import org.apache.catalina.LifecycleListener;
      9 import org.apache.catalina.Loader;
     10 import org.apache.catalina.Server;
     11 import org.apache.catalina.Service;
     12 import org.apache.catalina.Wrapper;
     13 import org.apache.catalina.connector.http.HttpConnector;
     14 import org.apache.catalina.core.StandardContext;
     15 import org.apache.catalina.core.StandardEngine;
     16 import org.apache.catalina.core.StandardHost;
     17 import org.apache.catalina.core.StandardServer;
     18 import org.apache.catalina.core.StandardService;
     19 import org.apache.catalina.core.StandardWrapper;
     20 import org.apache.catalina.loader.WebappLoader;
     21 
     22 import ex14.pyrmont.core.SimpleContextConfig;
     23 
     24 /**
     25  * <p>
     26  * <b>Title:Bootstrap.java</b>
     27  * </p>
     28  * <p>
     29  * Copyright:ChenDong 2018
     30  * </p>
     31  * <p>
     32  * Company:仅学习时使用
     33  * </p>
     34  * <p>
     35  * 类功能描述:应用程序启动类,应用程序重在说明如何使用 服务器组件 和 服务组件,重点是 StandardServer中的开启和关闭机制
     36  * </p>
     37  * 
     38  * @author 陈东
     39  * @date 2018年12月19日 下午9:33:03
     40  * @version 1.0
     41  */
     42 public class Bootstrap {
     43 
     44     /**
     45      * 
     46      * <p>
     47      * Title: main
     48      * </p>
     49      * 
     50      * @date 2018年12月19日 下午9:33:03
     51      * 
     52      *       <p>
     53      *       功能描述:
     54      *       </p>
     55      * 
     56      * @param args
     57      * 
     58      */
     59     public static void main(String[] args) {
     60         System.setProperty("catalina.base", System.getProperty("user.dir"));
     61         // 初始化一个连接器
     62         Connector connector = new HttpConnector();
     63 
     64         // 初始化 对应servlet的Wrapper容器
     65         Wrapper wrapper1 = new StandardWrapper();
     66         wrapper1.setName("Primitive");
     67         wrapper1.setServletClass("PrimitiveServlet");
     68 
     69         Wrapper wrapper2 = new StandardWrapper();
     70         wrapper2.setName("Modern");
     71         wrapper2.setServletClass("ModernServlet");
     72 
     73         // 初始化一个Context容器
     74         Context context = new StandardContext();
     75         // 设置根路径
     76         context.setPath("/app1");
     77         // 设置根文件夹
     78         context.setDocBase("app1");
     79         // 添加子容器
     80         context.addChild(wrapper1);
     81         context.addChild(wrapper2);
     82 
     83         // 给StandardContext 创建一个 配置监听器
     84         LifecycleListener listener = new SimpleContextConfig();
     85 
     86         ((Lifecycle) context).addLifecycleListener(listener);
     87 
     88         // 初始化一个Host级别的容器
     89         Host host = new StandardHost();
     90         host.addChild(context);
     91         host.setName("localhost");
     92         host.setAppBase("webapps");
     93 
     94         // 初始化一个加载器
     95         Loader loader = new WebappLoader();
     96         context.setLoader(loader);
     97 
     98         // 添加servletMapping映射
     99         context.addServletMapping("/Primitive", "Primitive");
    100         context.addServletMapping("/Modern", "Modern");
    101 
    102         // 初始化一个Engine级别的容器
    103         Engine engine = new StandardEngine();
    104         // 添加孩子host
    105         engine.addChild(host);
    106         // 根据 host的name属性设置默认的Host
    107         engine.setDefaultHost("localhost");
    108 
    109         // -----------------------重点来了
    110         // 初始化一个Service
    111         Service servie = new StandardService();
    112         servie.setName("stand-alone Service");
    113         // 初始化一个Server
    114         Server server = new StandardServer();
    115         server.addService(servie);
    116         servie.addConnector(connector);
    117         servie.setContainer(engine);
    118 
    119         // 启动服务器组件
    120         if (server instanceof Lifecycle) {
    121             try {
    122                 server.initialize();
    123                 ((Lifecycle) server).start();
    124                 //启动监听关闭命令的端口开始监听关闭指令,进入循环等待
    125                 //此时连接器已经处于运行状态
    126                 //在await方法没有接到关闭命令之前 是不回复返回的 一旦接到关闭命令则 会执行下面的stop反方
    127                 server.await();
    128             } catch (Exception e) {
    129                 e.printStackTrace();
    130             }
    131         }
    132         
    133         //关闭服务器组件
    134         if(server instanceof Lifecycle){
    135             try {
    136                 ((Lifecycle)server).stop();
    137                 
    138             } catch (Exception e) {
    139                 // TODO: handle exception
    140             }
    141             
    142         }
    143 
    144     }
    145 
    146 }

    Stopper类

      Stopper类提供了一种 优雅的方式来关闭Catalina服务器,它保证了所有生命周期组件的stop方法都能够调用

     1 package myex14.pyrmont.startup;
     2 
     3 import java.io.IOException;
     4 import java.io.OutputStream;
     5 import java.net.Socket;
     6 
     7 /**
     8  * <p>
     9  * <b>Title:Stopper.java</b>
    10  * </p>
    11  * <p>
    12  * Copyright:ChenDong 2018
    13  * </p>
    14  * <p>
    15  * Company:仅学习时使用
    16  * </p>
    17  * <p>
    18  * 类功能描述:目的是向Server负责监听关闭命令的端口发送 关闭命令
    19  * </p>
    20  * 
    21  * @author 陈东
    22  * @date 2018年12月19日 下午10:02:37
    23  * @version 1.0
    24  */
    25 public class Stopper {
    26 
    27     /**
    28      * 
    29      * <p>
    30      * Title: main
    31      * </p>
    32      * 
    33      * @date 2018年12月19日 下午10:02:37
    34      * 
    35      *       <p>
    36      *       功能描述:
    37      *       </p>
    38      * 
    39      * @param args
    40      * 
    41      */
    42     public static void main(String[] args) {
    43         int port = 8005;
    44         String shutdown = "SHUTDOWN";
    45         try {
    46             Socket socket = new Socket("127.0.0.1", port);
    47             
    48             OutputStream stream = socket.getOutputStream();
    49             
    50             for(int i=0;i<shutdown.length();i++)
    51                 stream.write(shutdown.charAt(i));
    52             
    53             stream.flush();
    54             stream.close();
    55             socket.close();
    56             System.out.println("这个服务已经成功关闭");
    57             
    58             
    59             
    60         } catch (IOException e) {
    61             System.out.println("这个服务还没有启动");
    62         }
    63             
    64 
    65     }
    66 
    67 }

     当然了 这个Stopper类 只是一个简单的例子 真正部署的时候 不会像现在这样的简单 ,只要是想关闭服务器 其实就是自己 创建了一个 连接到 指定ip指定端口的 套接字实例,并且使用该套接字,直接发送 关闭命令

  • 相关阅读:
    net.sf.json.JSONException: There is a cycle in the hierarchy!
    数据源的配置
    java枚举使用详解
    hibernate级联保存,更新个人遇到的问题
    NonUniqueObjectException 问题
    No.2 dotnetcore&docker--数据库等在Docker上的配置
    No.1 dotnetcore&docker--环境搭建
    No.5 dotnetcore&docker--采用Ambry做文件服务器
    No.3 dotnetcore&docker--搭建一个nuget服务器
    关于APM数据采集实例以及Eureka整合的一个想法
  • 原文地址:https://www.cnblogs.com/ChenD/p/Tomcat-Server-StandardServer.html
Copyright © 2020-2023  润新知