0%

Gradle多模块Spring Boot项目问题总结

模块化是设计软件的一个基本手段,将软件模块化使得模块可以被复用,可以独立维护。Spring boot应用通常使用Spring initializr创建,它是不支持多模块创建的。多模块构建的能力是构建工具所提供,也就是Maven或Gradle。一些高级的IDE,如JIDE,可以支持多模块创建,就是帮你修改好Maven或Gradle的脚本。你可能使用的vscode这类编辑工具,但是也想体验一下多模块,那么就只能手工修改构建脚本。

Gradle是基于Groovy语言构建出来的一个特定领域语言(DSL),用来进行项目的构建。我觉得它和C++领域的CMake比较像,但是CMake是完全特化的脚本语言,而Gradle是基于JVM上的脚本语言Groovy构建,其编程和扩展能力远远超过CMake,Gradle也支持C++语言的构建。一个Gradle脚本是后缀为gradle的文件,其中通常为DSL语句,具有特定的格式,这能降低脚本的编写难度,同时你也完全可以参杂进Groovy语句,实现更复杂的功能。同时借助插件,还可以很容易扩展这个DSL,实际上Gradle大部分功能都是通过插件实现的。

从网络上可以搜索到很多关于如何构建Gradle多模块项目的资料,但是它们都有共同的问题,所使用的Gradle版本较低,按照其方法都有一些问题。本文基于目前最新的版本,总结一下创建多模块Gradle项目的问题。

版本

  • Spring boot 2.2.5

  • Spring boot 2.2.5使用的Gradle版本是6.0.1

官方文档

最权威的关于如何创建多模块项目的文档莫过于官方文档,地址如下:

Spring官方:https://spring.io/guides/gs/multi-module/

Gradle官方:https://guides.gradle.org/creating-multi-project-builds/

所以当你遇到问题的时候,多去找原文、找源头。

Gradle插件引入方式变化

参照网络上的文章(例如此文VS Code开发Spring Boot + Cloud示例(四)Spring Boot + Gradle多项目框架)出现问题的原因,是你使用了新版本的Gradle,其DSL语法有变化,主要的变化是插件引入的方式变了。

以前的方式:

1
apply plugin: 'java'

新的方式:

1
2
3
plubins {
id 'java'
}

具体从哪个版本默认采用新的方式,我没有去考证,两者的区别见这里。plugins存在一些限制,不能放在subprojects或allprojects里面,且必须放在一开始。问题就出在这里,如果你按文章里面的将根项目build.gradle的内容全部移到subprojects里面,就会报如下错误:

Could not find method plugins() for arguments …

如果想在根项目引入插件,在子模块复用,可以这样做,注意核心插件不能使用version和apply:

1
2
3
4
5
6
7
8
9
10
11
12
13
plugins {
id 'org.springframework.boot' version '2.2.5.RELEASE' apply false
id 'io.spring.dependency-management' version '1.0.9.RELEASE' apply false
id 'java'
}

subprojects {

apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
apply plugin: 'java'

}

常用插件

开发Spring boot应用常用如下插件:

1
2
3
4
5
6
plugins {
id 'org.springframework.boot' version '2.2.5.RELEASE'
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
id 'java'
id 'java-library'
}

插件文档:

社区插件可以在门户https://plugins.gradle.org上查询(不过没有统一的插件使用说明文档,不太方便),核心插件随Gradle发行,使用说明包含在Gradle文档中。

dependency management插件

这个插件提供了类似maven的依赖管理功能,通过如下block定义,其中定义的依赖,在Gradle dependencies中使用可以省略版本号。

1
2
3
dependencyManagement {

}

常用的方式是通过maven bom导入依赖:

1
2
3
4
5
6
7
8
9
dependencyManagement {
imports {
mavenBom 'io.spring.platform:platform-bom:1.0.1.RELEASE'
}
}

dependencies {
implementation 'org.springframework.integration:spring-integration-core'
}

