> 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/02-index/02-4-options.md).

# 2.4.Options

    本文主要讲解`Options`，它是`Vert.x`中的一个经典结构，很多地方都会使用到，虽然不同的使用场景中，它的名字有所差异，但在整个`Vert.x`中，不同种类的Options结构是近似的。

## 1. 基本介绍

    Vert.x中很多组件都搭载了`Options`的结构，它定义了Vert.x中许多组件使用的配置信息，如前文中看到的`io.vertx.core.VertxOptions`类，本文我会带大家去看看整个`Vert.x`框架中常用的Options。

    先看看下边代码（来自官方）：

```java
    // 直接创建
    Vertx vertx = Vertx.vertx();
    // 使用 VertxOptions 的创建
    Vertx vertx = Vertx.vertx(new VertxOptions().setWorkerPoolSize(40));
```

    上述代码是官方创建Vertx实例的介绍代码，实际上第一行代码的内部调用如：

```java
// 位于：io.vertx.core.impl.VertxFactoryImpl 类中
  @Override
  public Vertx vertx() {
    return vertx(new VertxOptions());
  }
```

    简单说，VertxOptions 提供了Vertx实例需要使用的所有配置信息，如果开发人员不提供**自定义**的VertxOptions，那么Vert.x会使用内置默认的VertxOptions来实例化Vertx，这也是Options结构带了的**福利**，任何在Vert.x中运行的组件，不论是Router、Verticle还是HttpServer，都自带了一套默认的运行配置，开发人员甚至可以理解成**零配置**启动。但整个Vertx框架中Options的种类繁多，初学者往往会被不同的Options吓到。

## 2. 从VertxOptions出发

    开发人员不用去认识每一种不同的Options，只要理解了Options结构，就可以在任意场景随心所欲地使用不同类型的Options了，本章节对`io.vertx.core.VertxOptions`的结构进行解析，通过解析让开发人员理解如何去解读Vert.x中的Options结构并且彻底掌握它的使用原理。

### 2.1. Vertx中的codegen

    Vertx中的大部分Options都会引入了下边定义片段：

```java
import io.vertx.codegen.annotations.DataObject;
// ... 其他 import

@DataObject(generateConverter = true, publicConverter = false)
public class VertxOptions {
    // ... 内容代码
}
```

    这个注解是Vert.x中的另外一个子项目`vertx-codegen`\[^1]中的内容，Vert.x框架支持多种编程语言，它提供了一个很方便生成API的项目`vertx-codegen`，使用它可以简化多语言平台开发。Vert.x的官方介绍中，它有一个特性**Ployglot**，而这个项目就是实现**Ployglot**的桥梁，使用该项目很容易让开发的API支持**多语言架构**，这也是Vert.x中的一个亮点。如果包含了上边的注解`@DataObject`，则`io.vertx.core.VertxOptionsConverter`会自动生成，参考下边注释：

```java
    // io.vertx.core.VertxOptionsConverter
    /**
     * Converter for {@link io.vertx.core.VertxOptions}.
     * NOTE: This class has been automatically generated from the 
     * {@link io.vertx.core.VertxOptions} original class using Vert.x codegen.
     */
```

> **「注」**：`vertx-codegen`的使用需要配置才能启用，而不是自动识别。

### 2.2. 基本结构

　　     Vert.x中的Options结构近似于JavaBean的基本规范，包含了`set/get`基本的API，其中`get`的API是类似的，直接返回里面配置的每个属性项，而`set`API会在原始的JavaBean规范之中有所改动。参考下边Java代码对比：

```java
    // 普通 JavaBean 中的 set 方法
    public void setName(final String name){
        this.name = name;
    }

    // Options 中的 set 方法
    // 有些API中使用了 Fluent 注解，而有些未使用，主要和 codegen 的生成有关
    @Fluent
    public VertxOptions setName(final String name){
        this.name = name;
        return this;
    }
```

    上述代码段演示了两种不同风格的`set`方法的区别，而在Vert.x中所有的Options方法在设置数据时都使用了第二种，第一种是通用的`JavaBean`规范，而第二种就是Vert.x中的Fluent风格。

### 2.3. 构造和默认

    Options中的构造函数主要有三个（读者可理解成是**绝对统一**的，几乎所有的Options都包含这三个构造函数。）

