gradle 指定springcloud 版本_手把手,一步步教你将Maven项目迁移到Gradle

c93debdd04c58726b587f95dccb39a4a.png

最近嫌弃项目编译比较慢,又听说Gradle构建速度会比Maven快很多(官方的说法是至少2倍),于是萌生了将已有项目的Maven编译,迁移到Gradle编译的想法。当然,万事开头难,所以我决定先从一个项目开始试水。下面先简单的说下这个项目的架构概况:

  • 该项目是一个Java项目,混编了一部分Kotlin代码,并且使用了Spock作为单元测试框架。
  • 该项目是一个Maven多模块项目,父模块定义了<dependencyManagement>节点,统一管理了所有子模块的依赖,子模块中无需再定义依赖的版本号。
  • 该项目有API模块,基于Spring Boot编写,需要利用Spring Boot Maven Plugin生成可执行的FatJar。
  • 项目使用了lombok,需要基于lombok的注解生成器生成最终代码。
  • 项目使用了QueryDSL,需要使用QueryDSL的插件,将数据库Entity类编译为QClass,并在源码中引用。
  • 该项目需要发布到Nexus私服

该项目应该涵盖了大部分Maven项目需要用到的编译配置,下面,我就通过这个项目的实战经验。带大家一步步将项目从Maven构建,迁移到全新的Gradle构建。

基于以上的概况,要将该项目从使用Maven构建,迁移到Gradle构建,就需要在Gradle中实现以上同等的逻辑。那么,就开始干吧~

基于POM,自动生成Gradle配置

Gradle官方非常贴心的内置了插件,可以一键将Maven POM转换为Gradle配置。只需要在项目目录内执行:

gradle init

然后,按照提示,将该Maven项目转换为一个Gradle项目即可。Gradle会自动为我们生成根项目的build.gradle、settings.gradle和gradle wrapper文件夹,当然,还有每个子项目的build.gradle文件(太强大了,笔芯Gradle♥️)

然鹅,光是这样当然是不够的,自动生成的Gradle配置是无法通过编译的...我们还需要继续按照原有的编译配置继续调整Gradle配置文件。

另外,听说Gradle的Kotlin DSL对IDE更友好,而且是类型安全的。因此,我决定把自动生成的Groovy DSL改写为Kotlin DSL(自动转换工具目前没有提供生成Kotlin DSL的选项,遗憾)

DSL的转换不是必须的,不过这里也简单说下,过程不是很复杂,基本就是把单引号'换成",另外把省略括号的地方补上括号即可。其他写法差异比较大的,基本Google就可以解决,这一步难度不大。

引入Spring Boot Bom依赖版本管理

众所周知,基于Spring Boot的项目,一定离不开Spring Boot Bom依赖管理。这个spring boot的bom文件内定义了很多依赖的版本,例如spring-boot-jpaspring-boot-web等依赖的版本。这样,我们在开发时,就可以减少依赖冲突,也不用自己再额外定义版本号了。

在使用Maven构建时,引入Spring Boot Bom有两种做法,第一种就是官方教程中常用的,将它作为模块的Parent:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.6.RELEASE</version>
</parent>

但是,这种做法必须将该bom作为模块的parent,有一定局限性。因此,我一般是将他作为依赖管理引入:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>2.2.6.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

同时,因为项目也使用了Spring Cloud组件,我也在<dependencyManagement>中引入了spring-cloud-dependenciesbom文件。

下面,就是要解决如何在Gradle中应用上面这套,我们在Maven-Spring Boot项目中常用的逻辑了。

查阅资料(踩了很多坑)后,我发现网上最多的方案,是使用Spring Boot官方编写的io.spring.dependency-management插件,样例代码如下:

apply(plugin = "io.spring.dependency-management")

the<DependencyManagementExtension>().apply {
    imports {
        mavenBom(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES)
    }
}

但是,注意,我在踩了很多坑之后,我并不推荐这个插件。更简单直接的做法是,使用Gradle官方的解决方案:

dependencies {
    implementation(platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES))
}

具体的文档,也可以参考Spring Boot提供的文档:Managing Dependencies with Gradle’s Bom Support

总之,我尝试下来,我更加推荐使用Gradle官方提供的BOM解决方案。在使用io.spring.dependency-management插件的过程中,我遇到父模块引入了bom,子模块中没生效、无法修改bom中定义的Groovy版本等问题。当然,也可能是我的使用姿势不对,知道的小伙伴可以提点下我。

