如何使用 Bazel 构建 Java 项目

更新于 2025-12-29

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 文件:

  1. load 中添加 java_library

    load("@rules_java//java:defs.bzl", "java_binary", "java_library")
    
  2. 添加 java_library 目标:

    java_library(
        name = "Greeter",
        srcs = ["src/main/java/com/bazel/example/greetings/Greeter.java"],
    )
    
  3. 将新目标加入 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 如何提升工作流效率、加速大型项目构建有了初步了解。