*默认无参构造函数*

```java
    public VertxOptions() {}
```

*拷贝类型的构造函数*

```java
    public VertxOptions(VertxOptions other) {}
```

*自动生成类构造函数（JsonObject作参数）*

```java
    public VertxOptions(JsonObject json) {
        this();
        VertxOptionsConverter.fromJson(json, this);
    }
```

> \*\*「注」\*\*上述的第三种构造函数就是`codegen`自动生成的构造函数，它会在生成过程中生成`Converter`类，并且使用它对统一的JsonObject方法执行转换。

    Options中有默认无参的构造函数，而这些构造函数在构造Options对象时使用的默认值不是Java中的**类型默认**值，而是自定义的值，每个Options中都定义了静态公有变量对每个配置项提供默认值，定义片段如下：

```java
  /**
   * The default value of quorum size = 1
   */
  public static final int DEFAULT_QUORUM_SIZE = 1;

  /**
   * The default value of Ha group is "__DEFAULT__"
   */
  public static final String DEFAULT_HA_GROUP = "__DEFAULT__";

  /**
   * The default value of HA enabled = false
   */
  public static final boolean DEFAULT_HA_ENABLED = false;
```

### 2.4. 常用Options

    到这里，Vert.x中的Options基本结构就分析清楚了，读者可以按照这种思路去解读其他所有的Options，在整个Vert.x框架中，Options的基础结构是一致的，这里列举常用的Options给读者参考（`vertx-core`项目中）：

|             组件 |                父类 |                 Options |             备注 |
| -------------: | ----------------: | ----------------------: | -------------: |
|          Vertx |                 x |            VertxOptions |    Vertx实例专用配置 |
|       Verticle |                 x |       DeploymentOptions | Verticle实例专用配置 |
|      DnsClient |                 x |        DnsClientOptions |     网络DNS客户端配置 |
|              x |                 x |  AddressResolverOptions |    网络DNS内部寻址配置 |
|     FileSystem |                 x |             CopyOptions |         文件拷贝配置 |
|     FileSystem |                 x |             OpenOptions |         文件打开配置 |
|   FileResolver |                 x |       FileSystemOptions |         文件系统配置 |
|   VertxMetrics |                 x |          MetricsOptions |         监控指标配置 |
|              x |                 x |          NetworkOptions |         网络顶层配置 |
| DatagramSocket |    NetworkOptions |   DatagramSocketOptions |        UDP专用配置 |
|              x |    NetworkOptions |           TCPSSLOptions |    TCP和SSL专用配置 |
|       EventBus |     TCPSSLOptions |         EventBusOptions |   EventBus对应配置 |
|              x |     TCPSSLOptions |       ClientOptionsBase |        客户端抽象配置 |
|     HttpClient | ClientOptionsBase |       HttpClientOptions |      HTTP客户端配置 |
|      NetClient | ClientOptionsBase |        NetClientOptions |        网络客户端配置 |
|      NetServer |     TCPSSLOptions |        NetServerOptions |        网络服务器配置 |
|     HttpServer |  NetServerOptions |       HttpServerOptions |      HTTP服务器配置 |
|              x |     TCPSSLOptions |       ServerOptionsBase |      新版空配置（保留） |
|     HttpClient |                 x |          RequestOptions |       HTTP请求配置 |
|      WebSocket |    RequestOptions | WebSocketConnectOptions |    WebSocket配置 |
|       MessageX |                 x |         DeliveryOptions |       消息传输专用配置 |
|              x |                 x |        TrustOptions（接口） |       可信任客户端配置 |
|              x |                 x |      KeyCertOptions（接口） |        客户端证书配置 |
|              x |                 x |              JksOptions |        Jks证书配置 |
|              x |                 x |              PfxOptions |        Pfx证书配置 |
|              x |                 x |         PemTrustOptions |          受信任配置 |
|              x |                 x |        SSLEngineOptions |        SSL引擎配置 |
|              x |  SSLEngineOptions |     JdkSSLEngineOptions |  JDK ssl引擎实现配置 |
|              x |  SSLEngineOptions |    OpenSSLEngineOptions | Open ssl引擎实现配置 |

    上边枚举了`vertx-core`项目中所有涉及的Options结构的常用类和相关说明，对应的结构图如下：

![](/files/590DLeHKSEqxsd8wchl5)

    本章并不打算给读者讲解每一个Options的内容细节，主要是通过对Options结构的解读让读者掌握完整的Options结构的分析和理解方式，同时提供Vert.x中的标准的Options的整体关系，让读者从高处俯瞰整个Options部分的内容。

## 3. 开发Options

    看完了Options的整体结构，本章节我们来学习开发自定义的Options，为了让读者更加了解Options的结构，这里不使用`codegen`，而采用最原始的方式来编写Options部分的代码。Options的主要目的是提供配置项信息，它的一切都是以配置为基础，先看下边的配置片段：

```yaml
zero:
    vertx:
        clustered:
            enabled: true
            manager: ""
            options: 
                key1: "value1"        # 临时添加，用于演示
```

    上述代码取自Zero内部**集群管理器**，读者不要误解，这里将要开发的并不是单独针对`options`配置项，而是`clustered`节点配置项，按照前天提到过的结构，先开发一个`Converter`，参考下边代码：

```java
final class ClusterOptionsConverter {

    private ClusterOptionsConverter() {
    }

    static void fromJson(final JsonObject json, final ClusterOptions obj) {
        // 是否启用集群模式
        if (json.getValue("enabled") instanceof Boolean) {
            obj.setEnabled(json.getBoolean("enabled"));
        }

        // 如果包含 options 则直接转换成集群管理器所需配置，结构为 JsonObject
        if (json.getValue("options") instanceof JsonObject) {
            obj.setOptions(json.getJsonObject("options"));
        }

        // manager节点中的内容无法直接转换，它是一个Java类全名，定义了当前系统所使用的
        // ClusterManager的实现类
        final Object managerObj = json.getValue("manager");
        Fn.safeNull(() -> {
            final Class<?> clazz = Ut.clazz(managerObj.toString());
            Fn.safeNull(() -> {

                // 如果反射生成的Class<?>不为空，则实例化集群管理器
                final ClusterManager manager = Ut.instance(clazz);
                obj.setManager(manager);
            }, clazz);
        }, managerObj);
    }
}
```

    其实这个Converter中由于包含了类名，并且要实例化成该类对应的对象，所以也是无法直接使用`codegen`生成代码的原因，上述代码的基本格式参考了Vert.x中内置生成的Converter格式，而最后的`manager`节点的处理则是自定义逻辑，`Fn.safeNull`是非空安全执行类，保证在处理过程中不会读取到任何`null`的值，而`Ut.clazz`和`Ut.instance`底层则是直接使用反射执行类加载和对象实例化的操作。写好了Converter的代码后，再来看看`ClusterOptions`部分：

```java
public class ClusterOptions implements Serializable {

    private static final boolean ENABLED = false;
    private static final ClusterManager MANAGER = new HazelcastClusterManager();
    private static final JsonObject OPTIONS = new JsonObject();

    private boolean enabled;
    private ClusterManager manager;
    private JsonObject options;

    public ClusterOptions() {
        this.enabled = ENABLED;
        this.manager = MANAGER;
        this.options = OPTIONS;
    }

    public ClusterOptions(final ClusterOptions other) {
        this.enabled = other.isEnabled();
        this.manager = other.getManager();
        this.options = other.getOptions();
    }

    public ClusterOptions(final JsonObject json) {
        this();
        ClusterOptionsConverter.fromJson(json, this);
    }

    public boolean isEnabled() {
        return this.enabled;
    }

    @Fluent
    public ClusterOptions setEnabled(final boolean enabled) {
        this.enabled = enabled;
        return this;
    }

    public ClusterManager getManager() {
        return this.manager;
    }

    @Fluent
    public ClusterOptions setManager(final ClusterManager manager) {
        this.manager = manager;
        return this;
    }

    public JsonObject getOptions() {
        return this.options;
    }

    @Fluent
    public ClusterOptions setOptions(final JsonObject options) {
        this.options = options;
        return this;
    }

    @Override
    public String toString() {
        return "ClusterOptions{enabled=" + this.enabled
                + ", manager=" +
                ((null == this.manager) ? "null" : this.manager.getClass().getName())
                + ", options="
                + this.options.encode() + '}';
    }
}
```

    按照Options的结构，一个完整的`ClusterOptions`就开发好了。在自定义Options的过程中，这里我把`Class<?>`压到底层作为了配置项，这是没有直接使用`codegen`的原因，它包含了部分自定义的反射逻辑，并且为整个程序拿到`ClusterManager`的引用。从**配置**这个概念来讲，这种做法比较混淆，但是从实际使用效果上看来，这样做也有好处。

    在真实系统中，如果使用面向接口编程，那么实现类会自然引入**可配置**的特性，一旦可配置，真正代码中就不可能通过`new X()`的方式构造，这样的系统往往会变得灵活，但付出的代价就是牺牲代码的健壮性，而此时只能通过开发人员自身来保证代码质量。主要问题如：

