精通Spring Boot 3 : 3. Spring Boot 网络开发 (4)

Spring Boot Web:重写默认设置

现在我们已经完成了两个应用程序,可以探索一些可以自定义的默认设置。这两个应用程序的主要目标是 My Retro App 将使用 Users App 进行身份验证和授权,以及其他一些功能。如果我们想在同一台机器上运行这两个应用程序,默认设置是无法实现的,因为它们都使用相同的端口。但不用担心,Spring Boot 允许我们覆盖这个默认设置,具体如下所述。此外,Spring Boot 还允许我们自定义默认的 JSON 日期格式和应用程序容器,详细信息请参见后面的章节。

覆盖默认服务器配置

默认情况下,嵌入的 Tomcat 服务器会在 8080 端口启动,但您可以通过以下属性轻松更改此设置:

server.port=8082

Spring 的一个很酷的功能是可以将 Spring 表达式语言(SpEL)应用于这些属性。例如,当你创建一个可执行的 JAR(./gradlew build)时,可以在运行应用程序时传递参数。你可以这样操作:

java -jar users-0.0.1-SNAPSHOT.jar --port=8082

在你的 application.properties 文件中,内容大致如下:

server.port=${port:8082}

这个表达的意思是,如果你传递 --port 参数,应用程序会将该值作为端口;如果没有传递,则默认设置为 8182。

这只是您可以使用 SpEL 的一小部分。如果您想了解更多信息,请访问 https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#expressions。

您还可以更改服务器地址,这在您希望使用特定 IP 地址运行应用程序时非常有用。例如:

server.address=10.0.0.7

您还可以修改应用程序的上下文:

server.servlet.context-path=/contacts-app

你可以这样执行一个 cUrl 命令:

curl -I http://localhost:8080/contacts/users

您可以通过以下属性为 Tomcat 配置 SSL:

server.port=8443
server.ssl.key-store=classpath:keystore.jks
server.ssl.key-store-password=secret
server.ssl.key-password=secret

我们将在本章中重新审视这些属性,并使我们的应用程序支持 SSL。您可以通过以下属性来管理会话:

server.servlet.session.store-dir=/tmp
server.servlet.session.persistent=true
server.servlet.session.timeout=15
server.servlet.session.cookie.name=todo-cookie.dat
server.servlet.session.cookie.path=/tmp/cookies

如果您的环境支持,您可以通过以下方式启用 HTTP/2 功能:

JSON 日期格式表示法

默认情况下,日期类型在 JSON 响应中以长格式显示,但您可以通过在以下属性中提供自定义格式来进行更改:

spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=MST7MDT

这些属性用于格式化日期,并采用您指定的时区。

如果您想了解更多可用的时区 ID,请执行 java.util.TimeZone#getAvailableIDs。有关属性的详细信息,请访问 https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html。

使用不同的应用程序容器

默认情况下,Spring Boot 将 Tomcat(用于 Web Servlet 应用)作为应用容器,并配置了一个嵌入式服务器。如果您想要覆盖这个默认设置,可以通过修改 Gradle 的 build.gradle 文件来实现,如列表 3-23 所示。

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.2.3'
    id 'io.spring.dependency-management' version '1.1.4'
}
group = 'com.apress'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'
repositories {
    mavenCentral()
}
ext['jakarta-servlet.version'] = '5.0.0'
dependencies {
    implementation('org.springframework.boot:spring-boot-starter-web')
    modules {
                 module("org.springframework.boot:spring-boot-starter-tomcat") {
                       replacedBy("org.springframework.boot:spring-boot-starter-jetty",
                                                 "Use Jetty instead of Tomcat")
        }
    }
    implementation 'org.springframework.boot:spring-boot-starter-validation'
    // Jetty
          implementation 'org.springframework.boot:spring-boot-starter-jetty'
    // Lombok
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    // Web
    implementation 'org.webjars:bootstrap:5.2.3'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
tasks.named('test') {
    useJUnitPlatform()
}
test {
    testLogging {
        events "passed", "skipped", "failed"
        showExceptions true
        exceptionFormat "full"
        showCauses true
        showStackTraces true
        showStandardStreams = false
    }
}

列表 3-23 的 build.gradle 文件

Spring Boot 网络客户端

现在我们要创建一个客户端,以连接到用户应用程序并获取用户信息。我们将使用 RestTemplate 类来与外部服务进行连接。

要创建这个客户端,我们需要修改 My Retro App 的当前配置。在配置包中,我们之前只有两个类:MyRetroConfiguration 和 MyRetroProperties。现在我们需要添加第三个类,UsersConfiguration。在 MyRetroProperties 中,我们有一些类声明,可以绑定属性,但问题在于可见性,我们需要能够访问这些属性。图 3-5 显示了客户端的新类和包,以下列表将生成这些内容。


config 包中的 MyRetroConfiguration.java、MyRetroProperties.java 和 UsersConfiguration.java 文件的内容分别在列表 3-24、3-25 和 3-26 中展示。

package com.apress.myretro.config;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@EnableConfigurationProperties({MyRetroProperties.class})
@Configuration
public class MyRetroConfiguration {
}

文件路径:src/main/java/com/apress/myretro/config/MyRetroConfiguration.java(列表 3-24)

package com.apress.myretro.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix="service")
@Data
public class MyRetroProperties {
    UsersConfiguration users;
}

src/main/java/com/apress/myretro/config/MyRetroProperties.java

package com.apress.myretro.config;
import lombok.Data;
@Data
public class UsersConfiguration {
    String server;
    Integer port;
    String username;
    String password;
}

src/main/java/com/apress/myretro/config/UsersConfiguration.java

正如你所见,唯一的变化是可见性,以便添加分开的类。

接下来,我们来看看客户端包。列表 3-27 和 3-28 展示了 User 类和 UserRole 枚举。正如你所看到的,这些字段是相同的,只是我们不需要密码。

package com.apress.myretro.client;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@AllArgsConstructor
@NoArgsConstructor
@Data
public class User {
    private String email;
    private String name;
    private String gravatarUrl;
    private List<UserRole> userRole;
    private boolean active;
}

src/main/java/com/apress/myretro/client/User.java

package com.apress.myretro.client;
public enum UserRole {
    ADMIN, USER, INFO
}

src/main/java/com/apress/myretro/client/UserRole.java

客户端包中重要的类是 UsersClient,如清单 3-29 所示,它将远程连接到我们的其他网页应用(用户应用)。

package com.apress.myretro.client;
import com.apress.myretro.config.MyRetroProperties;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import java.text.MessageFormat;
@AllArgsConstructor
@Component
public class UsersClient {
    private final String USERS_URL = "/users";
    private final RestTemplate restTemplate = new RestTemplate();
    private MyRetroProperties myRetroProperties;
    public User findUserByEmail(String email) {
        String uri = MessageFormat.format("{0}:{1}{2}/{3}",
                myRetroProperties.getUsers().getServer(),
                myRetroProperties.getUsers().getPort().toString(),
                USERS_URL,email);
        return restTemplate.getForObject(uri, User.class);
    }
}

src/main/java/com/apress/myretro/client/UsersClient.java

让我们来分析一下 UsersClient 类:

  • MyRetroProperties:这些是定义外部服务的属性,我们可以通过前缀“服务”和类“UsersConfiguration”将它们添加。因此,我们的属性将如下所示(来自 src/main/resources/application.yaml 文件):
service:
  users:
    server: http://localhost
    port: 8082
    username: admin
    password: aW3s0m3
  • RestTemplate:这个类采用模板模式,隐藏了创建连接和处理异常的繁琐代码。RestTemplate 提供了多种实用方法,方便我们获取对象。在本书中,我们将进一步使用这个类的更多功能。

如你所见,这非常简单。现在我们只需要一种方法,通过用户的电子邮件查找用户并获取数据(暂时不包括密码)。

客户端测试

为了测试客户端,请在测试结构中创建 UsersClientTest 类,详见列表 3-30。

package com.apress.myretro;
import com.apress.myretro.client.User;
import com.apress.myretro.client.UsersClient;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest
public class UsersClientTest {
    @Autowired
    UsersClient usersClient;
    @Test
    public void findUserTest() {
        User user = usersClient.findUserByEmail("norma@email.com");
        assertThat(user).isNotNull();
        assertThat(user.getName()).isEqualTo("Norma");
        assertThat(user.getEmail()).isEqualTo("norma@email.com");
    }
}

列表 3-30 src/main/test/com/apress/myretro/UsersClientTest.java

列表 3-30 展示了 UsersClient 的测试。要运行此测试,您需要确保 Users 应用程序在 8082 端口上运行。

概要

在本章中,您学习了如何创建一个 Spring Boot 网络应用程序。您了解到 Spring Web 和 Spring Web MVC 是 Spring Boot 的基础。Spring Boot 会使用默认设置来配置您的网络应用程序,但您也学习了如何更改这些默认设置。

你学习了两种不同的 Web Servlet 应用方法:基于注解和基于功能。你已经完成了两个项目的基本结构,用户应用和我的复古应用,这意味着在后续章节中添加新功能将会相对简单。

目前,我们的应用程序通过内存持久化来存储数据。在第 4 章中,我们将探讨如何将数据存储到 SQL 数据库中,以及 Spring Boot 如何帮助我们使用数据库引擎来管理这些数据。