文章目录
1.概述
在上一篇涉及初始化 Gradle 项目的相关内容里,我们充分借助 IDEA 成功构建出了一个处于最基础层面的 Java 项目。不过,需要明确的是,这样的成果距离能够满足日常开发工作的实际需求,还存在着相当大的差距。在当下的这一篇当中,我们将会基于此前构建的基础,通过创建“SpringBoot”的服务实例,全方位地展示应当如何针对 Gradle 的多模块项目予以合理有效的组织,并且在整个这一进程之中,清晰地阐释一些全新的概念。
2.多模块项目构建
在gradle-demo
项目中创建三个子模块common
,demo-api
,demo-service
,其作用分别为:
- common:通过模块,放一些所有模块都要用到的常量、工具类等
- demo-api:Dubbo服务对外提供的api包,供其他依赖引入。
- demo-service:主服务,承载SpringBoot的MVC功能和Dubbo的实现,提供Http接口和RPC接口
作为演示项目,只拆分了3个模块,实际工作中可以根据需要继续拆分。
2.1.子模块创建
子模块创建方式非常简单,只需要下面的两个步骤。
按这个方法创建完三个子模块,项目结构如下图所示:
在settings.gradle
中已经自动加入了子模块的引用,如果没有加入的话,也可以按下面的语法 inclue + 模块名
的方式手动引入。
rootProject.name = 'gradle-demo'
include 'common'
include 'demo-api'
include 'demo-service'
2.2. SpringBoot引入
2.2.1.配置修改及启动
首先,需要在gradle.properties
中定义需要使用到的插件和jar包的版本号,方便统一管理。
# 项目版本配置
group=com.ls
version=1.0.0
description=gradledemo
# Spring version
springBootVersion=2.7.18
springDenpendencyVersion=1.0.15.RELEASE
# Lombok version
lombokVersion=1.18.20
group和version是可以直接配置在这个资源文件中的,配置后能直接对所有模块生效。
其次在最外层(父模块)的build.gradle
中,修改配置内容。下面是一个完整的配置文件内容,在后面的概念解释中会讲解每一步的含义。
plugins {
id 'java'
id 'org.springframework.boot' version "$springBootVersion" apply false
id 'io.spring.dependency-management' version "$springDenpendencyVersion" apply false
}
allprojects {
repositories {
maven {
url 'https://maven.aliyun.com/repository/public'
}
mavenCentral()
}
}
subprojects {
apply plugin: 'java'
sourceCompatibility = JavaVersion.VERSION_1_8
dependencies {
compileOnly "org.projectlombok:lombok:$lombokVersion"
annotationProcessor "org.projectlombok:lombok:$lombokVersion"
}
}
最后,先清空子模块的build.gralde
文件中的内容,再找到demo-service
里面加入SpringBootWeb依赖。
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
打印出了Tomcat端口,表示启动成功。
2.2.2.概念解释
解释一下上面配置中的各类名词及步骤的含义。
plugins
插件用于扩展 Gradle 的功能和行为,可以自动应用特定的构建逻辑、任务和配置。上一篇中已经提到了Java插件的作用,就是用于编译代码,生成jar包,执行单元测试等。
上面的其中SpringBoot插件org.springframework.boot
也类似于Java插件的效果,就是用来配置SpringBoot服务的打包流程,运行SpringBoot服务,生成可以执行jar包等。类似于Maven中的spring-boot-maven-plugin
。
另一个插件io.spring.dependency-management
则主要是给SpringBoot相关的jar包提供依赖,仔细看的话,在上面的demo-service
引入的依赖中并没有加入版本号。类似于Maven中的dependencyManagement
功能。
总的来说,引入这个插件非常类似于在Maven中的这个操作:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
<relativePath/>
</parent>
至于插件后的 apply false
,表示在项目中不直接生效而是在子项目中通过apply plugin:xxx
选择性的应用插件,这样使用更加灵活,既保证了统一定义插件版本,又保证了按需引入。
allprojects和subprojects
通过名字不难看出来前者是对所有项目生效,后者是对子项目生效。
- allprojects:一般用来做一些全局配置,例如仓库配置。
- subprojects:一般用于配置所有子项目都会使用到的通用依赖。
apply plugin
上面简单的说了一个apply plugin 的作用,就是将引入的插件在特定的模块中应用,仔细看父模块的配置:
在plugins中的Java插件并没有使用apply false
,在subjects中依然需要使用apply plugin
,这其实涉及到一个作用域的问题。
plugins中的Java插件是在跟项目中引入的,只会对根项目起作用,如果想对子模块也起作用,就需要在subprojects或者每个模块的build.gradle中通过apply plugin
应用。
dependencies
这一块可以说是gradle中最常用的功能,其作用就是引入各种各样的jar包,语法规则有下面两种:
依赖配置 "groupId:artifactId:version"
依赖配置("groupId:artifactId:version")
所谓的依赖配置(transitive dependencies),是指一个项目的依赖项及其子依赖项都被引入项目的构建路径。用来确定依赖范围,依赖传递等
本篇会涉及到的几种依赖配置:
- compileOnly:声明的依赖只在编译时可见,不包括在运行时。
- annotationProcessor:用于在编译时处理注解的依赖。
- implementation:声明的依赖在编译和运行时可见。
- testImplementation:声明的依赖在测试编译和运行时可见。
以上面的配置为例,lombok
的作用是简化代码,例如在源码中通过@Getter
@Setter
注解来替代get()
set()
方法,在编译的时候再生成对应的代码。也就是说,lombok只需要在编译的时候才需要用到,这里使用compileOnly
这个依赖配置就非常合适。
同时,这里涉及到在gradle build 的过程中解析注解,还需要通过annotationProcessor
来声明lombok需要被解析的注解,这样在编译过程中才会将@Getter
处理为get()
方法,否则将会被gradle忽略。
implementation
是最常用的依赖配置,它还有一种特殊的用法,即:
implementation project(':moduleXXX')
这种用法是用在多模块的项目中,让一个模块A直接依赖另一个模块B的源码,B模块源码和依赖项可以直接参与到当前模块的编译和运行过程。
这个功能是Maven中没有的一个功能,它的出现大大降低了复杂的多模块项目管理复杂度,让模块依赖关系更加清晰。
2.2.api模块
这是一个 Dubbo 的 API 模块,有两个作用:
- 在当前项目中引入 API 里的接口,并予以实现。
- 对外提供一个 Jar 包,提供可供调用的接口协议。
2.2.1.api模块的project引入
common
模块和api
功能定位不一样,但对于编码来说比较类似,下面就以api模块为例体验一下implementation project
。
在上面说到,这里的API模块主要是用于对外提供Dubbo请求需要的接口协议和对象,同时我们在demo-service
模块中,也需要实现对应的接口。
在api中随便定义一个接口
切换到demo-service,依赖api模块,在build.gradle的的dependcies中加入:
implementation project(":demo-api")
这种引入方式并不需要严格的指定坐标,只需要模块的名称即可,在下面的截图中可以看到,依赖已经引用成功了。
此时,我们在api中再随便加个方法,看看会出现什么结果:
可以看到的是,在IDAE中直接提示了接口有未实现的方法,就像是直接在当前模块编辑源码一样。相对于在Maven中,需要重新设置api的版本号,再修改service中依赖的api版本号而言,要方便很多。
PS:Maven中也可以通过SNAPSHOT模拟出类似的能力,但毕竟SNAPSHOT不是正式的jar包,不适合在生产中使用。
2.2.2.将API打包并推送到Maven私服
在 Gradle 中,其本身并不具备直接将 Jar 包推送至 Maven 私服的功能。然而,Gradle 为我们提供了相关的插件,通过运用这些插件,能够拓展出将 Jar 包推送至 Maven 私服的能力。当前,要实现此操作,我们总共需要完成四个步骤:
- 首先,要应用 Maven 插件
maven-publish
。 - 其次,对发布配置进行修改,清晰地定义发布的包的类型以及私服的具体地址。
- 接着,明确需要推送模块的坐标,这里所说的坐标指的是
groupId
、artifactId
、version
。 - 最后,执行推送的操作。
应用插件
在demo-api
的build.gradle文件中应用Maven发布插件,写下下面的脚步时候,IDEA右侧的TASK也会相应的变化。
apply plugin: 'maven-publish'
这里多出了两个Task:
- publish:发布到Maven远程仓库中
- publishToMavenLocal:发布到Maven本地仓库
但现在这两个Task是没有作用的,因为Gradle还不知道需要将代码打成jar
包还是war
包,需要将构建完成的包发布到哪里。
发布配置修改
发布配置主要是解决 what 和 where 的问题,也是代码需要打包的类型和发布的位置,通过publishing
块来进行配置,先看demo-api
的build.gradle 配置,下面我再解释里面的含义:
apply plugin: 'maven-publish'
publishing {
publications {
demoApi(MavenPublication) {
from components.java
}
}
repositories {
maven {
name = "mavenNexus"
allowInsecureProtocol = true
def releasesRepoUrl = 'http://192.168.200.101:8081/repository/maven-releases/'
def snapshotsRepoUrl = 'http://192.168.200.101:8081/repository/maven-snapshots/'
url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
credentials {
username 'admin'
password 'admin123'
}
}
}
}
- publications:发布配置,即定义“what”,里面可以配置打包的类型,这里的
from components.java
就是把代码打成jar包,如果想要打成war包,则是from components.web
demoApi
:当前发布配置的命名,你可以写成任意你想要的名字,用驼峰式命名即可
- repositories:仓库配置,即定义“where”,里面配置仓库的地址,
http://192.168.200.101:8081
是我我本地启动的私服。name
:当前仓库在task中的名字allowInsecureProtocol
:作用是让Gradle忽略不安全的http地址,如果不配置这一项的话,需要保证私服的地址是https
协议。credentials
:私服的用户名和密码
配置完成之后,task就变成了这个样子:
红框中的两个task分别表示的是把构建好的jar包推到Maven本地仓库和远程仓库,需要注意的是,在task中有DemoApi
和MavenNexus
两个单词,这就是我在上面定义的name,如果定义成其他的名字,这里也会显示另外的名字。
定义坐标
在根项目的gradle.properties
中,我们已经做了全局定义:
group=com.ls
version=1.0.0
也就是说,现在demo-api
的坐标为:com.ls:demo-api:1.0.0
,需要修改发布的版本的话,可以直接修改这个properties文件,但是由于这个文件影响的是当前项目下的所有子模块,如果只想修改demo-api
的版本,其他默认的版本号保持不变话,可以在demo-api
的buil.gradle中做单独的定义,例如修改为2.0.0
。
执行发布
一般我们发布给其他项目使用的jar,都需要将源码上传,以便对接者可以直接通过源码查看代码的注释,修改过后的最终配置如下:
apply plugin: 'maven-publish'
version = "2.0.0"
java {
withSourcesJar()
}
publishing {
publications {
demoApi(MavenPublication) {
from components.java
}
}
repositories {
maven {
name = "mavenNexus"
allowInsecureProtocol = true
def releasesRepoUrl = 'http://192.168.200.101:8081/repository/maven-releases/'
def snapshotsRepoUrl = 'http://192.168.200.101:8081/repository/maven-snapshots/'
url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
credentials {
username 'admin'
password 'admin123'
}
}
}
}
运行右侧publishDemoApiPublicationToMavenNexusRepository
,可以看到执行了多个Task,最终推送到远程仓库中。
推送结果如下图所示:
2.3.构建可以运行的SpringBoot包
在上面的内容中,我们已经将demo-service
在IDEA中启动了,但在实际的生产工作中,只在IDEA中启动是不够的,还需要把项目构建成为一个可运行的Jar包,以供生产发布。
在demo-service
的build.gradle文件中,我们应用了根项目引入的SpringBoot插件:
apply plugin: 'org.springframework.boot'
这个插件可以给gradle的task添加打包的流程,叫做bootJar
,通过一个截图可以看到应用和没有应用插件的区别:
这里直接双击bootJar就可以完成打包,但是我们在实际生产中,可能是通过自动化脚本来进行发布的,可以通过下面的脚步完成打包:
gradlew clean bootjar -x test
- gradlew:在根项目中有两个可执行文件,
gradlew
和gradlew.bat
,分别对应shell脚本和批处理脚本,我们大多数情况是发布在Linux环境中的,所以这里选择shell脚本 - clean:先清空之前打的包
- bootjar:构建SpringBoot的可执行jar包
- -x test:打包时跳过测试阶段
需要注意的是,打包时需要从根项目进入到demo-service
子模块中,所以打包的脚步就变成了这样:
cd demo-service
../gradlew clean bootjar -x test
打包成功,接下来看看能否运行,文件在{子项目目录}/build/libs
中,从下图可以看到启动成功。
3.总结
本篇主要讲解了如何在 Gradle 中引入 Spring Boot,并创建一个多模块项目,同时讲解了发布的两种形式,即可执行 jar 包的生成与构件的生成和发布。
- 演示了如何创建多模块项目,同时对插件、依赖等概念做了简单解释。
- 体验了 Gradle 插件的作用,通过 Spring Boot 插件,完成了一次可运行 jar 包的构建生成并成功启动。
- 对项目中子模块之间的特殊依赖“implementation project”做了讲解,可用这种方式直接依赖源码,无需频繁在依赖中修改版本号。
- 讲述了如何通过 Maven 的发布插件将 jar 包推送到 Maven 私服中。
到目前为止,已经能够通过 Gradle 完成大部分研发工作了。在下一篇文章中将会讲到 Gradle 的依赖范围、依赖路径、依赖传递等特性。