1. **类名异常**：开发人员有可能因为拼写错而导致常见的`ClassNotFound`的异常，如果这个类名配置是**必须**的则比较好处理，抛出异常也能提示开发人员这里有问题，直接将异常信息打印出来都可行；但是如果这个类名配置仅仅是**可选**配置，就意味着即使这里发生了`ClassNotFound`，系统可以直接提供默认行为忽略该配置，而不是直接抛出异常信息，这种情况下，自定义代码逻辑的优势就很明显了。
2. **接口冲突**：在配置这种带反射信息的数据时，还容易犯的一个错就是接口实现问题，有可能你配置的类本身并没有实现你所期望的接口，这种情况下，即使类加载成功了，在实例化过程中通常会因为类型原因导致实例化失败。但是，我们通常期望在实例化失败时，系统依旧可以运行，系统是忽略还是抛出异常取决于本身的业务场景。

    **约定**确实是个好东西，但它毕竟不是**约束**，再完美的约定都无法保证人为去破坏它，从编程角度讲这是可以的，但从系统角度讲，这样会破坏系统的严谨性。为了让系统在任何场景下都可以**适配**配置数据，就需要在Options的构造过程中尽可能保证不出错，这也是Options存在于Vert.x中的意义，如果读者仔细去研究所有Options的代码，它都存在**默认值**，这个目的就是为了保证任何情况下，组件本身拥有一套**可运行**的配置。在项目开发中，配置数据是面向开发人员而不是用户，不论是静态配置还是动态配置，都需要做到**完美约定**——即在任何情况下，配置数据都不能出错，一旦出错系统就会变得不稳定，所以作为开发人员在书写自定义Options的过程中，需要仔细思考、设计和编码，阻击所有有可能的错误发生，做到底层稳定（所以读者才会看到上述代码中繁琐的`Fn.safeNull`，既检查输入又保证输出）。

    开发了自定义的Options过后，您就可以在您的代码中使用了，最后提供一段消费Options的代码，读者慢慢去体会一下：

```java
        // 读取集群配置
        final ClusterOptions cluster = ZeroGrid.getClusterOption();
        if (cluster.isEnabled()) {
            final ClusterManager manager = cluster.getManager();
            logger.info(Info.APP_CLUSTERD, manager.getClass().getName(),
                    manager.getNodeID(), manager.isActive());
            // 集群启动器函数
            fnCluster.accept(manager, consumer);
        } else {
            // 单独启动器函数
            fnSingle.accept(consumer);
        }
```

    早在第一个章节我们演示了如何开发一个完整的启动器，这里不累赘介绍`fnCluster/fnSingle`的内部逻辑，一旦自定义了Options过后，您就可以在自己的接口中定义对应类型，实现**接口约定式**的开发和调用了。

## 4. 总结

    本章节我们主要学习了`Vert.x`中的`Options`架构，除了了解原生框架提供的基本结构以外，还使用例子告诉读者如何定义自己的`Options`来实现`Vert.x`组件对配置数据的消费。从我的使用经验可以知道，这种结构在很多场景下比直接使用`JsonObject`让系统更具有结构性，所以在适当的组件开发过程中，使用`Options`衔接组件和配置也是一种比较不错的策略。

\[^1]: Vert.x API Generation, <https://github.com/vert-x3/vertx-codegen>, Vert.x的`codegen`子项目。
