模块化是设计软件的一个基本手段,将软件模块化使得模块可以被复用,可以独立维护。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 | plubins { |
具体从哪个版本默认采用新的方式,我没有去考证,两者的区别见这里。plugins存在一些限制,不能放在subprojects或allprojects里面,且必须放在一开始。问题就出在这里,如果你按文章里面的将根项目build.gradle的内容全部移到subprojects里面,就会报如下错误:
Could not find method plugins() for arguments …
如果想在根项目引入插件,在子模块复用,可以这样做,注意核心插件不能使用version和apply:
1 | plugins { |
常用插件
开发Spring boot应用常用如下插件:
1 | plugins { |
插件文档:
-
dependency management插件(社区插件): org.springframework.boot
-
Spring boot插件(社区插件): io.spring.dependency-management
-
java语言插件(核心插件):java
-
java库开发(核心插件):java-library
社区插件可以在门户https://plugins.gradle.org上查询(不过没有统一的插件使用说明文档,不太方便),核心插件随Gradle发行,使用说明包含在Gradle文档中。
dependency management插件
这个插件提供了类似maven的依赖管理功能,通过如下block定义,其中定义的依赖,在Gradle dependencies中使用可以省略版本号。
1 | dependencyManagement { |
常用的方式是通过maven bom导入依赖:
1 | dependencyManagement { |
Spring boot插件
这个插件为Spring boot应用开发提供了一些便利。例如和dependency-management插件一起使用可以自动导入Spring boot的maven bom。
1 | plugins { |
等价于:
1 | plugins { |
还提供了bootRun任务启动应用:
1 | gradle bootRun |
java插件的依赖配置变化
有一个不一样的地方可能会困扰你,网上的很多文章里面会出现:
1 | dependencies { |
但是最新的是这样的:
1 | dependencies { |
这是因为在最新的java插件中依赖配置有变化,有一些deprecated了,如下灰色字体,例如compile,详见。
java-library提供如下依赖配置:
spring-boot-devtools不生效
spring-boot-devtools主要提供Automatic restart和LiveReload功能,极大的提升了开发调试的效率,在Spring Initializr中创建项目时选择DevTools,会生成如下构建脚本:
1 |
|
在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
理解依赖配置
启用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时同上
参考: