Mdu Sibisi 2023-07-19
如果你是一名 Java 开发者,很可能已经使用过 ANT、Maven 或 Gradle 来构建项目。这些工具中的许多已经存在了几十年,而 Java 在这些年里发生了很大变化。虽然这些工具仍然可用,但你可能希望使用一种更适应当前应用开发和部署速度的构建工具。
Bazel 是一个开源的自动化构建工具,在可扩展性、可伸缩性和灵活性方面提供了更多优势。它由 Google 开发,但适用于任何规模的组织,帮助加速构建并减少构建配置时间。由于 Bazel 使用缓存的构件,并且只重新构建代码中发生变化的部分,因此它可以快速构建复杂的代码库。
尽管 Bazel 支持大量编程语言和平台,本文将重点介绍如何将其专门用于 Java 项目。本教程将通过一个简单项目,展示 Bazel 的关键功能。
前提条件
在继续之前,我假设你已满足以下条件:
- 系统上已安装最新版 JDK。
- 已设置
JAVA_HOME环境变量。 - 本教程使用 Windows 10 操作系统,但 Bazel 同样支持 macOS 和 Linux。
安装 Bazel
目前安装 Bazel 的最佳方式是使用 Bazelisk。这需要你先安装并配置 Chocolatey(Windows 包管理器)。完成后,以管理员权限打开终端窗口(如命令提示符或 PowerShell),运行以下命令:
choco install bazelisk
当提示是否安装脚本时,输入 A(表示全部同意)。
创建 Java 项目文件
创建一个名为 BazelExample 的文件夹来存放项目,然后创建一个简单的 Java 类。注意:该类必须使用外部依赖项。例如,以下示例代码使用 TextIO 库创建一个简单的控制台应用程序,提示用户输入用户名和密码:
Salutations.java
package com.bazel.example;
import org.beryx.textio.TextIO;
import org.beryx.textio.TextIoFactory;
public class Salutations {
public static void main(String[] args) {
TextIO textIO = TextIoFactory.getTextIO();
String user = textIO.newStringInputReader()
.withDefaultValue("admin")
.read("Username");
String password = textIO.newStringInputReader()
.withMinLength(6)
.withInputMasking(true)
.read("Password");
}
}
建议使用 Maven 标准目录结构,如下所示:
BazelExample
└── src
└── main
└── java
└── com
└── bazel
└── example
└── Salutations.java
配置工作区(Workspace)
要使用 Bazel,必须为源文件、资源和构建输出分配一个工作区。进入项目根目录(如 BazelExample),创建一个无扩展名的文件,命名为 WORKSPACE。
接下来,创建项目的构建文件。
创建构建文件(BUILD)
构建文件告诉 Bazel 在哪里找到源文件以及如何构建它们。同样,在项目根目录下创建一个无扩展名的文件,命名为 BUILD,内容如下:
BUILD
load("@rules_java//java:defs.bzl", "java_binary")
java_binary(
name = "Salutations",
srcs = ["src/main/java/com/bazel/example/Salutations.java"],
main_class = "com.bazel.example.Salutations",
)
除了指定源文件外,BUILD 文件还包含一组称为**规则(rules)的指令和配置。第一行代码从 rules_java 扩展中加载 java_binary 规则。它指示 Bazel 将编译后的文件打包成 .jar 文件,并生成一个名为目标(target)**的包装 shell 脚本。
java_binary 规则包含多个参数:
name:指定最终二进制文件的名称。srcs:指定要编译和构建的源文件。main_class:指定程序启动时运行的主类。
💡 如果有多个源文件,可以使用
glob函数通配:srcs = glob(["src/main/java/com/bazel/example/*.java"])
此时你已具备构建项目的基本要素,但由于示例代码依赖外部库,若不添加依赖,构建和运行都会失败。
添加依赖项并拆分项目
添加外部依赖
要添加 TextIO 作为外部依赖,否则会遇到编译错误。
打开之前创建的 WORKSPACE 文件,添加以下内容:
WORKSPACE
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
# 使用最新版 RULES_JVM_EXTERNAL
RULES_JVM_EXTERNAL_TAG = "5.1"
RULES_JVM_EXTERNAL_SHA = "8c3b207722e5f97f1c83311582a6c11df99226e65e2471086e296561e57cc954"
# 获取 rules_jvm_external 扩展
http_archive(
name = "rules_jvm_external",
strip_prefix = "rules_jvm_external-%s" % RULES_JVM_EXTERNAL_TAG,
sha256 = RULES_JVM_EXTERNAL_SHA,
url = "https://github.com/bazelbuild/rules_jvm_external/releases/download/%s/rules_jvm_external-%s.tar.gz" % (RULES_JVM_EXTERNAL_TAG, RULES_JVM_EXTERNAL_TAG)
)
# rules_jvm_external 依赖此库
http_archive(
name = "bazel_skylib",
sha256 = "b8a1527901774180afc798aeb28c4634bdccf19c4d98e7bdd1ce79d1fe9aaad7",
urls = [
"https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.4.1/bazel-skylib-1.4.1.tar.gz",
"https://github.com/bazelbuild/bazel-skylib/releases/download/1.4.1/bazel-skylib-1.4.1.tar.gz",
],
)
上述代码首先加载 http_archive 规则,用于从网络获取依赖。接着引入 rules_jvm_external 扩展,使 Bazel 能从 Maven 仓库拉取 Java 依赖。
✅ 建议始终使用最新版本的
rules_jvm_external(撰写本文时为 5.1)。
现在,告诉该扩展要做什么。继续在 WORKSPACE 中添加:
load("@rules_jvm_external//:defs.bzl", "maven_install")
maven_install(
artifacts = [
"org.beryx:text-io:3.4.1",
],
repositories = [
"https://maven.google.com",
"https://repo1.maven.org/maven2",
"https://jcenter.bintray.com/",
"http://uk.maven.org/maven2",
],
)
这使用 maven_install 规则指定要获取的依赖(artifacts)和搜索的仓库(repositories)。仓库越多,Bazel 找到依赖的可能性越大。
保存并关闭 WORKSPACE 文件。
接下来修改 BUILD 文件,在 java_binary 中添加依赖:
BUILD
java_binary(
name = "Salutations",
main_class = "com.bazel.example.Salutations",
srcs = ["src/main/java/com/bazel/example/Salutations.java"],
deps = ["@maven//:org_beryx_text_io"],
)
📌 注意:依赖路径中的连字符
-和点.会被替换为下划线_,形成 Bazel 的规范路径(Canonical path)。
拆分项目并添加本地依赖
在 src/main/java/com/bazel/example/ 下创建新文件夹 greetings,并在其中创建 Greeter.java:
Greeter.java
package com.bazel.example.greetings;
public class Greeter {
public static void sayHello() {
System.out.println("Hello");
}
}
在 Salutations.java 中导入 Greeter 并调用其方法:
Salutations.java(更新后)
package com.bazel.example;
import com.bazel.example.greetings.Greeter;
import org.beryx.textio.TextIO;
import org.beryx.textio.TextIoFactory;
public class Salutations {
public static void main(String[] args) {
Greeter.sayHello();
TextIO textIO = TextIoFactory.getTextIO();
String user = textIO.newStringInputReader()
.withDefaultValue("admin")
.read("Username");
String password = textIO.newStringInputReader()
.withMinLength(6)
.withInputMasking(true)
.read("Password");
}
}
更新 BUILD 文件:
在
load中添加java_library:load("@rules_java//java:defs.bzl", "java_binary", "java_library")添加
java_library目标:java_library( name = "Greeter", srcs = ["src/main/java/com/bazel/example/greetings/Greeter.java"], )将新目标加入
deps:java_binary( name = "Salutations", main_class = "com.bazel.example.Salutations", srcs = ["src/main/java/com/bazel/example/Salutations.java"], deps = ["@maven//:org_beryx_text_io", ":Greeter"], )
最终 BUILD 文件如下:
BUILD(完整版)
load("@rules_java//java:defs.bzl", "java_binary", "java_library")
java_library(
name = "Greeter",
srcs = ["src/main/java/com/bazel/example/greetings/Greeter.java"],
)
java_binary(
name = "Salutations",
main_class = "com.bazel.example.Salutations",
srcs = ["src/main/java/com/bazel/example/Salutations.java"],
deps = ["@maven//:org_beryx_text_io", ":Greeter"],
)
构建项目
回到终端,进入项目根目录,运行:
bazel build //:Salutations
首次构建可能较慢(需下载依赖并编译),完成后输出类似:
INFO: Build completed successfully...
构建产物位于 bazel-bin/ 目录下。运行应用:
bazel-bin/Salutations
⚠️ 可能会看到 SLF4J 日志警告,可忽略。
按提示输入用户名和密码即可测试。
使用 Bazel 生成依赖图
Bazel 可生成依赖关系图,帮助可视化项目结构。
运行以下命令生成完整依赖图:
bazel query "deps(//:Salutations)" --output graph
但结果通常很“嘈杂”(包含工具链、传递依赖等)。可简化为仅核心依赖:
bazel query --notool_deps --noimplicit_deps "deps(//:Salutations)" --output graph
输出示例:
digraph mygraph {
node [shape=box];
"//:Salutations"
"//:Salutations" -> "//:Greeter"
"//:Salutations" -> "@maven//:org_beryx_text_io"
...
}
将输出粘贴到 Graphviz Online 即可生成清晰的依赖图。
打包可部署的 JAR
默认生成的 Salutations.jar 不包含依赖,无法独立运行。验证:
jar -tf bazel-bin/Salutations.jar
输出仅包含:
META-INF/
META-INF/MANIFEST.MF
com/bazel/example/Salutations.class
要生成包含所有依赖的可部署 JAR,运行:
bazel build //:Salutations_deploy.jar
新文件 Salutations_deploy.jar 位于 bazel-bin/ 中,包含所有类和依赖,可直接运行:
java -jar bazel-bin/Salutations_deploy.jar
结语
本教程介绍了使用 Bazel 构建 Java 项目的基础知识。你现在应该对 Bazel 如何提升工作流效率、加速大型项目构建有了初步了解。