另外,如果单纯的使用platform也会有子模块版本仍然没有被管理的问题,因此,我最后使用的是enforcedPlatform来引入bom。代码如下:

dependencies {
        implementation("io.vavr:vavr:0.9.0")
        implementation(enforcedPlatform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES))
        implementation(enforcedPlatform("org.springframework.cloud:spring-cloud-dependencies:${Version.SpringCloud}"))
        compileOnly("org.projectlombok:lombok:${Version.lombok}")
        annotationProcessor("org.projectlombok:lombok:${Version.lombok}")
    }

以上,就没有问题了,所有子项目中也可以随便引入Spring Boot相关的依赖,并且不需要再写版本号。

如果需要自定义bom中的kotlin、groovy等版本,Spring Boot写的文档中也有提供解决方案:

configurations.all {
        resolutionStrategy {
            eachDependency {
                if (requested.group == "org.jetbrains.kotlin") useVersion("1.3.72")
                if (requested.group == "org.codehaus.groovy") useVersion("2.5.11")
            }
        }
    }

迁移其他位于Maven依赖管理中的依赖版本定义

除了Spring Bom之外,原项目也在根模块的Maven POM中定义了很多依赖的版本:

<dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>${version.lombok}</version>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>commons-beanutils</groupId>
                <artifactId>commons-beanutils</artifactId>
                <version>${version.apache.common.beanutils}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

使用Gradle官方提供的解决方案替代即可:

configurations.all {
            force(
                    "org.projectlombok:lombok:${version.lombok}",
                    "commons-beanutils:commons-beanutils:${version.beanutils}"
            )
        }
    }

使用Nexus私服下载依赖

父模块中定义仓库配置:

// 私服和仓库配置
    repositories {
        // 优先查找本地仓库
        mavenLocal()
        // 私服配置
        maven {
            url = uri("http://192.168.20.99:8099/repository/maven-public/")
            // 账户密码
            credentials {
                username = "admin"
                password = "admin123"
            }
        }
        // 备选仓库
        maven {
            url = uri("https://repo.maven.apache.org/maven2")
        }
    }

发布模块到Nexus私服

父模块中引入maven-publish插件,并定义发布配置:

allprojects {
    // 引入发布插件
    apply(plugin = "maven-publish")

// 定义不需要执行发布的项目名称
val ignorePublishingProjects = setOf("xxx-api")
    if (!ignorePublishingProjects.contains(project.name)){
        publishing {
            repositories {
                maven {
                    val releasesRepoUrl = "http://192.168.20.99:8099/repository/maven-releases/"
                    val snapshotsRepoUrl = "http://192.168.20.99:8099/repository/maven-snapshots/"
                    // 根据版本号不同,发布到不同的Nexus仓库
                    url = uri(if (version.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl)
                    credentials {
                        username = "admin"
                        password = "admin123"
                    }
                }
            }
            // 定义发布的模块信息
            publications {
                create<MavenPublication>("maven") {
                    groupId = group.toString()
                    artifactId = project.name
                    version = project.version.toString()

                    from(components["java"])
                }
            }
        }
    }
}

将应用打包为可执行的Spring Boot FatJar

和Maven中一样,在这我们也需要借助Spring Boot为我们提供的Gradle插件:org.springframework.boot

我们可以在父模块的plugins配置下,定义好依赖的版本,这样在子模块中就可以直接使用插件,而不需要指定版本:

plugins {
    java
    `maven-publish`
    // 插件版本定义
    kotlin("plugin.jpa") version "1.3.72" apply false
    kotlin("jvm") version "1.3.72" apply false
    kotlin("plugin.spring") version "1.3.72" apply false
    id("org.springframework.boot") version "2.0.5.RELEASE" apply false
}

在需要打包执行的Spring Boot应用所在模块的build.gradle.kts配置中,引入插件:

plugins {
    java
    `groovy-base`
    kotlin("plugin.jpa")
    id("org.springframework.boot") // 引入Spring Boot插件
    kotlin("jvm")
    kotlin("plugin.spring")
}

就这样,就ok啦。只需要这样做,Spring Boot 插件就会自动帮我们把源文件打包为可执行的jar包。

如果想修改默认的jar包名字,例如把应用都打包为app.jar,只需像这样配置:

tasks.withType<org.springframework.boot.gradle.tasks.bundling.BootJar>{
    archiveFileName.set("app.jar")
}

混合编译Kotlin和Java代码

要让Gradle编译代码中的Kotlin代码,只须在相应模块中引入kotlin(jvm)插件:

plugins {
    java // 编译Java代码
    id("org.springframework.boot") // Spring Boot插件
    kotlin("jvm") // 编译JVM代码
}

当然,为了Kotlin和Spring、JPA框架能更好的一起工作,我们一般还会引入其他插件:

plugins {
    java
    kotlin("plugin.jpa")
    id("org.springframework.boot")
    kotlin("jvm")
    kotlin("plugin.spring")
}

如果要配置Kotlin插件的其他参数,可以通过下面的方式配置:

val compileKotlin: KotlinCompile by tasks
compileKotlin.kotlinOptions {
    freeCompilerArgs = listOf("-Xjsr305=strict", "-Xjvm-default=enable")
    jvmTarget = "1.8"
}
val compileTestKotlin: KotlinCompile by tasks
compileTestKotlin.kotlinOptions {
    jvmTarget = "1.8"
}

Kotlin插件默认也会帮我们编译源码目录下的Java代码(大善人啊~),不过,Kotlin插件默认的源码目录配置在src/main/kotlin下,而我目前的kotlin和java代码都位于src/main/java目录下,因此,我需要让Kotlin插件也将src/main/java视为源码目录:

sourceSets.main {
    java.srcDirs("src/main/java", "src/main/kotlin")
}

Spock单元测试

按照Spock官方文档介绍的那样,引入对应的插件和依赖即可:

引入Groovy插件:

plugins {
    java
    `groovy-base` // 编译Groovy
}

引入Groovy依赖和Spock依赖:

dependencies {
    implementation(project(":base-project"))
    testImplementation("org.springframework.boot:spring-boot-starter-test")
    testImplementation("org.spockframework:spock-core:1.2-groovy-2.4")
    testImplementation("org.spockframework:spock-spring:1.2-groovy-2.4")
    testImplementation("org.codehaus.groovy:groovy-all:2.5.11")
    testImplementation("com.h2database:h2:1.4.197")
}

大功告成,顺利的话,我们运行gradle test,就能看到Spock Test单元测试正常编译并运行了。

编译Lombok

要编译使用lombok的代码,只须在依赖中,引入lombok依赖和注解处理器:

dependencies {
        compileOnly("org.projectlombok:lombok:${Version.lombok}")
        annotationProcessor("org.projectlombok:lombok:${Version.lombok}")
    }

使用QueryDSL

抱歉,我没解决使用Gradle编译QueryDSL的问题。

所以,我的解决方案,就是把项目里使用QueryDSL的地方都替换成了JPA Specification...

开启并行编译和缓存

Gradle相比Maven的改进还有并行编译和缓存,他们都可以大大提升编译速度。要开启他们,只需在项目的根目录,创建gradle.properties文件,内容为:

# 启用缓存
org.gradle.caching=true
# 启用并行编译
org.gradle.parallel=true

构建时间对比

迁移完成,项目也可以正常Build了。那就来对比下同一个项目使用Maven和Gradle编译的时间对比吧:

  • Maven使用命令:
mvn clean package

执行结果为:

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 01:34 min
[INFO] Finished at: 2020-08-15T23:26:17+08:00
[INFO] ------------------------------------------------------------------------
  • Gradle使用命令:
gradle clean build

执行结果为:

BUILD SUCCESSFUL in 1m 46s

啥玩意儿?竟然比Maven还慢...

不过后几次有缓存的话,就快得多,字需要在7~8s的样子。

Gradle的机制还是不太熟悉,具体还得等之后再熟练掌握Gradle了,我会再进行更科学的测试。

总结

这次折腾花了我大概两天时间,终于把项目迁移到了Gradle。在这里分享经验,也是希望帮助到和我一样想要踩坑的同学...目前来看,使用Gradle似乎没有带来比Maven更多的优势,这其中有我对Gradle不太熟悉,还有项目不算太大的缘故。目前,Spring Boot官方也把构建切换到了Gradle,而且据他们的说法,速度提升了3~4倍。期待以后随着项目变大,能更多从Gradle构建中受益。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值