> For the complete documentation index, see [llms.txt](https://lang-yu.gitbook.io/vert.x/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://lang-yu.gitbook.io/vert.x/01-index/01-5-verticle-lifecycle.md).

# 1.5.Verticle生命周期

    本章承接前一章，谈谈未完成的剧情。上一章节讲到了主流程中，Vertx发布Verticle实例的细节，那么在发布之后，就进入了Verticle的内部代码，即`start/stop`两个核心生命周期，本章节的故事则是围绕Verticle的两个生命周期展开。Verticle的`start`方法将在Vertx实例调用`deployVerticle`的API后被触发。

## 1. Verticle的编写

    在书写Verticle[^1](https://vertx.io/docs/vertx-core/java/#_verticles)的过程中，我们会用到一个新类`io.vertx.core.AbstractVerticle`，我们编写的所有的Verticle都是从这个类继承过来的，最简单的一个Verticle代码如下：

```java
public class MyVerticle extends AbstractVerticle {

  // 启动Verticle时被调用的方法：Deploy
  public void start() {
  }

  // 停止Verticle时被调用的方法：Undeploy
  public void stop() {
  }
}
```

    上边的代码编写了一个最简单的Verticle组件，最直接的编写自定义逻辑的方式就是重写抽象类中的`start/stop`两个方法，这两个方法分别对应到Verticle的核心生命周期，在继续下边内容之前我们去`AbstractVerticle`这个类中逛逛：其实这个类的源代码很简单，去掉注释部分如下：

```java
public abstract class AbstractVerticle implements Verticle {

  protected Vertx vertx;

  protected Context context;

  @Override
  public Vertx getVertx() {
    return vertx;
  }

  @Override
  public void init(Vertx vertx, Context context) {
    this.vertx = vertx;
    this.context = context;
  }

  public String deploymentID() {
    return context.deploymentID();
  }

  public JsonObject config() {
    return context.config();
  }

  public List<String> processArgs() {
    return context.processArgs();
  }

  @Override
  public void start(Future<Void> startFuture) throws Exception {
    start();
    startFuture.complete();
  }

  @Override
  public void stop(Future<Void> stopFuture) throws Exception {
    stop();
    stopFuture.complete();
  }

  public void start() throws Exception {
  }

  public void stop() throws Exception {
  }
}
```

    上述代码可以看到，`AbstractVerticle`中定义了四个核心的生命周期方法：**两个同步、两个异步、两个开始、两个结束**，读者先不要去纠结`io.vertx.core.Future`类是做什么用的，只需要知道在Vert.x中有它的地方就意味着\*\*“异步”\*\*，综上所述：

|    | 同步      | 异步                               |
| -- | ------- | -------------------------------- |
| 开始 | start() | start(Future\<Void> startFuture) |
| 结束 | stop()  | stop(Future\<Void> stopFuture)   |

　　最开始的代码演示了如何同步启动/停止Verticle组件，那么接下来就看看如何异步地启动/停止Verticle组件。

```java
public class MyVerticle extends AbstractVerticle {

  private HttpServeer server;

  public void start(Future<Void> startFuture) {
    server = vertx.createHttpServer().requestHandler(req -> {
      req.response()
        .putHeader("content-type", "text/plain")
        .end("Hello from Vert.x!");
      });

    // Server的listen方法本身是一个异步动作，绑定了一个异步回调
    server.listen(8080, res -> {
      if (res.succeeded()) {
        startFuture.complete();
      } else {
        startFuture.fail(res.cause());
      }
    });
  }
}
```

    异步启动/停止的使用场景分析：结合官方教程，简单分析一下异步启动的必要性。异步启动和停止两个方法本身为了解决这样一种问题，您想要在这两个生命周期中定义自己的逻辑：

1. 这个代码逻辑本身包含了异步代码，如上边示例中异步调用了`server.listen`方法；
2. 这个代码可能包含了一些Block的动作，如访问IO装置；

    根据Vert.x官方提供的黄金法则，您不能在Verticle中引入直接的阻塞代码，这样的引入有可鞥会直接阻塞Event Loop，这个是不被允许的，在这样的情况下，您需要使用另外的手段来解决这种“阻塞”问题，那么异步的`start/stop`就是您的选择。这里做个假设，如果上边的代码为：

```java
public class MyVerticle extends AbstractVerticle {

  private HttpServeer server;

  public void start() {
    server = vertx.createHttpServer().requestHandler(req -> {
      req.response()
        .putHeader("content-type", "text/plain")
        .end("Hello from Vert.x!");
      });

    // Now bind the server:
    server.listen(8080, res -> {
      if (res.succeeded()) {
        System.out.println("Successed");
      } else {
        System.out.println("Failure");
      }
    });
  }
}
```

    上边代码会发生什么？

![](https://1702219450-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Lv070Tm3mzVeCv0ENzP%2Fuploads%2Fgit-blob-4b2a2c0f074341147797d4781edd804650c1e1a3%2F0001.png?alt=media)

    从上图可以看到，在Verticle的start方法执行完成后，HttpServer的listen动作有可能还没完成，也就是说这样的方式导致了`listen`的完成动作在`start`完成动作之后，——这样没有办法维持Verticle在启动过程中的状态一致性。我们期望的结果是当`HttpServer`的`listen`动作完成后才标志着Verticle的启动完成，那么改变前的代码就可以达到这个效果，这里的主要原因是我们使用了`HttpServer`的异步`listen`的API，所以为了确保状态的一致性，使用Verticle组件中的异步`start`就成为了一种必然。

    当然，您如果可以忍受在`listen`在Verticle的启动之后完成的话，那么您可以使用这样的写法，可是如果出现一种极端的情况：您写好的Verticle不论从日志还是其他参数都看到的是启动完成，而`HttpServer`在`listen`过程失败了（可能端口被占用，可能其他），那么这种情况下，从您想要的初衷，这个`HttpServer`并没有启动完成，那么这个时候Verticle的状态和HttpServer的状态就出现了明显的不一致。——之所以说这是一种极端情况是因为这种情况可能发生概率并不大，但是为了从程序的严谨上考虑，建议这样的代码还是不要出现，因为Vert.x中本来就有解决这种异步调用的解决方案。

## 2. 关于DeploymentID

    在Vert.x中，每个Verticle组件在发布过后会有一个唯一标识符——称为**DeploymentID**，该标识符的格式是UUID[^2](https://github.com/silentbalanceyh/vertx-book/blob/master/chapter01/《UUID》%3Chttps:/baike.baidu.com/item/UUID/5921266?fr=aladdin%3E/README.md)的格式如：`a8cdd717-49f8-452d-8d07-8263cae99a26`，Vert.x在发布（Deploy）时会触发Verticle组件的`start`方法，进入启动的生命周期，而在撤销（Undeploy）时会触发Verticle组件的`stop`方法，进入停止的生命周期，冒看之下DeploymentID在启动过程没有使用到，但在停止的过程中往往会用到，参考一下Vertx实例的`undeploy`系列方法。

```java
  /**
   * Undeploy a verticle deployment.
   * <p>
   * The actual undeployment happens asynchronously and may not complete 
   * until after the method has returned.
   *
   * @param deploymentID  the deployment ID
   */
  void undeploy(String deploymentID);

  /**
   * Like {@link #undeploy(String) } but the completionHandler will be notified 
   * when the undeployment is complete.
   *
   * @param deploymentID  the deployment ID
   * @param completionHandler  a handler which will be notified when the undeployment is complete
   */
  void undeploy(String deploymentID, Handler<AsyncResult<Void>> completionHandler);
```

    Vertx实例中只有两个`undeploy`方法，这两个方法的第一个参数都是DeploymentID，所以如果要处理“停止”这个生命周期的一些细节，比如使用自定义代码，那么在编程方式发布/撤销时需要考虑将发布过程的DeploymentID记录下来，这样才可以针对对应的Verticle实例执行后续操作。

> \*\*「注」\*\*这里的DeploymentID实际上关联的也不是一个Verticle实例（单线程），而是某一类（即您所编写的Verticle类），也就是说DeploymentID还不会涉及到线程这个级别，而是前文提到的“一堆”。

## 3. 例子：生命周期控制

    接下来通过zero中对`Deploy/Undeploy`的应用解析一下处理Verticle完整生命周期的代码

### 3.1. 开发Verticle

```java
package io.vertx.up._01.verticles;

import io.vertx.core.AbstractVerticle;

public class LifeVerticle extends AbstractVerticle {

    @Override
    public void start() {
        System.out.println(Thread.currentThread().getName() +
                ": Start : " +
                Thread.currentThread().getId()
                + ", Did: " + this.deploymentID());
    }

    @Override
    public void stop() {
        System.out.println(Thread.currentThread().getName() +
                ": Stop : " +
                Thread.currentThread().getId()
                + ", Did: " + this.deploymentID());
    }
}
```

    开发一个Verticle如上，使用打印语句的主要目的是可直接监控当前这个Verticle组件的生命周期。

### 3.2. 主程序

```java
package io.vertx.up._01.life;

import io.vertx.core.DeploymentOptions;
import io.vertx.up._01.lanucher.Launcher;
import io.vertx.up._01.lanucher.SingleLauncher;
import io.vertx.up._01.verticles.LifeVerticle;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

public class LifeCycle {

    private static final ConcurrentMap<String, String> IDS = new ConcurrentHashMap<>();

    public static void main(final String[] args) {
        // 选择单点模式
        final Launcher launcher = new SingleLauncher();

        launcher.start(vertx -> {
            // 发布
            vertx.deployVerticle(LifeVerticle::new, new DeploymentOptions().setInstances(10), res -> {
                if (res.succeeded()) {
                    IDS.put(res.result(), res.result());
                }
            });
            vertx.deployVerticle(LifeVerticle::new, new DeploymentOptions().setInstances(3), res -> {
                if (res.succeeded()) {
                    IDS.put(res.result(), res.result());
                }
            });

            Runtime.getRuntime().addShutdownHook(new Thread(() -> {
                // 撤销
                IDS.keySet().forEach(item -> vertx.undeploy(item, res -> {
                    System.out.println("Successfully undeploy the item: " + item);
                }));
            }));
        });
    }
}
```

### 3.3. 运行

    直接运行该程序，然后将该程序关闭，点击IDE中的停止或在命令行中`Ctrl + C`的中断模式，您将可以看到下边输出：

```
# public void start()方法
vert.x-eventloop-thread-0: Start : 14, Did: be38fa6e-80af-4ce0-8509-0a2ccfb6026c
vert.x-eventloop-thread-1: Start : 15, Did: be38fa6e-80af-4ce0-8509-0a2ccfb6026c
vert.x-eventloop-thread-12: Start : 26, Did: e5b0d620-4cc3-4709-8574-19a17ddeba5e
vert.x-eventloop-thread-11: Start : 25, Did: e5b0d620-4cc3-4709-8574-19a17ddeba5e
vert.x-eventloop-thread-10: Start : 24, Did: e5b0d620-4cc3-4709-8574-19a17ddeba5e
vert.x-eventloop-thread-9: Start : 23, Did: be38fa6e-80af-4ce0-8509-0a2ccfb6026c
vert.x-eventloop-thread-8: Start : 22, Did: be38fa6e-80af-4ce0-8509-0a2ccfb6026c
vert.x-eventloop-thread-7: Start : 21, Did: be38fa6e-80af-4ce0-8509-0a2ccfb6026c
vert.x-eventloop-thread-6: Start : 20, Did: be38fa6e-80af-4ce0-8509-0a2ccfb6026c
vert.x-eventloop-thread-5: Start : 19, Did: be38fa6e-80af-4ce0-8509-0a2ccfb6026c
vert.x-eventloop-thread-4: Start : 18, Did: be38fa6e-80af-4ce0-8509-0a2ccfb6026c
vert.x-eventloop-thread-3: Start : 17, Did: be38fa6e-80af-4ce0-8509-0a2ccfb6026c
vert.x-eventloop-thread-2: Start : 16, Did: be38fa6e-80af-4ce0-8509-0a2ccfb6026c

# public void stop()方法
vert.x-eventloop-thread-0: Stop : 14, Did: be38fa6e-80af-4ce0-8509-0a2ccfb6026c
vert.x-eventloop-thread-2: Stop : 16, Did: be38fa6e-80af-4ce0-8509-0a2ccfb6026c
vert.x-eventloop-thread-12: Stop : 26, Did: e5b0d620-4cc3-4709-8574-19a17ddeba5e
vert.x-eventloop-thread-4: Stop : 18, Did: be38fa6e-80af-4ce0-8509-0a2ccfb6026c
vert.x-eventloop-thread-3: Stop : 17, Did: be38fa6e-80af-4ce0-8509-0a2ccfb6026c
vert.x-eventloop-thread-6: Stop : 20, Did: be38fa6e-80af-4ce0-8509-0a2ccfb6026c
vert.x-eventloop-thread-10: Stop : 24, Did: e5b0d620-4cc3-4709-8574-19a17ddeba5e
vert.x-eventloop-thread-8: Stop : 22, Did: be38fa6e-80af-4ce0-8509-0a2ccfb6026c
vert.x-eventloop-thread-9: Stop : 23, Did: be38fa6e-80af-4ce0-8509-0a2ccfb6026c
vert.x-eventloop-thread-5: Stop : 19, Did: be38fa6e-80af-4ce0-8509-0a2ccfb6026c
vert.x-eventloop-thread-1: Stop : 15, Did: be38fa6e-80af-4ce0-8509-0a2ccfb6026c
vert.x-eventloop-thread-11: Stop : 25, Did: e5b0d620-4cc3-4709-8574-19a17ddeba5e
vert.x-eventloop-thread-7: Stop : 21, Did: be38fa6e-80af-4ce0-8509-0a2ccfb6026c

# undeploy方法的回调
Successfully undeploy the item: e5b0d620-4cc3-4709-8574-19a17ddeba5e
Successfully undeploy the item: be38fa6e-80af-4ce0-8509-0a2ccfb6026c
```

### 3.4. 分析

    在总结本小节之前先看看上边用到的一个特殊的JVM函数：`Runtime.getRuntime().addShutdownHook`\[^3]函数，该函数参数为一个`java.lang.Thread`，它的方法签名如下：

```java
public void addShutdownHook(Thread hook) ...
```

    该方法用于在JVM中增加一个关闭的钩子，当程序正常退出，系统调用`System.exit方法`或者虚拟机被关闭时就会执行该线程中的代码。其中shutdownHook是一个已经初始化但是并没启动的线程，当JVM关闭时，会执行系统中已经设置的所有通过`addShutdownHook`添加的钩子，等到系统执行完这些钩子中的代码后，JVM才会正式关闭，所以可以使用该方法在JVM关闭时进行内存清理、资源回收等工作。

    **为什么我们需要该方法**？Vert.x中的`stop`方法不会主动触发，它只有在您调用了`undeploy`过后才会触发，所以对于Vert.x中的”停止“部分，必须设置关闭时的代码，从实际使用看来，该代码放到`shutdownHook`是最有效的，因为该代码的触发点比较合适——最理想的状况就是在系统关闭之前调用所有Verticle组件的`stop`方法来清理相关资源，这些清理动作包括：

* 关闭未关闭完全的连接池。
* 在微服务模式下更新当前服务的状态（若使用ZooKeeper或Etcd3作为配置中心，需要善后）。
* 向其他节点发出消息提交当前节点的心跳信息，告诉其他节点当前节点将会停止。

    当然清理动作不仅仅是以上的信息，我这边只枚举了一部分。

    最后根据日志读者可以简单分析并且彻底理解DeploymentID的用途：

* `DeploymentID`是UUID格式，主要和每一次`Deploy`行为绑定，因为一次发布可能关联某一类Verticle的多个实例。
* 同一类Verticle（自己编写的Java类）可发布多次，每次发布会产生新的DeploymentID，应该说每个线程会独占一个`DeployementID`，这也是为什么反复在强调Verticle究竟是一类还是一个的原因。
* `stop`方法在调用了`undeploy`过后被触发，而且在完成之前打印了所有的日志。

    最后，在stop中一定要注意代码本身是异步还是同步，如果使用了异步方法，有可能代码还没执行JVM就关闭了导致关闭状态的不同步，所以在该代码中做好数据同步的完善工作是有必要的，不过这不是一个开发的问题，相反这是架构师应该考虑的点。

## 4.小结

    本章节主要针对Verticle生命周期的细节进行讲解，通过一个完整的例子（zero中的关闭部分远比例子中的复杂）来告诉读者如何设计以及开发Vert.x中的Verticle去触发`stop`生命周期，使得整个程序变得完整。曾经我遇到过一个朋友，当时一直问我为什么`stop`的生命周期没触发，当时给我演示时它直接点了IDE中的stop按钮，而当时点击该按钮后`stop`并没有执行。回到上边使用的手段，因为这个IDE在stop按钮点击时，只有ShutdownHook部分的代码可捕捉到这个行为，而Verticle中的`stop`生命周期是没有办法捕捉该行为的，所以它的`stop`并不是自动触发。

\[^3]: 《Runtime.addShutdownHook用法》<http://kim-miao.iteye.com/blog/1662550>, 作者：kim\_miao