Spring boot插件

这个插件为Spring boot应用开发提供了一些便利。例如和dependency-management插件一起使用可以自动导入Spring boot的maven bom。

1
2
3
4
5
6
7
8
9
plugins {
id 'org.springframework.boot' version '2.2.5.RELEASE'
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
id 'java'
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
}

等价于:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
plugins {
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
id 'java'
}

dependencyManagement {
imports {
mavenBom("org.springframework.boot:spring-boot-dependencies:2.2.5.RELEASE")
}
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
}

还提供了bootRun任务启动应用:

1
gradle bootRun

java插件的依赖配置变化

有一个不一样的地方可能会困扰你,网上的很多文章里面会出现:

1
2
3
dependencies {
compile('org.springframework.boot:spring-boot-starter-web')
}

但是最新的是这样的:

1
2
3
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
}

这是因为在最新的java插件中依赖配置有变化,有一些deprecated了,如下灰色字体,例如compile,详见

java-library提供如下依赖配置:

spring-boot-devtools不生效

spring-boot-devtools主要提供Automatic restart和LiveReload功能,极大的提升了开发调试的效率,在Spring Initializr中创建项目时选择DevTools,会生成如下构建脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14

configurations {
developmentOnly
runtimeClasspath {
extendsFrom developmentOnly
}
compileOnly {
extendsFrom annotationProcessor
}
}

dependencies {
developmentOnly 'org.springframework.boot:spring-boot-devtools'
}

在IDE中通过调式运行应用时,devtools功能一切正常,但是如果你通过如下命令启动应用,会发现自动重启并没有生效。

1
gradle bootRun

虽然bootRun会自动识别devtools,但是还是需要先编译,因为devtools默认监控的classpath下文件改变,另外重新编译后才能看到修改的效果。为了自动编译,可以通过gradle的连续构建实现,再执行如下命令:

1
gradle build --continuous

保持上述两个命令都开着,这时修改文件就会先触发build,然后再触发bootRun重启。

参考:

Continuous Auto-restart With Spring Boot DevTools and Gradle

SpringBoot配置devtools实现热部署

理解依赖配置

启用devtools工具后,依赖配置configurations中会出现自定义配置developmentOnly,这是什么目的呢?

configurations里面的内容,每一项称为一个dependency configuration,其实是对dependency的一个分组,在dependencies往这个分组里面加入dependency,gradle对每个configuration解析出一个依赖树,供task使用。

每一个dependency configuration代表一定角色,决定了:

  • 构建生命周期的哪些步骤会使用分组中的依赖
  • 分组中的依赖会传递吗,是传递到消费模块的编译期还是运行期,还是都有
  • 分组中的依赖会打包进构建输出吗

前文的java及java library插件预定义了一些dependency configuration,如implementation、compileOnly等,它们的角色由插件预定义好。

implementation compileOnly runtimeOnly api
构建阶段 compile time
runtime
test compile time
test runtime
compile time runtime compile time
runtime
test compile time
test runtime
传递到消费模块编译期 no no no yes
传递到消费模块运行期 yes no yes yes
打包到输出 yes no yes yes

对于devtools,它的实现原理是启用特殊的类加载器,仅用在开发调式阶段,所以它不会应用到构建阶段,也不需要传递到消费模块,也不需要打包到输出,所以这里就自定义了一个依赖配置developmentOnly,目的就是不参与构建阶段、不传递、不打包,这个依赖配置也可以用别的名字。

那么devtools又是怎么生效的呢:

  • 在IDE中进行调试运行时,检测到devtools时就会使用其中的"restart"类加载器加载打开的工程,其他jar使用"base"类加载器加载;
  • 使用gradle bootRun运行时,检测到devtools时同上

参考:

Faster Development with Spring Boot DevTools

Spring boot devtools tutorial

Maven Scopes and Gradle Configurations Explained

-------------本文结束感谢您的阅读-------------