场景描述

在maven里可以在pom.xml中统一定义项目依赖,依赖版本,插件,多环境构建,子模块;定义packaging 为pom类型,各子模块按需引入,并且指定环境profiles构建。
在gradle中也可以实现依赖统一管理,各模块按需引入,区分环境的构建任务。

先上效果图
统一定义依赖,依赖版本,插件
统一依赖版本统一定义依赖
依赖分组和统一定义插件
子模块按需引入
子模块按需引入
分环境打包task及docker镜像打包任务
在这里插入图片描述

环境

gradle8.1.1, springboot3.1.0, jdk17

步骤

  1. 创建项目
    通过IDEA或Spring Initializer 创建springboot+kotlin+gradle的基础项目;
  2. 修改gradle/wrapper/gradle-wrapper.properties 中gradle版本 https://services.gradle.org/distributions/gradle-8.1.1-bin.zip
  3. 统一定义依赖,版本,插件;由gradle catalogs实现,catalogs官方文档介绍
  4. 在根目录创建libs.versions.toml 文件,自己当前项目文件内容如下
#### 定义项目所有依赖库
## 统一定义版本
[versions]
project-version= "0.0.1"
springboot = "3.1.0"
kotlin = "1.8.21"
docker-plugin = "9.3.0"
mybatis-plus = "3.5.3"
velocity = "2.0"
mysql = "8.0.32"
hutool = "5.8.17"
knife4j = "4.3.0"
easyexcel = "3.2.1"
dynamic-datasource = "3.6.0"
sa-token = "1.35.0.RC"
spring-cloud = "2022.0.2"
spring-cloud-alibaba = "2022.0.0.0-RC2"
spring-doc = "2.1.0"
spring-redisson = "3.21.3"
dubbo = "3.2.0"
aliyun-sms = "2.0.23"
fastjson2 = "2.0.39"
dingtalk-service-sdk = "2.0.0"
dingtalk-sdk = "2.0.29"
flowable = "7.0.0.M1"
liteflow = "2.10.5"
rocketmq = "2.2.3"
minio = "8.5.4"
## 统一定义依赖
[libraries]
mybatis-plus = { module = "com.baomidou:mybatis-plus-boot-starter", version.ref = "mybatis-plus" }
mybatis-generator = { module = "com.baomidou:mybatis-plus-generator", version.ref = "mybatis-plus" }
velocity = { module = "org.apache.velocity:velocity-engine-core", version.ref = "velocity" }
mysql = { module = "mysql:mysql-connector-java", version.ref = "mysql" }
# 使用的是springdoc-openapi3, https://springdoc.org/#migrating-from-springfox 不再是springfox
knife4j = { module = "com.github.xiaoymin:knife4j-openapi3-jakarta-spring-boot-starter", version.ref = "knife4j" }
knife4j-gateway = { module = "com.github.xiaoymin:knife4j-gateway-spring-boot-starter", version.ref = "knife4j" }
hutool-core = { module = "cn.hutool:hutool-core", version.ref = "hutool" }
hutool-captcha = { module = "cn.hutool:hutool-captcha", version.ref = "hutool" }
hutool-crypto = { module = "cn.hutool:hutool-crypto", version.ref = "hutool" }
hutool-http = { module = "cn.hutool:hutool-http", version.ref = "hutool" }
hutool-extra = { module = "cn.hutool:hutool-extra", version.ref = "hutool" }
hutool-json = { module = "cn.hutool:hutool-json", version.ref = "hutool" }
easyexcel = { module = "com.alibaba:easyexcel", version.ref = "easyexcel" }
satoken-core = { module = "cn.dev33:sa-token-core", version.ref = "sa-token" }
satoken-web = { module = "cn.dev33:sa-token-spring-boot3-starter", version.ref = "sa-token" }
satoken-reactor = { module = "cn.dev33:sa-token-reactor-spring-boot3-starter", version.ref = "sa-token" }
satoken-dubbo = { module = "cn.dev33:sa-token-dubbo", version.ref = "sa-token" }
dynamic-datasource = { module = "com.baomidou:dynamic-datasource-spring-boot-starter", version.ref = "dynamic-datasource" }
spring-boot = { module = "org.springframework.boot:spring-boot-dependencies", version.ref = "springboot" }
spring-cloud = { module = "org.springframework.cloud:spring-cloud-dependencies", version.ref = "spring-cloud" }
spring-cloud-alibaba = { module = "com.alibaba.cloud:spring-cloud-alibaba-dependencies", version.ref = "spring-cloud-alibaba" }
spring-doc-common = { module = "org.springdoc:springdoc-openapi-starter-common", version.ref = "spring-doc" }
spring-doc-webflux = { module = "org.springdoc:springdoc-openapi-starter-webflux-ui", version.ref = "spring-doc" }
spring-doc-webmvc = { module = "org.springdoc:springdoc-openapi-starter-webmvc-ui", version.ref = "spring-doc" }
spring-redisson = { module = "org.redisson:redisson-spring-boot-starter", version.ref = "spring-redisson" }
spring-dubbo = { module = "org.apache.dubbo:dubbo-spring-boot-starter", version.ref = "dubbo" }
dubbo = { module = "org.apache.dubbo:dubbo", version.ref = "dubbo" }
dubbo-nacos = { module = "org.apache.dubbo:dubbo-registry-nacos", version.ref = "dubbo" }
aliyun-sms = { module = "com.aliyun:dysmsapi20170525", version.ref = "aliyun-sms" }
kotlin-fastjosn2 = { module = "com.alibaba.fastjson2:fastjson2-kotlin", version.ref = "fastjson2" }
dingtalk-service = { module = "com.aliyun:alibaba-dingtalk-service-sdk", version.ref = "dingtalk-service-sdk" }
dingtalk-sdk = { module = "com.aliyun:dingtalk", version.ref = "dingtalk-sdk" }
flowable = { module = "org.flowable:flowable-spring-boot-starter", version.ref = "flowable" }
flowable-bpmn-layout = { module = "org.flowable:flowable-bpmn-layout", version.ref = "flowable" }
liteflow = { module = "com.yomahub:liteflow-spring-boot-starter", version.ref = "liteflow" }
rocketmq = { module = "org.apache.rocketmq:rocketmq-spring-boot-starter", version.ref = "rocketmq" }
minio = { module = "io.minio:minio", version.ref = "minio" }
## 将多个需要同时引入的依赖定义成一个组,使用时直接引入组
[bundles]
hutool = ["hutool-core", "hutool-captcha", "hutool-crypto", "hutool-http", "hutool-extra", "hutool-json"]
spring-doc = ["spring-doc-common", "spring-doc-webflux", "spring-doc-webmvc"]
datasource = ["mysql", "dynamic-datasource"]
dubbo = ["dubbo", "spring-dubbo", "dubbo-nacos", "kotlin-fastjosn2", "satoken-dubbo"]
mybatisplus-generator = ["mybatis-generator", "velocity"]
flowable = ["flowable", "flowable-bpmn-layout"]
## 统一定义插件
[plugins]
springboot = { id = "org.springframework.boot", version.ref = "springboot" }
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
kotlin-spring = { id = "org.jetbrains.kotlin.plugin.spring", version.ref = "kotlin" }
docker = { id = "com.bmuschko.docker-spring-boot-application", version.ref = "docker-plugin" }
  1. 在根目录settings.gradle.kts 引入依赖文件,顺便定义全局镜像仓库
import java.net.URI

dependencyResolutionManagement {
    repositories {
        // 使用阿里云 镜像仓库
        maven {url = URI("https://maven.aliyun.com/nexus/content/groups/public/") }
        //私库
        maven { url = URI("xxx") }
        mavenCentral()
    }
    // 统一依赖库定义
    versionCatalogs {
        create("projectLibs"){
            from(files("./libs.versions.toml"))
        }
    }
}
rootProject.name = "project-name"

  1. 在根目录build.gradle.kts配置项目和子模块通用构建配置,直接贴文件内容,有注释说明
import org.apache.tools.ant.filters.ReplaceTokens
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import java.io.FileInputStream
import java.util.*
// 引入插件
plugins {
    alias(projectLibs.plugins.springboot)
    alias(projectLibs.plugins.kotlin.jvm)
    alias(projectLibs.plugins.kotlin.spring)
    alias(projectLibs.plugins.docker)
}
// 定义库引用
val pLibs = projectLibs
// 加载根路径自定义配置属性   用于替换打包时不同环境的变量值
val envProps = Properties()
envProps.load(FileInputStream("${project.projectDir.absolutePath}${File.separator}env.properties"))
subprojects {
    // 全部项目有基础插件kotlin jvm/spring
    apply {
        plugin(pLibs.plugins.kotlin.jvm.get().pluginId)
        plugin(pLibs.plugins.kotlin.spring.get().pluginId)
    }
    // 统一group和version
    group = "xxx"
    version = pLibs.versions.project.version.get()
    java.sourceCompatibility = JavaVersion.VERSION_17

    dependencies {
        // bom 管理着一组依赖的版本,各模块按需引入其中的依赖即可,由bom统一约束着版本,
        // spring boot bom
        implementation(platform(pLibs.spring.boot))
        // spring cloud bom
        implementation(platform(pLibs.spring.cloud))
        // spring cloud alibaba bom
        implementation(platform(pLibs.spring.cloud.alibaba))
    }
    // 排序logback, 使用log4j2
    configurations.all {
        exclude("org.springframework.boot","spring-boot-starter-logging")
        exclude("ch.qos.logback")
    }
    tasks.withType<KotlinCompile> {
        kotlinOptions {
            freeCompilerArgs = listOf("-Xjsr305=strict", "-Xjvm-default=all")
            jvmTarget = "17"
        }
    }
    tasks.withType<Test> {
        useJUnitPlatform()
    }
    tasks.jar {
        enabled = true
        archiveClassifier.set("")
    }

    // 为拥有bootJar的模块创建dev,test,prod的构建任务
    if (getTasksByName("bootJar", false).isNotEmpty()) {
        println("getTask:::" + getTasksByName("bootJar", false))
        tasks.register<Jar>("bootJar-dev") { buildJarTaskConf(this, "dev") }
        tasks.register<Jar>("bootJar-test") { buildJarTaskConf(this, "test") }
        tasks.register<Jar>("bootJar-prod") { buildJarTaskConf(this, "prod") }

        // docker镜像
        apply {
            plugin(pLibs.plugins.docker.get().pluginId)
        }
        docker {
            // docker镜像仓库配置
            registryCredentials {
                url.set("http://ip:port")
                username.set("username")
                password.set("password")
            }
            springBootApplication {
                baseImage.set("xxxx/java:17-latest")
                images.set(setOf("xxx/$name:$version"))
                jvmArgs.set(listOf("-Dfile.encoding=utf-8"))
            }
        }
        // docker测试环境构建并推送镜像任务
        tasks.register<Jar>("dockerBuildAndPushImage-test") { buildDockerTaskConf(this, "test") }
    }
	
	// 包含有bootjar的模块在processResources 任务阶段替换环境变量
    if (getTasksByName("bootJar", false).isNotEmpty() || project.name == "xxx-module") {
        // 在复制文件任务processResources阶段替换变量
        tasks.processResources {
            doFirst {  // 将逻辑放到执行阶段
                val env = System.getProperty("buildEnv", "dev") // 默认dev环境
                println("===============模块 ${project.name} 替换配置文件环境变量 , 构建环境: $env ==================")
                filter(
                    ReplaceTokens::class,
                    "tokens" to
//							envPropsToMap(env)
                            // todo 写个函数自动读取待替换列表转为map
                            mapOf(
                                "nacos.server" to envProps.getOrElse("env.$env.nacos.server") {
                                    throw IllegalArgumentException(
                                        "未找到环境:$env 属性:nacos.server 的配置"
                                    )
                                },
                                "nacos.namespace" to envProps.getOrElse("env.$env.nacos.namespace") {
                                    throw IllegalArgumentException(
                                        "未找到环境:$env 属性:nacos.namespace 的配置"
                                    )
                                },
                                "nacos.username" to envProps.getOrDefault("env.$env.nacos.username", ""),
                                "nacos.password" to envProps.getOrDefault("env.$env.nacos.password", ""),
                                "active.env" to env
                            )
                )
                filteringCharset = "UTF-8"
            }
        }
        tasks.classes {
            dependsOn("clean")
        }
    }
}
// docker服务地址
docker {
    url.set("tcp://xxx:2375")
}

