【AICon】探索八个行业创新案例,教你在教育、金融、医疗、法律等领域实践大模型技术! >>> 了解详情
写点什么

基于 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:423501
用户头像

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

关注

评论

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

又一重要进展发布!OpenMMLab算法仓支持昇腾AI训练加速

华为云开发者联盟

人工智能 华为云 昇腾AI 企业号 1 月 PK 榜

TracedModule: 更友好的模型表示方案,模型训练到部署的桥梁

MegEngineBot

深度学习 开源 MegEngine 模型训练到部署

ChatGPT中文版重装上阵

felix

openai ChatGPT AIMODELMARKET

软件测试/测试开发 | 接口测试之HTTP、HTTPS 抓包分析

测试人

https 软件测试 HTTP 自动化测试 测试开发

FL Studio2024中文版水果电音舞曲制作软件

茶色酒

FL Studio21 FL Studio2024

云上的米开朗基罗:在不确定时代,寻找建筑般的确定性

脑极体

直播|镜舟 x Smartbi《后疫情下如何利用数据驱动企业逆势破局》

镜舟科技

数据库 镜舟数据库

用无线控制LED显示屏的10个特点

Dylan

LED显示屏 全彩LED显示屏 led显示屏厂家

软件测试/测试开发 | 服务端接口自动化测试, Requests 库的这些功能你了解吗?

测试人

软件测试 自动化测试 接口测试 测试开发 requests

使用“宝塔一键迁移”工具,将单机版typecho博客系统迁移到京东云cvm云主机

京东科技开发者

服务器 京东云 安装宝塔 云迁移 企业号 1 月 PK 榜

软件测试/测试开发 | 接口测试之HTTP 协议讲解

测试人

软件测试 HTTP 自动化测试 接口测试 测试开发

合作升级|Kyligence 跬智智能分析平台入选华为云联营商品

Kyligence

数据分析

MYSQL数据库主从配置

Jackey

MySQL 数据库

CAP定理

threedayman

分布式 架构设计 java; 基础

SLS:基于 OTel 的移动端全链路 Trace 建设思考和实践

阿里巴巴终端技术

数据采集 Trace 移动端

报告下载 | DQMIS高端闭门论坛成果报告——《2022第六届数据质量管理国际峰会关于数据要素发展几点看法和建议》

数据质量管理智库

数据 数据治理 数据安全 隐私计算 数据要素

从指标到洞察力的普罗米修斯

宋小生

Prometheus 普罗米修斯 普罗米修斯监控

逃不开的安迪-比尔定律,在智能机器人时代该如何破解?

优必选科技

人工智能 机器人 视觉处理

软件测试/测试开发 | 如何模拟真实使用场景?mock 技术来帮你

测试人

软件测试 自动化测试 接口测试 测试开发 Mock

web 3d的开发技术方案选型

好孩子

web3d

[原生1v1视频源码]社交市场趋于饱和,出海成为1v1语聊平台的新选择

山东布谷科技胡月

社交APP出海 视频社交APP开发 1v1交友app开发 一对一视频语音系统搭建

重磅!持续应用安全(CAS)白皮书发布,云起无垠入选

云起无垠

Fuzzing CAS白皮书

书单 | 春节假期,我想把这几本书带回家!

博文视点Broadview

稳定支撑千万级月活,华为日历背后的英雄

华为云开发者联盟

数据库 后端 华为云 企业号 1 月 PK 榜

java 本地应用程序加载与修改properties配置文件

JefferLiu

logback 默认配置文件

JefferLiu

Mega 改进序列模型,引入移动平均捕捉时空依赖

Zilliz

计算机视觉

软件测试/测试开发丨接口测试经典面试题:Session、cookie、token有什么区别?

测试人

软件测试 自动化测试 接口测试 测试开发

架构实战营 - 模块 4- 作业

zealot0317

喜讯!云起无垠获评“2022中国科创好公司”

云起无垠

网络安全 Fuzzing 2022中国科创好公司

【iOS逆向与安全】系统推送服务(APNS)拦截

小陈

安卓 ios开发 逆向 iOS逆向 ios安全

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