# 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");
      }
    });
  }
}
```

    上边代码会发生什么？

![](/files/xGJfEObMfXYHV8oKSiNt)

    从上图可以看到，在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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://lang-yu.gitbook.io/vert.x/01-index/01-5-verticle-lifecycle.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