/**
 * 任务执行顺序说明
 * Task#finalizedBy 函数 的作用是为 Gradle 任务 设置任务执行完毕后执行的任务,A.finalizedBy B 的作用是 A 任务执行完毕后 , 执行 B 任务
 * A.finalizedBy B
 * B.dependsOn C
 * A 执行完毕后执行 B , B 依赖于 C , 执行 B 之前要先把 C 执行了。A -> C -> B
 */
/**
 * 构建各个环境的bootJar
 */
fun buildJarTaskConf(jarTask: Jar, env: String) {
    jarTask.group = "build"
    jarTask.dependsOn("clean")
    jarTask.doFirst {
        println("=============== 构建 ${jarTask.archiveFileName.get()} 包 , 构建环境: $env ==================")
        System.setProperty("buildEnv", env)
    }
    jarTask.finalizedBy("bootJar")
}

fun buildDockerTaskConf(dockerPushImage: Jar, env: String) {
    dockerPushImage.group = "docker"
    dockerPushImage.doFirst {
        System.setProperty("buildEnv", env)
    }
    dockerPushImage.finalizedBy("dockerPushImage")
}

  1. 根目录创建env.properties
## dev
env.dev.nacos.server=xxx:8848
env.dev.nacos.namespace=xxx
env.dev.nacos.username=xx
env.dev.nacos.password=xx
## test
env.test.nacos.server=xxx:8848
env.test.nacos.namespace=xxx
env.test.nacos.username=xx
env.test.nacos.password=xx
## prod
env.prod.nacos.server=xxx:8848
env.prod.nacos.namespace=xxx
env.prod.nacos.username=xxx
env.prod.nacos.password=xxx

  1. 创建Module, 一种是包含springboot启动类的模块, 一种是作为公共定义的模块

在这里插入图片描述
在这里插入图片描述
9. 包含springboot启动类的模块的build.gradle.kts文件
在这里插入图片描述
公共定义的模块的build.gradle.kts文件
在这里插入图片描述

  1. 在公共定义模块多了的情况,有些模块可能会出现互相依赖,这时候需要解决,解决方式
    1. 尽可能依赖最小模块,比如只引入mybatisplus-core在这里插入图片描述
    2. 抽取出来作为公共模块
    3. springboot3 自动配置类,将引入模块的配置注入spring
      在这里插入图片描述

撒花~

补充一个demo工程,不是文中的工程,文中的为微服务,该demo为单一服务 gitee地址

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