写点什么

基于 Java 9 模块系统和 Vert.x 开发持续集成系统

  • 2018-03-07
  • 本文字数:3925 字

    阅读完需:约 13 分钟

本文要点

  • Vert.x 兼容 Java 9,可一起用于构建应用程序。
  • 很多 Java 类库仍然不支持模块化。
  • 对“自动模块”要格外小心(一些类库还没有成为模块)。
  • Java 内置的 Nashorn JavaScript 运行环境对于 Vert.x 的应用程序来说十分有用。

这篇文章将介绍如何使用 Eclipse Vert.x 设计和开发一个基于消息驱动的响应式持续集成(CI)系统。我们将利用 Java 平台模块系统(JPMS)来构建一个由多个模块组成的应用程序,模块之间通过定义好的接口进行通信。

有了 JPMS,架构师和开发者就可以使用模块来重构大型的遗留系统,或者用它们来创建新的应用程序。不过,要在模块系统中使用已有的 Java 类库并不是件容易的事。因此,我们也会探讨在使用 JPMS 过程中可能遇到的各种问题,以及如何解决这些问题。

先让我们来定义这个 CI 系统的最小可用产品(MVP),我们将把它构建成 Docker 原生系统。这个系统需要提供如下特性,并通过 REST API 暴露出来:

  1. 支持针对仓库的 CRUD 操作。一个仓库代表一个项目,并带有 Git 仓库的连接地址。
  2. 支持“管道即代码”。管道定义了构建流程,并使用 JavaScript 来定义,JavaScript 脚本文件可以与代码保存在一起。
  3. 提供用于启动或关闭管道的 API。管道的一个实例就代表一次构建过程。

定义好 MVP 后,就可以开始构建我们的系统了。首先要创建项目的骨架,可以使用 IntelliJ 提供的多模块 Gradle 项目模板来创建骨架。因为要使用 JDK 9,所以最好可以选择最新版的 Gradle(在写这篇文章是最新版是 4.4.1)。我们还需要添加 Jigsaw 插件,并把代码兼容性设置为 Java 9。项目的主文件“build.gradle”应该看起来像下面这样:

与其他大多数系统一样,我们将会有一个公共库,用来放置实体类、工具类、共享常量、查询解析器等。我们把这个公共库定义成一个Java 9 模块。

之前已经讲过,Java 9 模块是接口、类和资源文件的集合,具有自描述的特点,而且有自己的名字。JPMS 引入了“module-info.java”文件,开发者用它定义模块的公共契约和对其他模块的依赖。我们也将使用这个文件来命名我们的模块,并指定对其他模块的依赖以及模块自身暴露出来的公共包。

下图是module-info.java 文件的示例代码:

每个“module-info.java”文件都以关键字“module”作为开头,后面跟上模块的名字。用在包命名上的反向域名命名方式也可以用在模块的命名上。

代码块中有两个新的关键词——“exports”和“requires”。“exports”用于声明由该模块暴露出来的公共包,也就是模块的公共API。“requires”用于声明对其他模块的依赖。

那么,问题来了,如果一个Java 9 模块的依赖包并不是模块,那该怎么办?这个时候,自动模块就派上用场了。

正如它的名字告诉我们的那样,非模块的JAR 包会被自动转成模块,并基于JAR 包的名字来生成模块名。模块名的生成遵循这样的规则:以JAR 包文件开头,去掉扩展名,用点号替代连字符,如果有版本号就把版本号去掉。这样的话,“vert-core-3.5.0.jar”对应的模块名就是“vertx.core”。不过,这种方式不一定都能奏效,后面我们会举一个与Netty 依赖包相关的例子。

除了核心模块,我们还要定义其他一些模块,用于访问数据库、用户认证、运行引擎以及与CI 系统中的其他插件交互。

在介绍了Java 模块化的一些概念后,接下来让我们来聊聊Vert.x。Vert.x 是一个工具套件,提供了非阻塞的API,也就是说,Vert.x 应用程序只需要使用很少的线程就可以处理大量的并发请求。Vert.x 采用了multi-reactor 模式来达到这个目的。

熟悉JavaScript 的开发者或许还记得单线程事件循环模型,multi-reactor 模式与之类似,只不过它使用了多个线程。Vert.x 根据给定服务器的CPU 核数创建相应个数的事件循环对象。

Vert.x 还提供了另一种基于 actor 的并发模型。在 Vert.x 生态系统中,actor 被称为“verticle”,verticle 之间通过 JSON 消息进行通信,这些消息通过事件总线进行传送。我们还可以指定部署多少个 verticle 实例。

事件总线可以是一个集群,使用集群管理器来管理,比如 Hazelcast 或 Zookeeper。我们可以把运行在 Vert.x 实例上的 verticle 或 verticle 组合看成是微服务。mutli-reactor 模型、verticle 和事件总线让 Vert.x 应用程序具备了高响应式、高弹性的特点,因此,我们可以说 Vert.x 应用程序是反应式的。

