# 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`子项目。


---

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