教程

使用 Spring Boot 创建 Docker 镜像

1、概览 随着越来越多的企业转向使用容器,Docker 在软件开发中的地位也越来越重要。为此,Spring Boot 2.3 的一大新功能就是为 Spring Boot 应用轻松创建 Docker 镜像提供了支持。 本文将带你了解如何为 Spring Boot 应用创建 Docker 镜像。 2、传统的 Docker 构建 使用 Spring Boot 构建 Docker 镜像的传统方法是使用 Dockerfile。 下面是一个简单的 Dockerfile 示例: FROM openjdk:8-jdk-alpine EXPOSE 8080 ARG JAR_FILE=target/demo-app-1.0.0.jar ADD ${JAR_FILE} app.jar ENTRYPOINT ["java","-jar","/app.jar"] 然后,使用 docker build 命令创建 Docker 镜像。这对大多数应用都很有效,但也有一些缺点。 首先,我们使用的是 Spring Boot 创建的 Fat jar。这会影响启动时间,尤其是在容器化环境中。我们可以通过添加 Jar 文件的解压内容来节省启动时间。 其次,Docker 镜像是分层构建的。Spring Boot Fat Jar 的性质导致所有应用代码和第三方库都被放在一个层中。这意味着,即使只有一行代码发生变化,也必须重建整个层。 通过在构建之前解压 Jar 文件,应用代码和第三方库分别有自己的层。这使我们能够利用 Docker 的缓存机制,当更改一行代码时,只需要重新构建相应的层。 有了这个理念,让我们看看 Spring Boot 如何改进创建 Docker 镜像的过程。

Linux 与 Spring 中 Cron 的区别

1、概览 通过 Cron 表达式,我们可以安排任务在特定日期和时间定期运行。Cron 表达式在 Unix 中推出后,其他基于 Unix 的操作系统和软件库(包括 Spring)都采用了它的任务调度方法。 本文将带你了解基于 Unix 操作系统的 Cron 表达式与 Spring Cron 之间的区别。 2、Unix Cron 在大多数基于 Unix 的系统中,Cron 有五个字段:分钟(0-59)、小时(0-23)、月日(1-31)、月份(1-12 或名称)和星期(0-7 或名称)。 可以在每个字段中添加一些特殊值,如星号(*): 5 0 * * * 任务将在每天午夜后 5 分钟执行。也可以使用数值范围: 5 0-5 * * * 如上,调度器将在午夜 12 点后 5 分钟执行任务,并在每天 1 点、2 点、3 点、4 点和 5 点后 5 分钟执行任务。 或者,可以使用一个值列表: 5 0,3 * * * 现在,调度器会在每天午夜 12 点后 5 分钟和下午 3 点后 5 分钟执行作业。原始 Cron 表达式提供的功能远不止这些。

在 Spring Boot 中通过 XML 定义 Bean

1、概览 在 Spring 3.0 之前,XML 是定义和配置 Bean 的唯一方法。Spring 3.0 引入了 JavaConfig,允许我们使用 Java 类配置 Bean。不过,如今有些项目仍然在使用 XML 配置文件。 本文将带你了解如何在 Spring Boot 应用中整合 XML 配置。 2、@ImportResource 注解 @ImportResource 注解可以导入一个或多个包含 Bean 定义的资源。 比方说,我们有一个包含 Bean 定义的 beans.xml 文件: <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <bean class="com.baeldung.springbootxml.Pojo"> <property name="field" value="sample-value"></property> </bean> </beans> 要在 Spring Boot 应用中使用它,可以使用 @ImportResource 注解,告诉它在哪里可以找到配置文件: @Configuration @ImportResource("classpath:beans.xml") public class SpringBootXmlApplication implements CommandLineRunner { @Autowired private Pojo pojo; public static void main(String[] args) { SpringApplication.

在 Spring Boot 中恢复 Flyway 失败的迁移

1、概览 Flyway 迁移并不总是一帆风顺行,本文将带你了解迁移失败后的恢复方案。 2、设置 从基本的 Spring Boot 配置 Flyway 开始。它依赖 flyway-core、spring-boot-starter-jdbc 和 flyway-maven-plugin。 对于如何在 Spring Boot 中使用 Flyway 进行数据库迁移,你可以参阅 这篇文章。 2.1、配置 首先,添加两个不同的 Profile(配置文件)。这能够轻松地针对不同的数据库引擎运行迁移: <profile> <id>h2</id> <activation> <activeByDefault>true</activeByDefault> </activation> <dependencies> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> </dependency> </dependencies> </profile> <profile> <id>postgre</id> <dependencies> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> </dependency> </dependencies> </profile> 还要为每个 Profile 添加 Flyway 数据库配置文件。 首先,创建 application-h2.properties: flyway.url=jdbc:h2:file:./testdb;DB_CLOSE_ON_EXIT=FALSE;AUTO_RECONNECT=TRUE;MODE=MySQL;DATABASE_TO_UPPER=false; flyway.user=testuser flyway.password=password 然后,创建 PostgreSQL application-postgre.properties: flyway.url=jdbc:postgresql://127.0.0.1:5431/testdb flyway.user=testuser flyway.password=password 注:你可以调整 PostgreSQL 配置,使其与你的数据库相匹配,也可以使用 代码示例中的 docker-compose 文件。 2.2、迁移 添加第一个迁移(migration)文件 V1_0__add_table.sql: create table table_one ( id numeric primary key ); 添加第二个包含错误的迁移文件 V1_1__add_table.

如果 @PathVariable 包含点(.),会被截断

1、概览 使用 Spring 的 @PathVariable 和 @RequestMapping 来映射包含点的请求时,最后一个 URI 路径变量的值被会截断。 2、原因 具体来说,Spring 认为最后一个点后面的任何内容都是文件扩展名,如 .json 或 .xml,因此,它会截断值以检索路径变量。 来看一个使用路径变量的例子: @RestController public class CustomController { @GetMapping("/example/{firstValue}/{secondValue}") public void example(@PathVariable("firstValue") String firstValue, @PathVariable("secondValue") String secondValue) { // ... } } 如上,考虑以下请求 URL 以及 firstValue 和 secondValue 变量的值: example/gallery/link:firstValue = "gallery",secondValue = "link"。 example/gallery.df/link.ar URL:firstValue = "gallery.df ",secondValue = "link" example/gallery.df/link.com.ar:firstValue = "gallery.df",secondValue = "link.com" 可以看到,第一个变量不受影响,但第二个带点(.)的变量总是被截断。 3、解决办法 解决这种不便的方法之一是修改 @PathVariable 定义,添加一个 regex(正则)映射。这样,任何点(包括最后一个点)都将被视为参数的一部分: @GetMapping("/example/{firstValue}/{secondValue:.+}") public void example( @PathVariable("firstValue") String firstValue, @PathVariable("secondValue") String secondValue) { //.

Spring 中的 @PathVariable 注解

1、概览 本文将带你了解 Spring 中 @PathVariable 注解的作用和用法。 简单地说,@PathVariable 注解可用于处理请求 URI 映射中的模板变量,并将其绑定到 Controller 方法参数。 2、示例映射 @PathVariable 注解的一个简单用例是用于标识具有 ID 的实体的端点: @GetMapping("/api/employees/{id}") @ResponseBody public String getEmployeesById(@PathVariable String id) { return "ID: " + id; } 在本例中,使用 @PathVariable 注解来提取 URI 的模板部分,该部分由变量 {id} 表示。 调用示例如下: http://localhost:8080/api/employees/111 ---- ID: 111 3、指定 PATH (路径)变量名 在上一个示例中,由于方法参数和路径变量的名称相同,可以不用主动设置模板路径变量的名称。 如果路径变量名不同,可以在 @PathVariable 注解的参数中指定: @GetMapping("/api/employeeswithvariable/{id}") @ResponseBody public String getEmployeesByIdWithVariableName(@PathVariable("id") String employeeId) { return "ID: " + employeeId; } 测试如下: http://localhost:8080/api/employeeswithvariable/1 ---- ID: 1 为了清晰起见,还可以将路径变量名定义为 @PathVariable(value="id"),而不是 PathVariable("id")。

使用 Spring Security 构建 OAuth 2.0 资源服务器

1、概览 本文将带你了解如何使用 Spring Security 构建 OAuth 2.0 资源服务器(使用 JWT 和 Opaque Token,这两种由 Spring Security 支持的 Bearer Token)。 2、背景介绍 2.1、JWT 和 Opaque Token 是什么? JWT 或 JSON Web Token 是一种以广泛接受的 JSON 格式安全传输敏感信息的方式。其中包含的信息可能是关于用户的,也可能是关于 Token 本身的,例如其 expiry(有效期)和 issuer(签发者)。 Opaque Token 顾名思义,它所携带的信息是不透明的。Token 只是一个标识符,指向存储在授权服务器上的信息;它通过服务器端的自省(Introspection)进行验证。 2.2、资源服务器是什么? 在 OAuth 2.0 中,资源服务器是通过 OAuth Token 保护资源的应用。这些 Token 由授权服务器(通常是客户端应用)签发。资源服务器的工作是在向客户端提供资源之前验证 Token。 令牌的有效性由几个因素决定: 该令牌是否来自配置的授权服务器? 是否未过期? 该资源是否为预期受众(audience)提供服务? Token 是否具有访问所请求资源的必要权限? 来看一下 授权码模式 的顺序图,并观察所有相关的参与者: 正如在步骤 8 中看到的,当客户端应用调用资源服务器的 API 访问受保护的资源时,它首先会转到授权服务器,以验证请求的 Authorization: Bearer Header 信息中包含的 Token,然后响应客户端。 本文的重点是第 9 步。

Spring WebFlux 中的并发

1、简介 本文将带你了解 Spring WebFlux 响应式应用中的并发。 2、响应式编程的动机 一个典型的 Web 应用由多个复杂的交互部分组成。其中许多交互在本质上是阻塞性的,例如那些涉及数据库调用以获取或更新数据的交互。而其他几个部分则是独立的,可以 并发 执行,也可能是 并行 执行。 并发是多个任务在同一时间段内交替执行,而并行是多个任务同时执行。并发关注的是任务的调度和切换,以提高系统的效率和响应性,而并行关注的是任务的同时执行,以提高计算速度和性能。 例如,用户对 Web 服务器的两个请求可以由不同的线程处理。在多核平台上,这对整体响应时间有明显的好处。因此,这种并发模型被称 thread-per-request(每个请求一个线程)模型: 如上图,每个线程一次处理一个请求。 虽然基于线程的并发为我们解决了部分问题,但却无法解决单个线程内的大部分交互仍然是阻塞的这一事实。此外,在 Java 中使用原生线程来实现并发时,还需要付出上下文切换的巨大代价。 与此同时,随着 Web 应用面临的请求越来越多,thread-per-request 模式开始无法满足人们的期望。 因此,我们需要一种并发模型,它可以帮助我们用相对较少的线程数处理越来越多的请求。这也是采用响应式编程的主要动机之一。 3、响应式编程中的并发 响应式编程可以帮助我们根据数据流和变化的传播来构建程序。在完全无阻塞的环境中,这可以让我们实现更高的并发性和更好的资源利用率。 然而,响应式编程是否完全摒弃了基于线程的并发?虽然这种说法有些激烈,但响应式编程肯定与使用线程实现并发的方法截然不同。响应式编程带来的根本区别在于 异步。 换句话说,程序流程从一连串同步操作转变为异步事件流。 例如,在响应式模型下,对数据库的读取调用不会在获取数据时阻塞调用线程。调用会立即返回一个发布者(Publisher),其他人可以订阅该发布者。订阅者(Subscriber)可以在事件发生后对其进行处理,甚至可以自己进一步生成事件: 最重要的是,响应式编程并不强调应该生成和消耗哪个线程事件。相反,它强调的是将程序构造成异步事件流。 这里的发布者和订阅者不需要属于同一个线程。这有助于我们更好地利用可用线程,从而提高整体并发性。 4、Event Loop Event Loop(事件循环)模型是一种用于服务器的响应式异步编程模型: 上图是一个事件循环的抽象设计,展示了响应式异步编程的思想: Event Loop 在单个线程中连续运行,我们可以根据可用内核的数量设置多个 Event Loop。 Event Loop 按顺序处理来自事件队列的事件,并在向平台注册回调后立即返回。 Platform(平台)可以触发操作的完成,如数据库调用或外部服务调用。 Event Loop 可在 operation(操作)完成通知时触发 callback(回调),并将结果发回给原始调用者。 包括 Node.js、Netty 和 Ngnix 在内的许多平台都实现了 Event Loop 模型。与 Apache HTTP 服务器、Tomcat 或 JBoss 等传统平台相比,它们具有更好的可扩展性。 5、用 Spring WebFlux 进行响应式编程 对响应式编程及其并发模型有了足够的了解后,来看看 Spring WebFlux。它是 Spring 在 5.

在 YAML 中定义 POJO 中的 Map 属性

1、概览 本文将带你了解如何在 YAML 配置文件中定义 POJO 类中的 Map 属性。 2、POJO 和 YAML POJO 即 Plain Old Java Objects(普通旧 Java 对象)。YAML 是一种人类可读的结构化数据格式,使用缩进表示嵌套。 2.1、简单的 Map 假设我们正在经营一家网店,并且要创建一个服装尺码转换的服务。起初,我们只销售英国的服装。我们想知道标签 S、M、L 等代表的是英国的哪个尺码。 创建 POJO 配置类: @ConfigurationProperties(prefix = "t-shirt-size") public class TshirtSizeConfig { private Map<String, Integer> simpleMapping; public TshirtSizeConfig(Map<String, Integer> simpleMapping) { this.simpleMapping = simpleMapping; } //get、set } 注意 @ConfigurationProperties 注解的 prefix 值表示配置前缀,在下一章会定义该配置。 还要在 Application.class 上使用以下注解启用配置属性类: @EnableConfigurationProperties(TshirtSizeConfig.class) public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } } 2.

Spring Boot 中的健康指标(Health Indicators)

1、概览 Spring Boot 提供了几种不同的方法来检查运行中的应用及其组件的状态和健康状况。在这些方法中,HealthContributor 和 HealthIndicator API 是值得注意的两种。 本文将带你了解这些 API 的原理以及如何向它们提供自定义信息。 2、依赖 Health Information Contributor(健康信息贡献者)是 Spring Boot Actuator 模块的一部分: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> 3、内置的 HealthIndicator Spring Boot 注册了许多开箱即用的 HealthIndicator 来报告应用特定方面的健康状况。 其中一些指标默认已经注册了,如 DiskSpaceHealthIndicator 或 PingHealthIndicator。前者报告磁盘的当前状态,后者则作为应用的 ping 端点。 Spring Boot 也会有条件地注册一些指标。也就是说,如果 classpath 上存在某些依赖或满足某些其他条件,Spring Boot 可能也会注册一些其他的 HealthIndicator。例如,如果使用了关系型数据库,那么 Spring Boot 就会注册 DataSourceHealthIndicator。同样,如果使用的是 Cassandra 作为数据存储,它会注册 CassandraHealthIndicator。 可以调用 /actuator/health 端点来检查 Spring Boot 应用的健康状态。该端点会报告所有已注册的 HealthIndicator 的汇总结果。 如果要查看某个特定指标的健康报告,可以调用 /actuator/health/{name} 端点。例如,调用 /actuator/health/diskSpace 端点将返回 DiskSpaceHealthIndicator 的状态报告: { "status": "UP", "details": { "total": 499963170816, "free": 134414831616, "threshold": 10485760, "exists": true } } 4、自定义 HealthIndicator 除了内置的 HealthIndicator,还可以注册自定义 HealthIndicator 来报告组件或子系统的健康状况。只需将 HealthIndicator 接口的实现注册为 Spring Bean 即可。