现在让我们来看看这个 CI 系统的整体流程:

如上图所示,有好几个verticle 通过Vert.x 事件总线进行通信。要注意,图中的插件也是verticle。Server verticle 是CI 系统的入口,对外暴露了一个REST API,命令行或GUI 客户端可以通过这个API 指定代码仓库的连接地址、创建和运行构建管道。

下面的代码告诉我们如何在Vert.x 中定义API 和路由:

我们使用Vert.x 的Web 类库来定义REST API,而且所有的路由均以“/api/v1/”作为前缀。Vert.x 还提供了很多其他类库,用于快速开发反应式应用程序。

例如,我们可以使用Web API 类库来设计一个基于OpenAPI 3 的应用程序API,这个类库会帮我们处理好请求验证和安全验证问题。Vert.x 的OAuth 类库可用来提高应用程序API 的安全性,OAuth 厂商可以是谷歌、Facebook,也可以自定义。

在上一张图片中,Engine verticle 负责协调管道的执行。在客户端调用Server verticle 提供的API 之后,Server verticle 向Engine verticle 发送消息,Engine verticle 在收到消息之后会初始化一个新的flow 对象。

flow 对象实际上是一个简单的状态机,用来跟踪管道的执行状态。在任意时刻,flow 对象可能处于这三种状态中的一种:setup、run 或 teardown。它会根据输入消息来改变状态。在进入一个新的状态时,flow 对象会触发一个事件,并将事件发送到事件总线。

注册到事件总线上插件会处理这些消息,并把处理结果通过事件总线异步传回。下面的代码演示了如何注册一个消息处理器、创建 flow 对象以及处理流入的消息:

Engine verticle 也负责定位和部署其他插件或 verticle。我们使用了在 Java 6 中引入并在 Java 9 中改进过的服务加载器机制,用它在服务器启动过程中定位和部署插件。为了更好地理解服务加载机制,有必要讨论一下服务和服务提供者。

服务其实就是一个已知的接口或类(通常是抽象类),而服务提供者则是服务的具体实现。ServiceLoader 类用于加载实现了给定服务的服务提供者。我们可以在模块中声明它使用了某个特定的服务,然后使用 ServiceLoader 来定位和加载部署在运行环境中的服务提供者。

例如,server 模块声明了它要使用 Plugin 接口,workspace 模块则声明它将提供两个实现了 Plugin 接口的服务。

因此,server 模块在启动的时候,它会调用ServiceLoader,找到两个插件,然后把它们部署成verticle:

插件会完成很多工作,包括注册消息处理器,用于处理感兴趣的管道事件。例如,workspace 插件负责同步Git 代码,而脚本解析器插件负责扫描workspace,找出和执行管道脚本文件(使用JavaScript 编写)。执行完脚本文件会生成一些shell 命令,Docker 容器中的脚本执行器插件会执行这些命令。因为Vert.x 使用了Java 内置的Nashorn 引擎,所以完全可以运行JavaScript 代码。要知道,Vert.x 还可以支持JavaScript、Kotlin 和Groovy。

下面是管道脚本文件的部分代码:

收到消息后,脚本执行器插件将会下载Docker 镜像、创建容器并执行shell 命令。

Docker 的 REST API 通常是通过基于 unix domain socket 的 HTTP 来暴露的,并非传统的基于 TCP socket 的 HTTP(S)。这也是 Vert.x 得以发挥其作用的地方之一,我们可以使用 Vert.x 的异步客户端与 Docker 进行交互,而不是使用普通的同步阻塞式方案。

如果项目中包含了由 Netty 提供的原生传输类库,Vert.x 就会转而使用原生传输。如果我们在“build.gradle”和“module-info.java”文件中指定了类似“netty-transport-native-kqueue”这样的依赖,就会发生这样的情况。

或许 Vert.x 下一个版本会支持基于 unix domain socket 的 HTTP。目前,我们可以通过修改 Vert.x 类库的少量代码来解决这个问题。与 Docker 引擎交互的插件代码看起来是这样的:

加入非模块JAR 包“netty-transport-native-kqueue-4.1.15.Final-osx-x86_64.jar”作为依赖将会自动生成一个模块名,不过因为JAR 包含了Java 关键字native,我们的应用程序无法正常编译。

Netty 将在下一个版本中解决这个问题,而在问题得到解决之前,我们可以在 JAR 包的 manifest 文件中加入“Automatic-Module-Name”来绕过这个问题。为此,我们需要解压 JAR 包,修改“MANIFEST.MF”文件,加入“Automatic-Module-Name: io.netty.transport.kqueue”,然后通过下面的命令重新打包:

可以通过下面的命令来验证在manifest 文件中指定的自动模块名是否可以被正确识别:

我们还要使用相同的命令解决其他非模块JAR 包的命名问题。

接下来就可以构建和运行我们的CI 系统了。下面是运行应用程序的命令:

为了支持JPMS,我们在“javac”和“java”命令中增加了一些新的参数,这些参数告诉Java 编译器和运行时,使用模块路径和模块JAR 包来替代原先的类路径。

