精通Spring Boot 3 : 14. 扩展 Spring Boot 框架 (2)
构建 myretro-spring-boot-starter 自定义启动模块
接下来,让我们来编译自定义启动器。如果之前的步骤都顺利进行,那么您可以使用以下命令来构建自定义启动器:
./gradlew build
此命令会在 build/libs 文件夹中生成 myretro-spring-boot-starter-0.0.1.jar 文件,准备好使用!如果您希望跳过将 JAR 发布到 Maven 仓库的步骤,可以直接在您的 Users App 项目或 My Retro App 项目中以以下方式使用此 JAR(在您的 Users App 或 My Retro App 的 build.gradle 文件中):
//...
dependencies {
//...
implementation files('../myretro-spring-boot-starter/build/libs/myretro-spring-boot-starter-0.0.1.jar')
//...
}
本声明假设您在下一级目录中有 myretro-spring-boot-starter 项目。
但是自定义启动器的理念是每位开发者都能访问它,对吗?因此,我们需要将其发布到 Maven 仓库。
将自定义启动器作为 Maven 工件发布到 GitHub 上
为了发布我们的启动项目,我们可以将 GitHub 用作 Maven 工件库。好处在于,GitHub 不仅为 Maven 提供这些发布功能,还支持 Docker 镜像。
发布 Maven 工件需要我们完成三项工作:
- 1.
- 创建一个 GitHub 令牌,以便我们能够向我们的代码库进行写入和发布。请参考以下文档以创建您的个人令牌:https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens。
- 2.
- 在 GitHub 上创建一个新的空仓库,作为一个常规项目。在我的例子中,我在这里创建了一个新的仓库:https://github.com/felipeg48/myretro-spring-boot-starter。
- 3.
- 在 build.gradle 文件中添加必要的声明,以便发布工件。
接下来,让我们来修改一下 build.gradle 文件。请查看清单 14-20。
plugins {
id 'java'
id 'org.springframework.boot' version '3.2.2' apply false
id 'io.spring.dependency-management' version '1.1.4'
id 'maven-publish'
}
group = 'com.apress'
version = '0.0.1'
java {
sourceCompatibility = '17'
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
tasks.named('compileJava') {
inputs.files(tasks.named('processResources'))
}
repositories {
mavenCentral()
}
dependencyManagement {
imports {
mavenBom org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES
}
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-aop'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
runtimeOnly 'com.h2database:h2'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
tasks.named('test') {
useJUnitPlatform()
}
publishing {
publications {
mavenJava(MavenPublication) {
from components.java
artifactId = 'myretro-spring-boot-starter'
versionMapping {
usage('java-api') {
fromResolutionOf('runtimeClasspath')
}
usage('java-runtime') {
fromResolutionResult()
}
}
pom {
name = 'My Retro Starter'
description = 'A spring-boot-starter library example'
licenses {
license {
name = 'The Apache License, Version 2.0'
url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
}
}
developers {
developer {
id = 'felipeg48'
name = 'Felipe'
email = ''
}
}
scm {
connection = 'scm:git:git://github.com/felipeg48/myretro-spring-boot-starter.git'
developerConnection = 'scm:git:ssh://github.com/felipeg48/myretro-spring-boot-starter.git'
url = 'https://github.com/felipeg48/myretro-spring-boot-starter'
}
}
}
}
repositories {
maven {
name = "GitHubPackages"
url = uri("https://maven.pkg.github.com/felipeg48/myretro-spring-boot-starter")
credentials {
username = project.findProperty("GITHUB_USERNAME") ?: System.getenv("GITHUB_USERNAME")
password = project.findProperty("GITHUB_TOKEN") ?: System.getenv("GITHUB_TOKEN")
}
}
}
}
让我们逐部分回顾一下修改后的 build.gradle 文件:
- 在这里,我们添加了 maven-publish 插件,该插件负责将构件发布到 Maven 仓库。
- 本节帮助定义 Maven 仓库所需的元数据,并说明如何登录,特别是在仓库为私有或需要凭据及身份验证机制的情况下。
- 发布出版物。在这里,我们定义了工件在 Maven 仓库中注册所需的元数据。审查过程非常简单。
- 本节定义了存储库的位置。我们有一个特殊的 URL,https://maven.pkg.github.com/felipeg48/myretro-spring-boot-starter。这个 URL 仅用于声明,帮助查找软件包。它还定义了所需的凭据,在这种情况下,我们需要通过 GITHUB_USERNAME 和 GITHUB_TOKEN 变量来获取用户名和密码(这些变量可以在 $HOME/.gradle/gradle.properties 文件中设置,或者通过环境变量设置)。
现在我们已经准备好一切,让我们来发布我们的工件。请记得您需要创建一个空的仓库;在我的例子中,我在 GitHub 上创建了 myretro-spring-boot-starter(作为一个项目)。请执行以下命令以在您的仓库中发布工件(见图 14-1):
./gradlew publish
GitHub 上的图 14-1 包 (https://github.com/felipeg48/myretro-spring-boot-starter/packages)
正如您所见,我们的包已经成功部署。现在是时候开始使用它了。
使用 myretro-spring-boot-starter 自定义启动模块
让我们打开用户应用程序项目;您可以从 14-extending/users 目录将其导入到您喜欢的 IDE 中。或者,如果您想从 Spring Initializr(https://start.spring.io)开始,请将 Group 字段设置为 com.apress,将 Artifact 和 Name 字段都设置为 users。添加依赖项:Web、JPA、Processor、Validation、Actuator、Lombok 和 H2。生成并下载项目,解压缩后导入到您喜欢的 IDE 中。
接下来,打开 build.gradle 文件,参见清单 14-21。
plugins {
id 'java'
id 'org.springframework.boot' version '3.2.2'
id 'io.spring.dependency-management' version '1.1.4'
id 'org.hibernate.orm' version '6.4.1.Final'
}
group = 'com.apress'
version = '0.0.1-SNAPSHOT'
java {
sourceCompatibility = '17'
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
maven {
url 'https://maven.pkg.github.com/felipeg48/myretro-spring-boot-starter'
credentials {
username = project.findProperty("GITHUB_USERNAME") ?: System.getenv("GITHUB_USERNAME")
password = project.findProperty("GITHUB_TOKEN") ?: System.getenv("GITHUB_TOKEN")
}
}
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
// H2 runtime only
runtimeOnly 'com.h2database:h2'
// My Retro Starter
implementation 'com.apress:myretro-spring-boot-starter:0.0.1'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
// Web
implementation 'org.webjars:bootstrap:5.2.3'
// Test
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
tasks.named('test') {
useJUnitPlatform()
}
列表 14-21 的 build.gradle 文件
让我们一起查看 build.gradle 文件:
- 在这个语句中,我们使用了设置的 URL:https://maven.pkg.github.com/felipeg48/myretro-spring-boot-starter。在凭据部分,我们查看 GITHUB_USERNAME 和 GITHUB_TOKEN 变量(这些变量可以在 $HOME/.gradle/gradle.properties 文件中设置,或者通过环境变量设置)。如果您的仓库是公开的,您可以省略凭据部分。
- 这里我们使用的是刚刚发布的工件!请记住,如果您需要单独测试 JAR,可以使用以下命令(前提是您将项目放在同一文件夹中):
implementation files('../myretro-spring-boot-starter/build/libs/myretro-spring-boot-starter-0.0.1.jar')
如果您正在使用 14-extending/users 中的源代码,您可以继续进行,但如果您是从头开始创建,您可以通过删除事件包并修复 UserService 中使用/发布事件的部分,来使用 JPA 章节中的代码。这将会有所改变。
接下来,打开或创建 UserService 类。请参阅清单 14-22。
package com.apress.users.service;
import com.apress.myretro.annotations.MyRetroAudit;
import com.apress.myretro.annotations.MyRetroAuditOutputFormat;
import com.apress.users.actuator.LogEventEndpoint;
import com.apress.users.model.User;
import com.apress.users.repository.UserRepository;
import lombok.AllArgsConstructor;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import java.util.Optional;
@AllArgsConstructor
@Service
public class UserService {
private UserRepository userRepository;
private ApplicationEventPublisher publisher;
private LogEventEndpoint logEventsEndpoint;
public Iterable<User> getAllUsers() {
return this.userRepository.findAll();
}
public Optional<User> findUserByEmail(String email) {
return this.userRepository.findById(email);
}
@MyRetroAudit(showArgs = true, message = "Saving or updating user", format = MyRetroAuditOutputFormat.JSON, prettyPrint = false)
public User saveUpdateUser(User user) {
User userResult = this.user repository.save(user);
return userResult;
}
public void removeUserByEmail(String email) {
this.userRepository.deleteById(email);
}
}
文件路径:src/main/java/com/apress/users/UserService.java(列表 14-22)
列表 14-22 显示我们正在使用@MyRetroAudit 注解,并且在这里使用了一些参数。
现在,如果你尝试运行应用程序(使用 ./gradlew bootRun),什么也不会发生。没有日志,也没有数据库中的事件记录。我们缺少 @EnableMyRetroAudit 注解。因此,让我们添加它。请打开或创建 UserConfiguration 类。请参见清单 14-23。
package com.apress.users.config;
import com.apress.myretro.annotations.EnableMyRetroAudit;
import com.apress.users.model.User;
import com.apress.users.model.UserRole;
import com.apress.users.service.UserService;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
@EnableMyRetroAudit
@Configuration
@EnableConfigurationProperties({UserProperties.class})
public class UserConfiguration {
@Bean
CommandLineRunner init(UserService userService) {
return args -> {
userService.saveUpdateUser(new User("ximena@email.com", "Ximena", "https://www.gravatar.com/avatar/23bb62a7d0ca63c9a804908e57bf6bd4?d=wavatar", "aw2s0meR!", List.of(UserRole.USER), true));
userService.saveUpdateUser(new User("norma@email.com", "Norma", "https://www.gravatar.com/avatar/f07f7e553264c9710105edebe6c465e7?d=wavatar", "aw2s0meR!", List.of(UserRole.USER, UserRole.ADMIN), false));
};
}
}
用户配置文件的列表 14-23src/main/java/com/apress/users/config/UserConfiguration.java
列表 14-23 显示我们现在使用的是@EnableMyRetroAudit 注解(没有参数,意味着它将使用默认值)。
在我们运行之前,让我们在 application.yaml 文件中添加一些属性。请参见清单 14-24。
spring:
application:
name: users-service
h2:
console:
enabled: true
jpa:
generate-ddl: true
show-sql: true
hibernate:
ddl-auto: update
datasource:
url: jdbc:h2:mem:users_db
info:
developer:
name: Felipe
email: felipe@email.com
api:
version: 1.0
management:
endpoints:
web:
exposure:
include: health,info,event-config,shutdown,configprops,beans
endpoint:
configprops:
show-values: always
health:
show-details: always
status:
order: events-down, fatal, down, out-of-service, unknown, up
shutdown:
enabled: true
info:
env:
enabled: true
server:
port: ${PORT:8091}
myretro:
audit:
useLogger: true
prefix: '>>> '
列表 14-24 源自 main/resources/application.yaml
列表 14-24 显示我们使用 myretro.audit.*属性来设置 useLogger 和前缀。如果你在 IDE 中尝试这些属性,你应该能看到我们在 additional-spring-configuration-metadata.json 文件中添加的帮助、描述和提示。
现在我们可以开始运行这个应用了。
使用 myretro-spring-boot-starter 启动用户应用程序
要运行用户应用,您可以使用您的 IDE,或者使用以下命令:
./gradle bootRun
当你运行它时,在 UserConfiguration#init 方法中,我们使用 UserService 保存了两个用户,因此你应该在控制台看到如下输出:
...
...
INFO 19475 --- [users-service] [main] MyRetroAudit: >>> {"id":1,"timestamp":"2024-02-20 18:34:38","interceptor":"BEFORE","method":"saveUpdateUser","args":"[User(email=ximena@email.com, name=Ximena, gravatarUrl=https://www.gravatar.com/avatar/23bb62a7d0ca63c9a804908e57bf6bd4?d=wavatar, password=aw2s0meR!, userRole=[USER], active=true)]","result":"User(email=ximena@email.com, name=Ximena, gravatarUrl=https://www.gravatar.com/avatar/23bb62a7d0ca63c9a804908e57bf6bd4?d=wavatar, password=aw2s0meR!, userRole=[USER], active=true)","message":"Saving or updating user"}
....
....
INFO 19475 --- [users-service] [main] MyRetroAudit: >>> {"id":2,"timestamp":"2024-02-20 18:34:38","interceptor":"BEFORE","method":"saveUpdateUser","args":"[User(email=norma@email.com, name=Norma, gravatarUrl=https://www.gravatar.com/avatar/f07f7e553264c9710105edebe6c465e7?d=wavatar, password=aw2s0meR!, userRole=[USER, ADMIN], active=false)]","result":"User(email=norma@email.com, name=Norma, gravatarUrl=https://www.gravatar.com/avatar/f07f7e553264c9710105edebe6c465e7?d=wavatar, password=aw2s0meR!, userRole=[USER, ADMIN], active=false)","message":"Saving or updating user"}
....
....
是的!我们有一个自定义的启动器,使用日志记录和 JSON 格式。
现在,让我们检查事件是否已成功保存到数据库中。因为在 application.yaml 文件中,我们启用了 H2 控制台(spring.h2.console.enabled=true)。请打开浏览器,访问 http://localhost:8091/h2-console。在 URL 字段中输入 jdbc:h2:mem:users_db,然后点击连接。您将看到两个表,分别是 PEOPLE 和 MY_RETRO_AUDIT_EVENT。选择 MY_RETRO_AUDIT_EVENT 表并运行以下 SQL 语句:
SELECT * FROM MY_RETRO_AUDIT_EVENT
你应该能看到如图 14-2 所示的两行。
图 14-2 http://localhost:8091/h2-console
恭喜您!您刚刚成功创建了 Spring Boot 启动器!
你可以尝试我们添加的所有设置;例如,如果你将 prettyPrint 设置为 true 并重新运行应用程序,你应该会看到类似以下的输出(以美观的 JSON 格式呈现!):
2024-02-20T18:45:26.405-05:00 INFO 20644 --- [users-service] [ main] MyRetroAudit : >>>
{
"id" : 2,
"timestamp" : "2024-02-20 18:45:26",
"interceptor" : "BEFORE",
"method" : "saveUpdateUser",
"args" : "[User(email=norma@email.com, name=Norma, gravatarUrl=https://www.gravatar.com/avatar/f07f7e553264c9710105edebe6c465e7?d=wavatar, password=aw2s0meR!, userRole=[USER, ADMIN], active=false)]",
"result" : "User(email=norma@email.com, name=Norma, gravatarUrl=https://www.gravatar.com/avatar/f07f7e553264c9710105edebe6c465e7?d=wavatar, password=aw2s0meR!, userRole=[USER, ADMIN], active=false)",
"message" : "Saving or updating user"
}
概要
在本章中,您学习了如何创建自己的 Spring Boot 启动器。同时,您还深入了解了 @Conditional*、@Enable* 和 @AutoConfiguration 注解,这些注解在背后发挥着重要作用,帮助您实现所需功能。
你了解了 Condition 接口,并学习了如何对其进行修改,以便通过更多的配置和逻辑来创建自己的 bean,或者跳过下一个自动配置。
你了解到需要在 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 中声明自动配置类。你还学习了 BeanFactoryPostProcessor,以及如何使用它来初始化 bean 或查找信息,就像你在@EnableMyRetroAudit 注解中所做的那样。
现在您对 Spring Boot 的工作原理和它能做什么有了更清晰的理解。在第 15 章中,我们将回顾两个新的 Spring 项目:Spring Modulith 和 Spring AI。