使用bazel构建spring boot项目

前言

根据官网的定义,Bazel是类似于Make,Maven和Gradle的开源构建和测试工具。它使用人类可读的高级构建语言Starlark(一种基于python的方言)。 Bazel支持多种语言的项目,并为多种平台构建输出。

从我个人角度来看,bazel是一个强大且复杂的构建系统,通过build rule的概念,支持多种语言、不同平台,支持构建C/C++,Java,Android,IOS,Golang,Nodejs,Docker项目

本文的目的是使用bazel去构建并运行一个spring boot项目。

配置bazel编译java项目

在项目根目录中创建.bazelrc文件,设置bazel使用java17构建:

bash 复制代码
build --java_language_version=17 --java_runtime_version=17 --tool_java_language_version=17 --tool_java_runtime_version=17
test  --java_language_version=17 --java_runtime_version=17 --tool_java_language_version=17 --tool_java_runtime_version=17

在根目录中创建workspace文件,并引入相关的java依赖:

bazel 复制代码
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

RULES_JVM_EXTERNAL_TAG = "6.0"
RULES_JVM_EXTERNAL_SHA = "c44568854d8bb92fe0f7dd6b1e8957ae65e45e32a058727fcf62aaafbd36f17b"

http_archive(
    name = "rules_jvm_external",
    strip_prefix = "rules_jvm_external-%s" % RULES_JVM_EXTERNAL_TAG,
    sha256 = RULES_JVM_EXTERNAL_SHA,
    urls = [
        "https://github.com/bazelbuild/rules_jvm_external/archive/%s.zip" % RULES_JVM_EXTERNAL_TAG,
        "https://mirror.ghproxy.com/https://github.com/bazelbuild/rules_jvm_external/archive/%s.zip" % RULES_JVM_EXTERNAL_TAG,
    ]
   
)

load("@rules_jvm_external//:repositories.bzl", "rules_jvm_external_deps")

rules_jvm_external_deps()

load("@rules_jvm_external//:setup.bzl", "rules_jvm_external_setup")

# rules_jvm_external_setup()

load("@rules_jvm_external//:specs.bzl", "maven")
load("@rules_jvm_external//:defs.bzl", "maven_install")

然后使用使用maven_install导入spring boot项目的相关依赖。 这里的shiro依赖被我单独拎出来做特殊处理去兼容java17

bazel 复制代码
shiros = [
    maven.artifact(
        group = "org.apache.shiro",
        artifact = "shiro-spring",
        version = "1.13.0",
        classifier = "jakarta",
        exclusions = [
            maven.exclusion(
                  group = "org.apache.shiro",
                  artifact = "shiro-core",
            ),
            maven.exclusion(
                  group = "org.apache.shiro",
                  artifact = "shiro-web",
            ),
        ]
    ),
    maven.artifact(
        group = "org.apache.shiro",
        artifact = "shiro-core",
        version = "1.13.0",
        classifier = "jakarta",
    ),
    maven.artifact(
        group = "org.apache.shiro",
        artifact = "shiro-web",
        version = "1.13.0",
        classifier = "jakarta",
    ),
   
]

maven_install(
    artifacts = [
        "org.springframework.boot:spring-boot:3.2.2",
        "org.springframework.boot:spring-boot-starter:3.2.2",
        "org.springframework.boot:spring-boot-loader-tools:3.2.2",
        "org.springframework.boot:spring-boot-loader:3.2.2",
        "org.springframework.boot:spring-boot-starter:3.2.2",
        "org.springframework.boot:spring-boot-starter-web:3.2.2",
        "org.springframework.boot:spring-boot-starter-jdbc:3.2.2",
        "org.springframework.boot:spring-boot-starter-quartz:3.2.2",
        "org.springframework.boot:spring-boot-starter-aop:3.2.2",

        #"org.springframework.boot:spring-boot-configuration-processor:3.2.2",

        "io.springfox:springfox-boot-starter:3.0.0",
        "com.auth0:java-jwt:3.19.4",
        "org.postgresql:postgresql:42.4.0",

        "jakarta.servlet:jakarta.servlet-api:6.0.0",  
        'javax.annotation:javax.annotation-api:1.3.2',

        "org.springframework.boot:spring-boot-devtools:3.2.2",
        "org.springframework.boot:spring-boot-starter-test:3.2.2"
         
    ] + shiros,
    fetch_sources = True,
    repositories = [
        "https://maven.aliyun.com/repository/public/",
        "https://maven.aliyun.com/nexus/content/groups/public/",
        "http://uk.maven.org/maven2",
        "https://maven.google.com",
        "https://repo1.maven.org/maven2",
    ],
    # maven_install_json = "//:maven_install.json",
)

maven_install_json = "//:maven_install.json"目前是被注视掉的,这是因为我们需要自动生成改文件:

bash 复制代码
bazel run @maven//:pin 

执行成功之后需要在workspace中给maven_install添加maven_install_json属性, 并将加载@maven//:defs.bzl中的pinned_maven_install配置

perl 复制代码
maven_install(
    artifacts = # ...,
    repositories = # ...,
    maven_install_json = "@//:maven_install.json",
)

load("@maven//:defs.bzl", "pinned_maven_install")
pinned_maven_install()

在更新maven依赖的话,那么可以使用下面的依赖更新maven_install.json

bazel 复制代码
bazel run @unpinned_maven//:pin

maven_install_json是可以不用配置的,但是我推荐尽量配置。它有两个好处,可重复性和速度(reproducibility and speed)。

在根目录创建BUILD.bazel进行编译java项目的准备:

bazel 复制代码
oad("@rules_java//java:defs.bzl", "java_binary", "java_library", "java_test")

package(default_visibility = ["//visibility:public"])

java_library(
    name = "jobs-lib",
    srcs = glob([
        "src/main/java/org/daming/jobs/*.java",
        "src/main/java/org/daming/jobs/api/advice/*.java",
        "src/main/java/org/daming/jobs/api/controller/*.java",
        "src/main/java/org/daming/jobs/api/interceptor/*.java",
        "src/main/java/org/daming/jobs/base/*.java",
        "src/main/java/org/daming/jobs/base/**/*.java",
        "src/main/java/org/daming/jobs/config/**/*.java",
        "src/main/java/org/daming/jobs/pojo/**/*.java",
        "src/main/java/org/daming/jobs/security/**/*.java",
        "src/main/java/org/daming/jobs/service/**/*.java",
        "src/main/java/org/daming/jobs/task/**/*.java",
    ]),
    resources = glob(["src/main/resources/**"]),
    deps = [
        "@maven//:org_springframework_boot_spring_boot_starter_web",
        "@maven//:org_springframework_boot_spring_boot_starter_jdbc",
        "@maven//:org_springframework_boot_spring_boot_starter_quartz",
        "@maven//:org_springframework_boot_spring_boot_starter_aop",

        "@maven//:io_springfox_springfox_boot_starter",
        "@maven//:io_springfox_springfox_core",
        "@maven//:io_springfox_springfox_spi",
        "@maven//:io_springfox_springfox_oas",
        "@maven//:io_springfox_springfox_spring_web",
        "@maven//:io_swagger_swagger_annotations",

        "@maven//:com_auth0_java_jwt",
        "@maven//:org_postgresql_postgresql",
        "@maven//:org_springframework_boot_spring_boot_devtools",

        "@maven//:org_springframework_boot_spring_boot",
        "@maven//:org_springframework_boot_spring_boot_loader",
        "@maven//:org_springframework_boot_spring_boot_loader_tools",
        "@maven//:org_springframework_boot_spring_boot_autoconfigure",
        "@maven//:org_springframework_spring_aop",
        "@maven//:org_springframework_spring_beans",
        "@maven//:org_springframework_spring_core",
        "@maven//:org_springframework_spring_context",
        "@maven//:org_springframework_spring_expression",
        "@maven//:org_springframework_spring_web",

        # "@maven//:org_apache_shiro_shiro_spring_boot_starter",
        "@maven//:org_apache_shiro_shiro_core_jakarta",
        "@maven//:org_apache_shiro_shiro_spring_jakarta",
        "@maven//:org_apache_shiro_shiro_web_jakarta",

        "@maven//:org_slf4j_slf4j_api",

        "@maven//:org_quartz_scheduler_quartz",
        "@maven//:org_aspectj_aspectjweaver",
        "@maven//:org_apache_tomcat_embed_tomcat_embed_core",
        "@maven//:jakarta_servlet_jakarta_servlet_api",  
        "@maven//:jakarta_annotation_jakarta_annotation_api",
        "@maven//:jakarta_xml_bind_jakarta_xml_bind_api",
        "@maven//:com_fasterxml_jackson_core_jackson_core",
        "@maven//:com_fasterxml_jackson_core_jackson_databind"
    ],
)

java_binary(
    name = "jobs",
    main_class = "org.daming.jobs.JobsApplication",
    runtime_deps = [":jobs-lib"],
    deploy_manifest_lines = {
        "Main-Class": "org.daming.jobs.JobsApplication",
    },
)

然后我们可以执行bazel命令去构建一个java项目了:

bazel 复制代码
# 构建
bazel build //:jobs

# 构建并运行
bazel run //:jobs

然后你会发现构建没有问题,但是运行会报错:

bash 复制代码
jobs git:(master) ✗ java -jar bazel-bin/jobs.jar            
no main manifest attribute, in bazel-bin/jobs.jar