一些值得注意的参数:

“-p”或“—module-path”用于告诉Java 系统在指定的目录中查找Java 模块。

“-m”或“—module”用于指定模块和主类。

在这篇文章里,我们基于Vert.x 设计了一个模块化的微服务应用程序。我们使用了JPMS 和JDK 9,并构建了一个Docker 原生的CI 系统。可以在 GitHub 上下载相关代码,了解如何基于 Vert.x 和模块化系统开发一个小型的自包含模块化 Java 应用程序。

关于作者

Uday Tatiraju 是 Oracle 的首席工程师,在电子商务平台、搜索引擎、后端系统和 Web 与移动编程领域拥有十年的开发经验。

查看英文原文: Building a CI System with Java 9 Modules and Vert.x Microservices

2018-03-07 16:423948
用户头像

发布了 322 篇内容, 共 144.8 次阅读, 收获喜欢 148 次。

关注

评论

发布
暂无评论
发现更多内容

如何为Affinity Publisher或Designer创建条形码?

Rose

Affinity Publisher 条形码设计

火山引擎云原生存储加速实践

字节跳动云原生计算

大数据 云原生 存储

Mac效率办公软件 Alfred 5中文完美破解版 兼容M1

iMac小白

Alfred 5下载 Alfred 5 Mac Alfred 5破解版

助力工业数字化!TDengine 与恩菲 MIM+ 工业互联网平台实现兼容性互认

TDengine

tdengine 时序数据库

云工作流 CloudFlow 重磅发布,流程式开发让云上应用构建更简单

Serverless Devs

阿里云 Serverless 函数计算FC

Mac日程管理软件Fantastical 中文破解版 让日程管理更加便捷!

Rose

日程管理App Fantastical Mac中文版 Fantastical下载 Mac日历软件

架构实战营 - 模块四作业

王朝阳

架构实战营

极致性能优化:前端SSR渲染利器Qwik.js | 京东云技术团队

京东科技开发者

性能优化 前端 前端性能 企业号11月PK榜 Qwik.js

Golang面试题从浅入深高频必刷「2023版」

王中阳Go

Go golang 面试题 大厂面经 最新面试题

PhotoFoundry for Mac(专业的照片滤镜编辑软件)v1.2激活版

iMac小白

PhotoFoundry下载 PhotoFoundry破解版 PhotoFoundrymac

endnote x9怎么和word关联?Word中用EndNote X9教程

Rose

Word 2021 许可证 endnote x9 文献写作管理软件

UUID意想不到的block

FunTester

线上JAVA应用平稳运行一段时间后出现JVM崩溃问题 | 京东云技术团队

京东科技开发者

Java 定时任务 JVM 企业号11月PK榜

什么是大模型?一文读懂大模型的基本概念

九章云极DataCanvas

专业终端SSH工具 SecureCRT注册码正式版

胖墩儿不胖y

Mac软件 终端SSH仿真工具 终端仿真器

解决Lightroom无法使用修改照片的问题

Rose

Lightroom Classic 2022 Mac破解软件 Lightroom无法使用 LR2024 Mac中文

Mac窗口管理软件合集|告别混乱屏幕,一切井井有条

Rose

mac窗口管理软件 Mac破解软件 苹果电脑分屏软件 Mac软件下载站

如何使用 Loadgen 来简化 HTTP API 请求的集成测试

极限实验室

集成测试 loadgen 极限科技

Office2019 for Mac(办公套件全家桶) v16.78正式激活版

mac

办公软件 苹果mac Windows软件 office 2019

安全好用的远程协同运维软件重点推荐-行云管家

行云管家

远程运维 运维软件 远程系统 远程协同 协同运维软件

企业制作网站时为何香港云服务器成为首选?

一只扑棱蛾子

香港云服务器

商用解决方案助力产业链实现自循环 OpenHarmony技术大会硬件(南向)生态分论坛圆满落幕

科技热闻

ps2021一键换天空教程

Rose

ps2021破解版 一键换天空 ps2021下载 ps2021天空替换 Photoshop Mac破解版

对话在行人|合众思壮:基于用友BIP重塑业务应用,推进业财融合

用友BIP

对话在行人 数智化领先实践

专业fcpx视频剪辑推荐 Final Cut Pro最新激活中文版

mac大玩家j

Mac软件 fcpx插件 视频编辑器

调用API接口获取淘宝商品评论:方法与实战

Noah

即时通讯技术文集(第22期):IM安全相关文章(Part1) [共13篇]

JackJiang

网络编程 即时通讯 IM

微前端无界机制浅析 | 京东物流技术团队

京东科技开发者

前端 源码剖析 微前端 无界 企业号11月PK榜

用友全球司库十问(六)|新一代票据能力如何实现赋能企业票据管理?

用友BIP

全球司库 票据管理

基于Java 9模块系统和Vert.x开发持续集成系统_Java_Uday Tatiraju_InfoQ精选文章