1、概览 在处理 Java 项目时,建立新项目或更新或添加依赖时常见的错误是 “class file has wrong version(类文件版本错误)”。
本文将带你了解出现这个错误的原因以及解决办法。
2、原因 要了解根本原因,我们需要更多地了解类文件。
每当我们编译 Java 代码时,都会为编译文件中包含的每个类创建一个 .class 文件。这些 .class 文件的部分元数据是一个版本号,该版本号与用于编译代码的 Java 当前主版本号相对应。
对于最常见的 Java 版本,这些类的版本号分别为:Java 21 = 65.0、Java 17 = 61.0、Java 11 = 55.0 和 Java 8 = 51.0。如果我们使用的是不同版本的 Java,可以根据需要加减一个版本号,从而计算出相应的类文件版本号。
在运行时,Java 会查看每个 .class 文件,并要求运行环境的版本号大于或等于编译 .class 文件时使用的版本号。这种版本号要求是出于 Java 的向后兼容性机制。也就是说,如果我们运行的是 Java 17 代码,我们就可以使用任何用 Java 17 或以前版本的 Java 编译的类文件。但是,如果我们遇到一个用 Java 21 编译的 .class 文件,就会遇到 “class file has wrong version 65.0, should be 61.0”(class 文件版本号错误,应为 61.
1、概览 在分布式系统和微服务架构中,优雅地处理故障对于保持系统可靠性和性能至关重要。断路器(Circuit Breaker,也称为熔断器)和重试(Retry)是有助于实现这一目标的两种基本弹性模式。虽然这两种模式都旨在提高系统的稳定性和可靠性,但它们的目的截然不同,适用于不同的场景。
本文将带你深入了解这些模式,包括它们的机制、用例以及在 Spring Boot 中使用 Resilience4j 实现的细节。
2、什么是重试? 重试模式是一种简单而强大的机制,用于处理分布式系统中的瞬时故障。当操作失败时,重试模式会尝试多次执行相同的操作,希望临时问题能自行解决。
2.1、重试的主要特点 重试机制围绕特定属性展开,这些属性使重试机制能够有效处理瞬时问题,确保临时故障不会升级为重大问题:
重复尝试:核心理念是对失败的操作重新执行指定次数。 Backoff(回退)策略:这是一种高级的重试机制,其中包括退避策略,如指数退避,有助于避免对系统造成过大压力。 适用于临时故障:最适合处理间歇性网络问题、临时服务不可用或瞬间资源紧张的情况。 2.2、重试示例 来看一个使用 Resilience4j 实现重试机制的简单示例:
@Test public void whenRetryWithExponentialBackoffIsUsed_thenItRetriesAndSucceeds() { IntervalFunction intervalFn = IntervalFunction.ofExponentialBackoff(1000, 2); RetryConfig retryConfig = RetryConfig.custom() .maxAttempts(5) .intervalFunction(intervalFn) .build(); Retry retry = Retry.of("paymentRetry", retryConfig); when(paymentService.process(1)).thenThrow(new RuntimeException("First Failure")) .thenThrow(new RuntimeException("Second Failure")) .thenReturn("Success"); Callable<String> decoratedCallable = Retry.decorateCallable( retry, () -> paymentService.processPayment(1) ); try { String result = decoratedCallable.call(); assertEquals("Success", result); } catch (Exception ignored) { } verify(paymentService, times(3)).
1、简介 NullPointerException(空指针异常)是 Java 最常见的异常之一。它发生在访问或与指向空(null)的引用变量交互时。验证对象非空(尤其是当它们是方法或构造函数中的参数时)对于确保代码的健壮性和可靠性非常重要。我们可以通过编写自己的 null 检查代码、使用第三方库或选择更方便的方法来实现这一点。
本文将带你了解 Java 中内置的一种灵活的解决方案 - Objects.requireNonNull()。
2、Null 值处理 简单回顾一下,有很多方法可以避免手动空值检查。我们可以从各种库中进行选择,而不是用 if 语句来封装我们的代码,这可能会导致错误和耗时。Spring、Lombok(@NonNull) 和 Uber 的 NullAway 就是其中的几种。
相反,如果我们想保持一致性,避免在代码库中引入额外的库,或者使用普通 Java,我们可以选择 Optional 类或 Objects 方法。虽然 Optional 可简化空值处理,但它往往会增加编码开销,并使简单用例的代码变得杂乱无章。由于它是一个独立的对象,因此也会消耗额外的内存。此外,Optional 只能将 NullPointerException 转换为 NoSuchElementException,即不能解决缺陷。
而,Objects 类提供了静态工具方法,可以更高效地处理对象。该类在 Java 7 中引入,并在 Java 8 和 Java 9 中多次更新,它还提供了在执行操作前验证条件的方法。
3、Objects.requireNonNull() 的优点 Objects 类通过提供一组 requireNonNull() 静态方法,简化了对 null 值的检查和处理。这些方法提供了一种简单明了的方法,可在使用前检查对象是否为空。
requireNonNull() 方法的主要目的是检查对象引用是否为空,如果为空,则抛出 NullPointerException 异常。通过明确抛出异常,我们传达了检查的意图,使代码更易于理解和维护。这也会告知开发人员和维护人员,该错误是故意的,这有助于防止随着时间的推移对行为进行无意的更改。
Objects 类是 java.util 包的一部分,因此无需外部库或依赖关系即可轻松访问。它的方法都有详细的文档说明,为开发人员提供了正确使用的明确指导。
4、使用案例 现在,来看看 requireNonNull() 方法的各种用例(包括其各种重载方法)。
这些方法允许在不同情况下处理空值,从简单的空值检查到带有自定义信息的更复杂的验证。
此外,还有两个方法支持默认值 - requireNonNullElse() 和 requireNonNullElseGet()。
1、概览 日志是任何软件应用程序的基本功能。它通过记录错误、警告和其他事件,帮助跟踪应用程序在运行期间的行为。
默认情况下,Spring Boot 应用程序会生成非结构化、人类可读的日志。虽然这些日志对开发人员很有用,但它们不容易被日志聚合工具解析或分析。结构化日志解决了这一限制。
本文将带你了解如何使用 Spring Boot 3.4.0 版中引入的功能实现结构化日志。
2、Maven 依赖 首先,在 pom.xml 中添加 spring-boot-starter 来启动 Spring Boot 项目:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <version>3.4.0</version> </dependency> 上述依赖为 Spring Boot 应用中的自动配置和日志记录提供了支持。
3、Spring Boot 默认的日志 以下是 Spring Boot 的默认日志:
INFO 22059 --- [ main] c.b.s.StructuredLoggingApp : No active profile set, falling back to 1 default profile: "default" INFO 22059 --- [ main] c.b.s.StructuredLoggingApp : Started StructuredLoggingApp in 2.349 seconds (process running for 3.259) 虽然这些日志信息量很大,但却无法被 Elasticsearch 等工具轻松抓取或进行指标分析。JSON 等结构化日志格式通过标准化日志内容解决了这一问题。
1、简介 通过使用联机分析处理(Online Analytical Processing,OLAP),企业可以深入了解当前的运营情况,并确定改进趋势。这通常是通过对汇总的业务数据进行复杂的分析来实现的。
ClickHouse 是一个开源的、列式 OLAP 数据库,因其出色的 性能 而大受欢迎。
本文将带你了解如何在 Spring Boot 中整合、使用 ClickHouse。
2、项目设置 在开始与 ClickHouse 数据库交互之前,我们需要添加一些 SDK 依赖,并正确配置我们的应用。
2.1、依赖 首先,在项目的 pom.xml 中添加必要的依赖:
<dependency> <groupId>com.clickhouse</groupId> <artifactId>clickhouse-jdbc</artifactId> <version>0.7.1</version> </dependency> <dependency> <groupId>org.lz4</groupId> <artifactId>lz4-java</artifactId> <version>1.8.0</version> </dependency> clickhouse-jdbc 依赖提供了 JDBC API 的实现,使我们能够与 ClickHouse 数据库建立连接并进行交互。
默认情况下,ClickHouse 使用 LZ4 压缩来存储数据,为此我们添加了 lz4-java 依赖项。
2.2、使用 Flyway 定义数据表 接下来,定义数据库表,并对其执行操作。
使用 Flyway 来管理数据库迁移。这需要添加 flyway-core 和 flyway-database-clickhouse 依赖:
<dependency> <groupId>org.flywaydb</groupId> <artifactId>flyway-core</artifactId> </dependency> <dependency> <groupId>org.flywaydb</groupId> <artifactId>flyway-database-clickhouse</artifactId> <version>10.16.3</version> </dependency> 将这些依赖添加到 pom.xml 之后,在 src/main/resources/db/migration 目录中创建一个名为 V001__create_table.
1、简介 本文将带你了解 Spring 中 chain.doFilter() 方法的作用。
2、什么是 Spring Filter 在 Spring 应用中,Filter 过滤器以 Java Servlet Filter 为基础,后者代表拦截请求和响应的对象。Filter 是 Java Servlet API 的一部分,在 Web 应用中扮演着重要角色,因为它们位于客户端和服务器处理逻辑之间。
利用它们,我们可以在请求到达 servlet 之前或生成响应之后执行任务。过滤器的常见用例包括
认证和授权 审计和日志 修改请求/响应 过滤器虽然不是 Spring 框架的一部分,但与 Spring 框架完全兼容。我们可以将它们注册为 Spring Bean,并在应用中使用它们。Spring 提供了一些过滤器的实现,其中常见的包括 OncePerRequestFilter 和 CorsFilter。
3、理解 chain.doFilter() 方法 在了解 chain.doFilter() 方法之前,首先要了解过滤器链(Filter Chain)的概念及其在过滤过程中的作用。
过滤器链表示应用于传入请求或传出响应的处理逻辑顺序流。换句话说,它是用于预处理请求或后处理响应的过滤器集合。这些过滤器以明确定义的顺序排列,确保每个过滤器都能在将请求或响应传递到链中的下一阶段之前执行其处理逻辑。
要定义过滤器链,可以使用了 Java Servlet API 中的 FilterChain 接口,其中包含了我们感兴趣的方法。查看该方法的签名,就会发现请求(request)和响应(response)对象被定义为输入参数:
void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException; chain.doFilter() 方法将请求和响应传递给链中的下一个过滤器。如果过滤器链中没有过滤器,则会将请求转发给目标资源(通常是 Servlet),并将响应发送给客户端。
3.1、为什么调用 chain.doFilter() 非常重要? 我们可以看到,chain.
1、概览 在使用 Spring Data 开发应用时,我们经常需要根据选择条件构建动态查询,以便从数据库中获取数据。
本文将带你了解在 Spring Data JPA Repository 中创建动态查询的三种方法:Example 查询、Specification 查询和 Querydsl 查询。
2、示例 创建 School 和 Student 两个实体。这两个实体类之间的关系是一对多,即一个 School 可以有多个 Student:
@Entity @Table public class School { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column private Long id; @Column private String name; @Column private String borough; @OneToMany(mappedBy = "school") private List<Student> studentList; // 构造函数、Getter、Setter 省略 } @Entity @Table public class Student { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column private Long id; @Column private String name; @Column private Integer age; @ManyToOne private School school; // 构造函数、Getter、Setter 省略 } 除了实体类,还要为 Student 实体定义一个 Spring Data Repository:
我们很高兴推出 Spring AI MCP,它是模型上下文协议(Model Context Protocol,MCP)的强大 Java SDK 实现。Spring AI 生态系统的这一新成员为 Java 平台带来了标准化的 AI 模型集成能力。
MCP 是什么? 模型上下文协议(MCP)是一种开放式协议,它规范了应用程序为大型语言模型(LLM)提供上下文的方式。MCP 提供了一种将人工智能模型连接到不同数据源和工具的标准化方法,使集成无缝且一致。它可以帮助你在 LLM 的基础上构建代理和复杂的工作流程。LLM 经常需要与数据和工具集成,而 MCP 提供了以下功能:
一个不断扩充的预构建集成列表,你的大语言模型(LLM)可以直接接入使用。 在不同的大语言模型(LLM)提供商和供应商之间灵活切换的能力。 总体结构 MCP 的核心是客户端-服务器(CS)架构,一个应用可以连接多个服务器。
Spring AI MCP 采用模块化架构,包含以下组件:
Spring AI 应用: 使用 Spring AI 框架构建希望通过 MCP 访问数据的生成式 AI 应用。 Spring MCP 客户端:与服务器保持 1:1 连接的 MCP 协议的 Spring AI 实现。 MCP 服务器:轻量级程序,每个程序都通过标准化的模型上下文协议公开特定功能。 本地数据源:MCP 服务器可安全访问的计算机文件、数据库和服务。 远程服务:MCP 服务器可通过互联网(如 API)连接的外部系统。 该架构支持广泛的用例,从简单的文件系统访问到复杂的多模型人工智能与数据库和互联网连接的交互。
入门 Spring AI MCP GitHub:https://github.com/spring-projects-experimental/spring-ai-mcp
Maven 依赖 在 Maven 项目中添加以下依赖之一
1、简介 本文将带你了解如何将 Future 转换为 CompletableFuture。通过这种转换,我们可以利用 CompletableFuture 的高级功能,如非阻塞操作、链式任务和更好的错误处理,同时仍可使用返回 Future 的 API 或库。
2、为什么要把 Future 转换为 CompletableFuture? Java 中的 Future 接口表示异步计算的结果。它提供了检查计算是否完成、等待计算完成和检索结果的方法。
不过,Future 也有其局限性,比如阻塞调用需要使用 get() 来获取结果。此外,它还不支持链式调用多个异步任务或处理回调。
而,Java 8 中引入的 CompletableFuture 解决了这些缺陷。它通过用于任务链和回调的 thenApply() 和 thenAccept() 等方法支持非阻塞操作,并使用 exceptionally() 进行错误处理。
通过将 Future 转换为 CompletableFuture,我们可以在使用返回 Future 的 API 或库时利用这些功能。
3、逐步转换 来看看如何将 Future 转换为 CompletableFuture。
3.1、使用 ExecutorService 模拟 Future 要了解 Future 如何工作,我们首先要使用 ExecutorService 模拟异步计算。ExecutorService 是一个用于管理和调度独立线程中任务的框架。这将有助于我们理解 Future 的阻塞特性:
@Test void givenFuture_whenGet_thenBlockingCall() throws ExecutionException, InterruptedException { ExecutorService executor = Executors.newSingleThreadExecutor(); Future<String> future = executor.
1、概览 Java 虚拟机(JVM)是驱动 Java 应用程序的强大引擎。它具有高度可定制性,通过标准选项提供基本配置,通过非标准选项进行一般性性能调优,以及通过高级选项实现精确控制。
高级选项允许开发人员对性能进行微调,诊断问题,并尝试最前沿的功能。
本文将带你了解最著名的高级 JVM 选项以及如何使用它们,从而对 JVM 行为进行更精细的控制。
2、JVM 选项的分类 JVM 参数可分为三大类:
标准选项(-version、-help) 非标准选项(-X: options) 高级选项(-XX: options) 3、理解高级 JVM 选项 高级选项不仅限于基本配置,还可以设置 JVM 的更低级属性。这些选项允许我们调整性能关键参数,如垃圾回收、内存管理和运行时诊断。
其中一些高级选项也是常用的最重要的 JVM 参数。不过,由于它们可以针对特定应用场景进行微调,我们必须谨慎使用。在不清楚应用程序行为的情况下过度定制,可能会导致性能低下、崩溃或意外行为。
此外,高级 JVM 选项并不能保证为所有 JVM 实现所支持,而且可能会发生变化。因此,由于这些选项会随着 JVM 的更新而变化,有些选项可能会过时,或者在较新版本中表现不同。
例如,Java 并发标记和清理垃圾收集(CMS)算法就曾出现过这种情况,该算法在 Java 9 中被弃用,并在 Java 14 中被删除。通过关注官方文档,我们可以在发生任何变化之前及时了解情况。
4、垃圾收集调整 垃圾回收对内存管理至关重要,但也会带来影响性能的停顿。高级选项可控制垃圾回收行为,确保应用程序运行更流畅。
从 Java 9 开始,默认情况下使用垃圾优先的垃圾收集器(G1),旨在平衡吞吐量和延迟。
为了克服 G1 的延迟限制,JDK12 引入了 Shenandoah GC,可以使用 -XX:+UseShenandoahGC 选项启用它。Shenandoah 的可用性取决于 JDK 供应商和版本。
还可以根据专门的工作负载使用其他实现方式。Epsilon 垃圾收集器也非常适合用于性能调优,以检查垃圾收集是否会影响我们程序的性能。
5、内存管理 如上所述,垃圾回收是内存管理的重要组成部分,但它只是 JVM 中更大的内存管理生态系统的一部分。
要实现最佳性能,同样重要的是配置内存分配、管理堆大小以及了解堆外内存的工作原理,特别是对于内存密集型应用或有特定性能要求的系统。
回顾一下与内存管理相关的一些高级 JVM 选项: