在开发 Spring Boot Web 项目时,多 Module 设计可以将功能拆分、解耦,方便迭代和代码复用。通常将一个 Module 作为运行的应用主程序,其他 Module 作为 Library 被主程序引用。如果 Library Module 只是纯 Java API,没有用到 Spring 特性,那么可以像其他 Java 项目一样引用 Library ,无需特殊处理。本文介绍,Library 使用 Spring 特性时,Spring Boot 的多 Module 引用方式。
本文示例开发环境:
Spring Boot: 2.7.3
语言:Kotlin
JDK:jbr-11
数据库: MySQL8.0.27
构建:Gradle 7.5
示例会创建一个 Root Project:MultiModule,包含两个 Module:Application、Library。其中,Application 引用 Library。
目录结构如下:
- MultiModule
- Application
- Library
使用 Spring Initializr 创建根项目 MultiModule,创建和选择过程请参考:直接上手SpringBoot创建Web项目
在项目根目录下添加两个文件夹:Application、Library,作为两个 Module 的根目录。
在 settings.gradle.kts 中添加这两个 Module,建立根项目、Application和Library之间的亲子关系。
rootProject.name = "MultiModule"
include("Library")
include("Application")
先将 build.gradle.kts 文件复制两份分别放在 Application 和 Libaray 文件中,后续我们会进行修改。由于根项目是空的,没有代码,仅用于 Gradle 构建。所以需要清空 build.gradle.kts 中的内容,删除 src 文件夹。
刷新 Gradle 之后,我们会看到 Application、Library 两个文件夹已经变成了 引用 Module 的样式,表示相互关系已经建立。
Library Module 将为 Application 提供服务,完成一部分 Spring 功能。在 Library 文件夹上右键选择 New -> Directory,在弹窗中可以选择 Spring Boot 预设的文件夹:src\main\kotlin。
build.gradle.kts 文件内容与 Spring Initializr 初始创建时基本一致,只是,作为 library,该模块不需要生成 BootJar。在 spring boot plugin 后面加上 apply(false),可以取消 task。
id("org.springframework.boot") version "2.7.3" apply(false)
Application Module 将作为主程序,引用 Library 完成主要业务逻辑。和 Library 一样操作,可以方便地新建项目目录。
build.gradle.kts 文件内容与 Spring Initializr 初始创建时基本一致,仅需要在dependencies 中引用 Library:
implementation(project(":Library"))
以上工作完成后将得到这样的目录结构:
- MultiModule
- Application
- src
- main
- kotlin
- build.gradle.kts
- Library
- src
- main
- kotlin
- build.gradle.kts
- build.gradle.kts
- settings.gradle.kts
为了测试模块功能引用的完整性,我们在 Library 中使用几个常用的 Spring 特性,看是否能在 Application 中引用到。
在测试 Library 引用之前,需要在 Application 中添加启动程序:Application.kt,包名 com.example.application。
@SpringBootApplication
class Application
fun main(args: Array<String>) {
runApplication<Application>(*args)
}
常用组件包括 @Service @Controller @Configuration @Component 等。添加这些注解后,为了能让 Application 识别,需要指定 @SpringBootApplication 的 scanBasePackages 参数。下文将结合其他功能一并测试。
Library Moudle 的包名:com.example.library
@ConfigurationProperties("mine")
class LibraryProperties {
var name: String = "default"
}
@Service
@EnableConfigurationProperties(LibraryProperties::class)
class LibraryService {
@Autowired
private lateinit var properties: LibraryProperties
fun name() = properties.name
}
其中 EnableConfigurationProperties 用于启用自定义属性。
@RestController
class AppController {
@Autowired
private lateinit var service: LibraryService
@GetMapping("/name")
fun getName(): String {
return service.name()
}
}
@SpringBootApplication(
scanBasePackages = [
"com.example.library",
"com.example.application"
]
)
#library
mine.name = Peter
启动 Application 项目后,在浏览器输入:
localhost:8080/name
得到自定义属性值 name:
Peter
说明自定义属性配置成功,Application 可以引用到 Library Service组件,自定义属性也可解析到 LibraryProperties 。
值得注意的是,application.properties 一般在 Application 中指定,用来控制 Library 的行为。如果 Library 中也指定相同属性,可能会引起冲突。
参考 SpringBoot自定义配置属性 方法进行配置并不能在 Application 中识别输入的参数名称,这可能是 kapt 只扫描了 Application 包下的属性。暂时不知道如何配置,只能手动输入参数了。如果正在阅读本文的你有方法实现,希望可以留言分享。
我们来看看如何配置才能让 Application 识别 Lirary 中定义的 Entity 和 Repository。
@Entity
class Person(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long = 0L,
var name: String = ""
){
override fun toString(): String {
return "(Person: id=$id, name=$name)"
}
}
interface Persons: JpaRepository<Person, Long>
@EntityScan(
basePackages = [
"com.example.library",
"com.example.application"
]
)
@EnableJpaRepositories(
basePackages = [
"com.example.library",
"com.example.application"
]
)
# datasource
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/mm?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT
spring.datasource.username=root
spring.datasource.password=test
#jpa
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true
spring.jpa.open-in-view=false
@Autowired
private lateinit var persons: Persons
fun persons(): List<Person> = persons.findAll()
@GetMapping("/persons")
fun getPersons(): List<Person> {
return service.persons()
}
新建数据库 mm 后启动 Application,在数据库 person 表中插入一条数据:
mysql> INSERT INTO person
-> (id,name)
-> VALUES
-> (1, "Lili");
在浏览器输入:
localhost:8080/persons
得到插入的记录:
[{ "id": 1,"name": "Lili" }]
需要注意的是,虽然在 @SpringBootApplication 中我们已经指定了 scanBasePackages 参数,但是仍需指定 @EntityScan @EnableJpaRepositories 的扫描路径。
本文用示例详细展示了多 Module 的创建和引用过程,其中关键方法在于在启动类上指定 @SpringBootApplication、@EntityScan、@EnableJpaRepositories 的扫描路径包含 Library 所在包。
本文源码:https://gitee.com/yoshii_x/multi-module.git
源码中包含了部分其他注解的测试,这里没有详述。
Spring Boot Guide: https://spring.io/guides/gs/multi-module/