教程

Gradle 排除传递依赖

1、概览 Gradle 是一款构建自动化工具,用于管理和自动化应用程序的构建、测试和部署过程。 使用基于 Groovy 或 Kotlin 的 DSL(Domain-Specific Language)来定义构建任务,可以轻松地自动定义和管理项目中所需的依赖。 本文将带你了解在 Gradle 中排除传递依赖的几种方法。 2、传递依赖是什么? 假设我们使用的 A 依赖于另一个库 B。默认情况下,当我们包含 A 时,Gradle 会自动将 B 添加到项目的 classpath 中,这样,即使我们没有明确地将 B 添加为依赖,也可以在项目中使用 B 的代码。 来看一个实际的例子,在项目中定义 Google Guava 依赖: dependencies { // ... implementation 'com.google.guava:guava:31.1-jre' } 如果 Google Guava 与其他库存在依赖关系,那么 Gradle 会自动包含这些其他库。 要查看项目中使用的依赖项,可以使用如下命令将其打印出来: ./gradlew <module-name>:dependencies 假设我们的模块名为 excluding-transitive-dependencies: ./gradlew excluding-transitive-dependencies:dependencies 输出如下: testRuntimeClasspath - Runtime classpath of source set 'test'. \--- com.google.guava:guava:31.1-jre +--- com.google.guava:failureaccess:1.0.1 +--- com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava +--- com.google.code.findbugs:jsr305:3.0.2 +--- org.

Spring Data JPA 中的 @DynamicInsert

1、概览 Spring Data JPA 中的 @DynamicInsert 注解通过在 SQL 语句中只包含非 null 字段来优化插入操作。这一过程加快了结果查询的速度,减少了不必要的数据库交互。 虽然它提高了对具有许多可为空字段的实体的效率,但也引入了一些运行时开销。因此,在只有在排除空列的好处超过性能成本的情况下,有选择地使用它。 2、JPA 中 INSERT 的默认行为 使用 EntityManager 或 Spring Data JPA 的 save() 方法持久化 JPA 实体时,Hibernate 会生成一条 SQL 插入(INSERT)语句。该语句包括每个实体列,即使某些列包含 null 值。因此,在处理包含许多可选字段的大型实体时,插入操作的效率会很低。 先来看一个简单的 Account 实体: @Entity public class Account { @Id private int id; @Column private String name; @Column private String type; @Column private boolean active; @Column private String description; // Getter / Setter } 为 Account 实体创建 JPA Repository:

在 Docker 中构建多模块 Maven 项目

1、概览 本文将带你了解如何利用 Docker 的多阶段构建功能高效地为多模块 Maven 项目构建 Docker 镜像,,以充分利用 Docker 的缓存机制。 然后,还会介绍 Google Jib Maven 插件,用于在没有 Dockerfile 或 Docker 的情况下构建 Docker 镜像。 2、多模块 Maven 项目 多模块 Maven 应用由不同功能的独立模块组成。Maven 通过管理依赖关系来构建应用,并将这些模块组装成一个可部署的单元。 在本文的代码示例中,我们将使用一个包含两个 Maven 模块的基本 Spring Boot 项目,这两个模块分别代表应用程序的 Domain 和 API。 Maven 项目的结构如下: +-- parent +-- api | `-- src | `-- pom.xml +-- domain | `-- src | `-- pom.xml `-- pom.xml 查看父模块的 pom.xml 文件,就会发现它继承了 spring-boot-starter-parent,并包含了 domain 和 api 模块: <project> <groupId>com.baeldung.docker-multi-module-maven</groupId> <artifactId>parent</artifactId> <packaging>pom</packaging> <version>0.

Java 反射中的 AccessFlag(访问标志)

1、概览 Java 中的反射是一个强大的功能,它允许我们操纵不同的成员,如类、接口、字段和方法。此外,使用反射,我们可以在编译时实例化类、调用方法和访问字段,而无需知道类型。 本文将带你了解如何使用 JVM AccessFlag(访问标志),以及 Modifier 和 AccessFlag 之间的区别。 2、JVM AccessFlag Java 虚拟机规范 定义了 JVM 中已编译类的结构,它由一个 ClassFile 组成: ClassFile { u4 magic; u2 minor_version; u2 major_version; u2 constant_pool_count; cp_info constant_pool[constant_pool_count-1]; u2 access_flags; u2 this_class; u2 super_class; u2 interfaces_count; u2 interfaces[interfaces_count]; u2 fields_count; field_info fields[fields_count]; u2 methods_count; method_info methods[methods_count]; u2 attributes_count; attribute_info attributes[attributes_count]; } 除其他项目外,ClassFile 还包含 access_flags 项。简而言之,access_flags 是一个掩码,由定义类的访问权限和其他属性的各种标志组成。 此外,ClassFile 还包括 field_info 和 method_info 项,每个项都包含其 access_flags 项。 Javassist 和 ASM 等库使用 JVM AccessFlag 来操作 Java 字节码。

在运行时更改 Spring Boot 属性的几种方式

1、概览 动态管理应用程序配置是许多实际应用场景中的关键要求。在微服务架构中,由于扩展操作或负载条件的变化,不同的服务可能需要即时更改配置。在其他情况下,应用程序可能需要根据用户偏好、来自外部 API 的数据调整其行为,或满足动态变化的要求。 application.properties 文件是静态的,在不重启应用的情况下无法更改。不过,Spring Boot 提供了几种强大的方法,可在运行时调整配置而无需停机。无论是在实时应用程序中切换功能、更新数据库连接以实现负载均衡,还是在不重新部署应用的情况下更改第三方集成的 API Key,Spring Boot 的动态配置功能都能为这些复杂的环境提供所需的灵活性。 本文将带你了解几种无需直接修改 application.properties 文件即可动态更新 Spring Boot 应用程序中属性的策略。这些方法可满足不同的需求,从非持久性内存更新到使用外部文件进行持久性更改。 本文中的示例使用 Spring Boot 3.2.4、JDK17 以及 Spring Cloud 4.1.3。不同版本的 Spring Boot 可能需要对代码进行轻微调整。 2、使用 Prototype Scope Bean 当我们需要动态调整特定 Bean 的属性,而不影响已创建的 Bean 实例或更改全局应用程序状态时,一个简单的使用 @Value 直接注入的 @Service 类是不够的,因为这些属性在 Application Context 的生命周期内是静态的。 相反,可以使用 @Configuration 类中的 @Bean 方法创建具有可修改属性的 Bean。这种方法允许在应用程序执行过程中动态更改属性: @Configuration public class CustomConfig { @Bean @Scope("prototype") // Scope 为 prototype public MyService myService(@Value("${custom.property:default}") String property) { return new MyService(property); } } 通过使用 @Scope("prototype") 注解,我们可以确保每次调用 myService(.

在 Java Servlet 中读取、解析 POST 请求数据

1、简介 Java Servlet 是一个服务端组件,用于处理客户端传入的 HTTP 请求,通常我们需要通过 Servlet 中的 HttpServletRequest 对象来获取到客户端提交的请求数据。 本文将带你了解在 Java Servlet 中读取 Payload(即请求体)数据的各种方法,以及最佳实践和注意事项。 2、理解 Request Payload Post 请求主要用于通过 HTTP 请求向服务器发送数据。这些数据可以是任何内容,从包含用户输入的表单数据到结构化数据如 JSON 和 XML,甚至是二进制文件。这些数据位于请求体中,与 URL 分开。这样可以实现更广泛和安全的数据传输。我们可以通过请求中的 Content-Type Header 标识不同类型的数据。 常见的 Content-Type 包括: application/x-www-form-urlencoded:用于以键值对形式编码的表单数据 application/json:用于 JSON 格式的数据 application/xml:用于 XML 格式的数据 text/plain:用于发送纯文本 multipart/form-data:用于上传二进制文件和常规表单数据(form data) 3、读取 Post 请求体的方式 接下来,让我们看看从 POST Payload 中提取数据的不同方法。 3.1、使用 getParameter() 获取 URL 编码的表单数据 我们可以使用 HttpServletRequest 接口提供的 getParameter() 方法,使用通过 POST 请求提交的参数名检索特定表单数据。该方法使用表单参数名作为参数,并以字符串(String)形式返回相应的值。 举个例子: @WebServlet(name = "FormDataServlet", urlPatterns = "/form-data") public class FormDataServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { String firstName = StringEscapeUtils.

Spring 中的 Fallback Bean

1、概览 本文将带你了解 Spring 中 Fallback Bean 的概念。 Fallback Bean 是在 Spring Framework 6.2.0-M1 中引入的,当另一个相同类型的 Bean 不可用或无法初始化时,它提供了一种替代实现。 2、Primary Bean 和 Fallback Bean 在 Spring 中,我们可以定义多个相同类型的 Bean。默认情况下,Spring 使用 Bean 名称和类型来标识 Bean。当有多个名称和类型相同的 Bean 时,可以使用 @Primary 注解将其中一个标记为 Primary(主要)Bean,使其优先于其他 Bean。如果在 Application Context 初始化时创建了多个相同类型的 Bean,而我们又想指定默认使用哪个 Bean,那么这就非常有用了。 同样,我们可以定义一个 Fallback Bean,以便在没有其他合格 Bean 时提供替代实现。我们可以使用 @Fallback 注解将一个 Bean 标记为 Fallback(后备) Bean。只有当没有其他同名的 Bean 可用时,才会将后备 Bean 注入到 Application Context。 3、示例代码 来看一个示例,演示如何在 Spring 应用中使用 Primary Bean 和 Fallback Bean。 我们要创建一个使用不同 MQ 服务发送消息的小应用。假设我们在生产环境和非生产环境中拥有多个 MQ 服务,并且需要在它们之间切换以优化性能和成本。

Spring Boot 3.4 中的结构化日志

日志记录是应用故障排除的重要组成部分,也是可观测性的三大支柱之一,另外两个是指标和追踪(Trace)。没有人喜欢在生产环境中瞎操作,当事故发生时,开发者会很乐意看到日志文件。日志通常以人类可读的格式输出。 结构化日志是一种技术,其中日志输出以定义良好的格式编写,通常是机器可读的。这种格式可以输入到日志管理系统中,从而实现强大的搜索和分析功能。结构化日志最常用的格式之一是 JSON。 Spring Boot 3.4 开箱即支持结构化日志。它支持 Elastic Common Schema(ECS)和 Logstash 格式,也可以使用自己的格式进行扩展。 结构化日志的 Hello World 在 start.springboot.io 上创建一个新项目,不需要添加任何依赖,但至少要选择 Spring Boot 3.4.0-M2。 要在控制台上启用结构化日志记录,请将如下配置添加到 application.properties 中: logging.structured.format.console=ecs 这将指示 Spring Boot 以 Elastic Common Schema(ECS) 格式输出日志。 启动应用,你就会看到日志是 JSON 格式的: {"@timestamp":"2024-07-30T08:41:10.561295200Z","log.level":"INFO","process.pid":67455,"process.thread.name":"main","service.name":"structured-logging-demo","log.logger":"com.example.structured_logging_demo.StructuredLoggingDemoApplication","message":"Started StructuredLoggingDemoApplication in 0.329 seconds (process running for 0.486)","ecs.version":"8.11"} 将结构化日志记录到文件 你还可以将结构化日志写入文件。例如,这可以用于在控制台上打印人类可读的日志,同时将结构化日志写入文件以供机器读取。 要启用此功能,请将如下配置添加到 application.properties 中,并确保删除 logging.structured.format.console=ecs 设置: logging.structured.format.file=ecs logging.file.name=log.json 现在启动应用,你会看到控制台上有人类可读的日志,而 log.json 文件则包含机器可读的 JSON 内容。 添加额外字段 结构化日志的一个强大功能是,开发人员可以以结构化的方式在日志事件中添加信息。例如,你可以在每个日志事件中添加用户 ID,然后根据该 ID 进行过滤,查看这个特定用户做了什么。 Elastic Common Schema 和 Logstash 都在 JSON 中包含了 Mapped Diagnostic Context 的内容。

Spring Cloud Gateway 整合 OpenAPI

1、概览 文档是构建任何健壮的 REST API 的重要部分。我们可以基于 OpenAPI 规范实现 API 文档,并在 Spring 应用中使用 Swagger UI 进行可视化展示。 此外,由于 API 端点可以通过 API 网关公开,我们还需要将后端服务的 OpenAPI 文档与网关服务集成。网关服务将提供所有 API 文档的汇总视图。 本文将带你了解如何在 Spring 应用中集成 OpenAPI,以及如何使用 Spring Cloud Gateway 服务公开后端服务的 API 文档。 2、示例应用 假设我们需要构建一个简单的微服务来获取一些数据。 2.1、Maven 依赖 首先,添加 spring-boot-starter-web 依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>3.3.2</version> </dependency> 2.2、实现 REST API 我们的后端应用有一个端点返回 Product 数据。 首先,创建 Product 类: public class Product { private long id; private String name; // 标准 Getter / Setter } 接下来,在 ProductController 中实现 getProduct 端点:

在 Spring Authorization Server 中动态注册客户端

1、简介 Spring Authorization Server(授权服务器)自带一系列合理的默认设置,开箱即用。 但是,它还有一个功能,默认下没有启动:态客户端注册。本文将带你了解如何在客户端应用中启用和使用它。 2、为什么使用动态注册? 当基于 OAuth2 的客户端应用(在 OIDC 术语中称为依赖方)启动认证流程时,它将自己的客户端标识符发送给身份提供者(Provider)。 一般情况下,这个标识符是通过外部流程(如邮件发送等其他手段)发放给客户端的,客户端随后将其添加到配置中,并在需要时使用。 例如,在使用 Azure 的 EntraID 或 Auth0 等流行的身份提供商(Identity Provider)解决方案时,我们可以使用管理控制台或 API 来配置新客户端。在此过程中,我们需要告知应用名称、授权回调 URL、支持的作用域等信息。 提供所需信息后,我们会得到一个新的客户端标识符,对于所谓的 “secret” 客户端,还将得到一个 client secret。然后,我们将这些信息添加到应用的配置中,就可以开始部署了。 现在,当我们应用不多,或者总是使用单一的一个身份供应商时(Identity Provider),这种方式就能正常工作。但对于更复杂的情况,注册过程需要是动态的,这就是 OpenID Connect 动态客户端注册规范 的用武之地。 在现实世界中,英国的 OpenBanking 标准就是一个很好的例子,该标准将动态客户注册作为其核心协议之一。 3、动态注册是如何实现的? OpenID Connect 标准使用一个注册 URL,客户端使用该 URL 注册自己。注册是通过 POST 请求完成的,该请求包含一个 JSON 对象,其中有执行注册所需的客户端元数据。 重要的是,访问注册端点需要身份认证,通常是一个 Bearer Token。当然,这就引出了一个问题:想成为客户端的人如何获得用于此操作的 Token? 遗憾的是,答案并不明确。一方面,规范指出端点是受保护的资源,因此需要某种形式的身份认证。另一方面,它也提到了开放注册端点的可能性。 对于 Spring 授权服务器来说,注册需要一个具有 client.create scope 的 Bearer Token。要创建该令牌,我们需要使用常规 OAuth2 的 Token 端点和基本凭证。 动态注册的流程如下: 客户端注册成功后,就可以使用返回的客户端 ID 和 secret secret 执行任何标准授权流程。