由于Springboot的代码需要使用Springboot loader进行启动,Springboot程序的打包逻辑与普通的Java程序不同。这意味着,Bazel原生的 java_binary 无法正常启动Springboot程序。

所以我们需要给bazel配置spring相关支持。

配置rule_spring去构建运行spring boot

我们使用salesforce的rules_spring定义好了的rule去帮助我们构建spring boot项目。

我们在workspace添加rules_spring的规则文件

ini 复制代码
http_archive(
    name = "rules_spring",
    sha256 = "7bb891ccb2f53ca188a769b3a3777be1c38348e18091afea05320f3003b3e886",
    urls = [
        "https://github.com/salesforce/rules_spring/releases/download/2.3.1/rules-spring-2.3.1.zip",
        "https://mirror.ghproxy.com/https://github.com/salesforce/rules_spring/releases/download/2.3.1/rules-spring-2.3.1.zip",
    ],
)

然后在BUILD.bazel中间中导入rules_spring的相关规则:

bazel 复制代码
load("@rules_spring//springboot:springboot.bzl", "springboot")

springboot(  
    name = "springboot",  
    # specify the main class
    boot_app_class = "org.daming.jobs.JobsApplication",
    # refrence the library
    java_library = ":jobs-lib",
    # https://github.com/salesforce/rules_spring/issues/177
    boot_launcher_class = 'org.springframework.boot.loader.launch.JarLauncher',
)

由于spring boot 3.2.0之后使用新的启动器,所以我们这里指定了boot_launcher_class。 如果你使用的版本低于3.2.0,可以直接删除。

现在我们可以编译运行spring boot项目了

bazsl 复制代码
# 构建
bazel build //:springboot

# 运行
bazel run //:springboot

运行部分输出如下:

bazel 复制代码
Application    Name: 
Application Version: 
Spring Boot Version: 3.2.2 (v3.2.2)
2024-02-11 21:49:17.449 INFO [main] org.springframework.boot.StartupInfoLogger:50 Starting JobsApplication using Java 17 with PID 67028 (/private/var/tmp/_bazel_gming001/7a71463ee80a3358d2f71ab2db616aea/execroot/__main__/bazel-out/darwin_arm64-fastbuild/bin/springboot.jar started by gming001 in /private/var/tmp/_bazel_gming001/7a71463ee80a3358d2f71ab2db616aea/execroot/__main__/bazel-out/darwin_arm64-fastbuild/bin/springboot.runfiles/__main__)
2024-02-11 21:49:17.451 INFO [main] org.springframework.boot.SpringApplication:654 No active profile set, falling back to 1 default profile: "default"
2024-02-11 21:49:17.501 INFO [main] org.springframework.boot.logging.DeferredLog:252 For additional web related logging consider setting the 'logging.level.web' property to 'DEBUG'
2024-02-11 21:49:17.846 WARN [main] org.springframework.context.support.AbstractApplicationContext:632 Exception encountered during context initialization - cancelling refresh attempt: java.lang.TypeNotPresentException: Type javax.servlet.http.HttpServletRequest not present
2024-02-11 21:49:17.856 INFO [main] org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLogger:82 

遗留的问题

有两个问题没有解决,第一个是如何运行的单元测试,这个还没研究,不过这个不急。还有一个是这个项目实际上跑不起来,会报错:

log 复制代码
java.lang.TypeNotPresentException: Type javax.servlet.http.HttpServletRequest not present

我原本以为是bazel的配置问题,结果我发现直接maven也跑不起来。。。

我想了想了应该是springfox没有去适配spring boot3的问题,不想降级spring boot的版本话,只能从springfox迁移到springdoc

源码

本文使用的源代码都可以在jobs看到。

参考资料

相关推荐
zhendeWD3 分钟前
springboot项目启动报错:spring boot application in default package
java·spring boot·后端
古茗前端团队7 分钟前
让我带实习生,真的假的?
前端
风象南11 分钟前
SpringBoot实现6种JWT令牌失效方案
java·spring boot·后端
我不吃饼干12 分钟前
倒反天罡,CSS 中竟然可以写 JavaScript
前端·javascript·css
苹果醋31 小时前
SpringCloud Alibaba场景实践(Nacos篇)
java·运维·spring boot·mysql·nginx
10年前端老司机2 小时前
什么!纯前端也能识别图片中的文案、还支持100多个国家的语言
前端·javascript·vue.js
乐神嘎嘎嘎2 小时前
springboot速通
java·spring boot
摸鱼仙人~2 小时前
React 性能优化实战指南:从理论到实践的完整攻略
前端·react.js·性能优化
程序员阿超的博客3 小时前
React动态渲染:如何用map循环渲染一个列表(List)
前端·react.js·前端框架
magic 2453 小时前
模拟 AJAX 提交 form 表单及请求头设置详解
前端·javascript·ajax