2.4.Options
本文主要讲解Options
,它是Vert.x
中的一个经典结构,很多地方都会使用到,虽然不同的使用场景中,它的名字有所差异,但在整个Vert.x
中,不同种类的Options结构是近似的。
1. 基本介绍
Vert.x中很多组件都搭载了Options
的结构,它定义了Vert.x中许多组件使用的配置信息,如前文中看到的io.vertx.core.VertxOptions
类,本文我会带大家去看看整个Vert.x
框架中常用的Options。
先看看下边代码(来自官方):
上述代码是官方创建Vertx实例的介绍代码,实际上第一行代码的内部调用如:
简单说,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都会引入了下边定义片段:
这个注解是Vert.x中的另外一个子项目vertx-codegen
[^1]中的内容,Vert.x框架支持多种编程语言,它提供了一个很方便生成API的项目vertx-codegen
,使用它可以简化多语言平台开发。Vert.x的官方介绍中,它有一个特性Ployglot,而这个项目就是实现Ployglot的桥梁,使用该项目很容易让开发的API支持多语言架构,这也是Vert.x中的一个亮点。如果包含了上边的注解@DataObject
,则io.vertx.core.VertxOptionsConverter
会自动生成,参考下边注释:
「注」:
vertx-codegen
的使用需要配置才能启用,而不是自动识别。
2.2. 基本结构
Vert.x中的Options结构近似于JavaBean的基本规范,包含了set/get
基本的API,其中get
的API是类似的,直接返回里面配置的每个属性项,而set
API会在原始的JavaBean规范之中有所改动。参考下边Java代码对比:
上述代码段演示了两种不同风格的set
方法的区别,而在Vert.x中所有的Options方法在设置数据时都使用了第二种,第一种是通用的JavaBean
规范,而第二种就是Vert.x中的Fluent风格。
2.3. 构造和默认
Options中的构造函数主要有三个(读者可理解成是绝对统一的,几乎所有的Options都包含这三个构造函数。)
默认无参构造函数
拷贝类型的构造函数
自动生成类构造函数(JsonObject作参数)
**「注」**上述的第三种构造函数就是
codegen
自动生成的构造函数,它会在生成过程中生成Converter
类,并且使用它对统一的JsonObject方法执行转换。
Options中有默认无参的构造函数,而这些构造函数在构造Options对象时使用的默认值不是Java中的类型默认值,而是自定义的值,每个Options中都定义了静态公有变量对每个配置项提供默认值,定义片段如下:
2.4. 常用Options
到这里,Vert.x中的Options基本结构就分析清楚了,读者可以按照这种思路去解读其他所有的Options,在整个Vert.x框架中,Options的基础结构是一致的,这里列举常用的Options给读者参考(vertx-core
项目中):
上边枚举了vertx-core
项目中所有涉及的Options结构的常用类和相关说明,对应的结构图如下:
本章并不打算给读者讲解每一个Options的内容细节,主要是通过对Options结构的解读让读者掌握完整的Options结构的分析和理解方式,同时提供Vert.x中的标准的Options的整体关系,让读者从高处俯瞰整个Options部分的内容。
3. 开发Options
看完了Options的整体结构,本章节我们来学习开发自定义的Options,为了让读者更加了解Options的结构,这里不使用codegen
,而采用最原始的方式来编写Options部分的代码。Options的主要目的是提供配置项信息,它的一切都是以配置为基础,先看下边的配置片段:
上述代码取自Zero内部集群管理器,读者不要误解,这里将要开发的并不是单独针对options
配置项,而是clustered
节点配置项,按照前天提到过的结构,先开发一个Converter
,参考下边代码:
其实这个Converter中由于包含了类名,并且要实例化成该类对应的对象,所以也是无法直接使用codegen
生成代码的原因,上述代码的基本格式参考了Vert.x中内置生成的Converter格式,而最后的manager
节点的处理则是自定义逻辑,Fn.safeNull
是非空安全执行类,保证在处理过程中不会读取到任何null
的值,而Ut.clazz
和Ut.instance
底层则是直接使用反射执行类加载和对象实例化的操作。写好了Converter的代码后,再来看看ClusterOptions
部分:
按照Options的结构,一个完整的ClusterOptions
就开发好了。在自定义Options的过程中,这里我把Class<?>
压到底层作为了配置项,这是没有直接使用codegen
的原因,它包含了部分自定义的反射逻辑,并且为整个程序拿到ClusterManager
的引用。从配置这个概念来讲,这种做法比较混淆,但是从实际使用效果上看来,这样做也有好处。
在真实系统中,如果使用面向接口编程,那么实现类会自然引入可配置的特性,一旦可配置,真正代码中就不可能通过new X()
的方式构造,这样的系统往往会变得灵活,但付出的代价就是牺牲代码的健壮性,而此时只能通过开发人员自身来保证代码质量。主要问题如:
类名异常:开发人员有可能因为拼写错而导致常见的
ClassNotFound
的异常,如果这个类名配置是必须的则比较好处理,抛出异常也能提示开发人员这里有问题,直接将异常信息打印出来都可行;但是如果这个类名配置仅仅是可选配置,就意味着即使这里发生了ClassNotFound
,系统可以直接提供默认行为忽略该配置,而不是直接抛出异常信息,这种情况下,自定义代码逻辑的优势就很明显了。接口冲突:在配置这种带反射信息的数据时,还容易犯的一个错就是接口实现问题,有可能你配置的类本身并没有实现你所期望的接口,这种情况下,即使类加载成功了,在实例化过程中通常会因为类型原因导致实例化失败。但是,我们通常期望在实例化失败时,系统依旧可以运行,系统是忽略还是抛出异常取决于本身的业务场景。
约定确实是个好东西,但它毕竟不是约束,再完美的约定都无法保证人为去破坏它,从编程角度讲这是可以的,但从系统角度讲,这样会破坏系统的严谨性。为了让系统在任何场景下都可以适配配置数据,就需要在Options的构造过程中尽可能保证不出错,这也是Options存在于Vert.x中的意义,如果读者仔细去研究所有Options的代码,它都存在默认值,这个目的就是为了保证任何情况下,组件本身拥有一套可运行的配置。在项目开发中,配置数据是面向开发人员而不是用户,不论是静态配置还是动态配置,都需要做到完美约定——即在任何情况下,配置数据都不能出错,一旦出错系统就会变得不稳定,所以作为开发人员在书写自定义Options的过程中,需要仔细思考、设计和编码,阻击所有有可能的错误发生,做到底层稳定(所以读者才会看到上述代码中繁琐的Fn.safeNull
,既检查输入又保证输出)。
开发了自定义的Options过后,您就可以在您的代码中使用了,最后提供一段消费Options的代码,读者慢慢去体会一下:
早在第一个章节我们演示了如何开发一个完整的启动器,这里不累赘介绍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
子项目。
最后更新于