教程

用 Java 调用 GoLang 函数

1、简介 众所周知,Java 和 Go 是两种著名的编程语言,各自在不同的领域表现出色。Java 以其可移植性和广泛的生态系统而闻名,而 Go 则以其简洁性、性能和高效的并发处理而著称。在某些情况下,将两种语言的优势结合起来,可以开发出更强大、更高效的应用程序。 本文将带你了解如何利用 Java Native Access(JNA)库来弥合两种语言之间的差距,从而在不编写任何 C 代码的情况下从 Java 中调用 Go 函数。 2、用 JNA 架起 Java 和 Go 之间的桥梁 传统上,从 Java 调用本地代码需要用 C 语言编写 Java 本地接口(JNI)代码,这会增加复杂性和开销。然而,随着 Java Native Access(JNA)库的出现,可以直接从 Java 调用本地共享库,而无需深入研究 C 代码。这种方法简化了集成过程,允许开发人员在 Java 应用程序中无缝利用 Go 的功能。 要理解这种集成是如何工作的,首先,要了解 Java Native Access(JNA)库在连接 Java 和 Go 方面的作用。具体来说,JNA 提供了一种从 Java 代码调用本地共享库函数的直接方法。通过将 Go 代码编译到共享库中并导出必要的函数,Java 可以与 Go 函数进行交互,就像它们是自己生态系统的一部分一样。 本质上,这一过程包括编写 Go 函数,将其编译成共享库,然后使用 JNA 创建映射到这些函数的相应 Java 接口。 2、项目设置 首先,设置项目环境。这包括配置集成所需的构建工具和依赖项。在本例中,我们需要以下组件: Java Development Kit(JDK):用于编译和运行 Java 代码。 Go 语言环境:用于编写和编译 Go 代码。 Java Native Access(JNA)库:作为 Maven 项目的一个依赖项包含在内。 构建工具:Maven 适用于 Java,Go 编译器适用于 Go 代码。 添加 JNA 库到 maven 依赖:

在 Java 中将 JAR 添加到 Classpath 的几种方法

1、简介 在开发 Java 项目时,我们经常依赖外部库来开发应用。 本文将带你了解把这些第三方库作为 JAR 添加到 classpath 下的不同方法。 2、在命令行中使用 -cp 或 -classpath 选项 如果我们从命令行启动程序,那么可以在命令中指定 JAR 依赖: java -cp /path/to/jar/file.jar com.example.MyClass 如上,/path/to/jar/file.jar 是 JAR 文件的路径,com.example.MyClass 是要执行的类。 还可以添加多个 jar: java -cp "lib/jar1.jar:lib/jar2.jar" com.example.MyClass 3、在命令行中使用 CLASSPATH 在某些情况下,在同一台机器上运行的多个 Java 程序可能需要用同一个 JAR。 在这种情况下,我们可以在 macOS/Linux 中设置 CLASSPATH 环境变量,而不是在每条命令中指定 classpath: export CLASSPATH=/path/to/jar/file.jar Windows 系统的设置方法如下: set CLASSPATH=C:\path\to\jar\file.jar 设置 CLASSPATH 后,我们就可以直接运行 Java 程序,而无需指定 -classpath 选项。 注意,如果我们以这种方式设置 CLASSPATH,它只对该终端会话有效。一旦终端关闭,设置就会丢失。所以,我们可以将 classpath 添加到环境变量,使其永久有效。 4、在 MANIFEST.MF 文件中指定 Classpath 当我们创建一个独立可执行的应用时,最佳实践是将所有依赖的 JAR 都打包到一个 JAR 中。

JWT parser().setSigningKey() 方法已废弃

1、概览 Java JWT(JSON Web Token)库是一个用于创建、解析和验证 JWT Token 的工具。这些 Token 可确保 API 和 Web 应用的安全,是身份验证和授权的通用解决方案。 本文将带你了解如何使用更先进的 parserBuilder.setSigningKey() 方法替换该库中已废弃的 parser().setSigningKey() 方法,以及使用 JWT Parser Builder 解析和验证 JWT 的优势。 2、签名密钥是什么? 签名密钥是 JWT Token 的关键要素。JWT Token 由三部分组成:Header、Payload 和签名。签名是通过使用秘钥或公钥/私钥对对 Header 和 Payload 进行签名来创建的。当 Token 发送到服务器时,签名密钥将用于验证签名,确保 Token 未被篡改。 在 Java JWT 库中,签名密钥是在 Token 验证过程中提供给解析器的。这可以确保 Token 的完整性,并使服务器能够信任其内容。 3、Jwts 类是什么? Jwts 类是 Java JWT 库中的一个核心工具类。它是处理 JWT 的入口点。该类为我们提供了创建、解析和验证 JWT 的各种方法。例如,当我们要解析 JWT 时,首先要调用 Jwts.parserBuilder() 方法,该方法为我们提供了一个构建器,用于构建带有必要配置选项(如设置签名密钥)的解析器。 3.1、已废弃方法概述 有了上述理论后,来看看我们要替换的已废弃的 parser().setSigningKey() 方法: JwtParser parser = Jwts.

Swagger 与 HATEOAS 的区别

1、概览 设计 REST API 通常使用两种流行的方法:Swagger 和 HATEOAS。这两种方法都旨在使 API 更友好、更易懂,但遵循不同的范式。 本文将带你了解 Swagger 和 HATEOAS 的区别以及一些常见用例。 2、Swagger 是什么? Swagger 是一套用于构建、记录和使用 REST API 的开源工具。它允许开发人员使用基于 OpenAPI Specification(OAS) 的 JSON 或 YAML 文件来描述其 API 的结构。 来看看 Swagger 的主要功能。 2.1、代码生成 有了 Swagger,我们可以自动生成交互式 API 文档、代码和客户端库。Swagger 还可以用各种编程语言创建服务器存根(Stub)和客户端 SDK,从而加快开发速度。 这是一种 API 优先的方法,它定义了需求与应用程序维护人员之间的契约。 开发人员可以使用 SwaggerHub 等工具,通过提供 Swagger 规范文件为不同的编程语言创建模板代码。 例如,来看看一个简单的 User 端点的 YAML 模板: openapi: 3.0.1 info: title: User API version: "1.0.0" description: API for managing users. paths: /users: get: summary: Get all users security: - bearerAuth: [] # 端点的安全设置 responses: '200': description: A list of users.

Hibernate 异常 “QueryParameterException: No Argument for Ordinal Parameter”

1、概览 本文将带你了解 Hibernate 出现 QueryParameterException: “no argument for ordinal parameter” 异常的原因以及解决办法。 2、理解异常 通常,Hibernate 会抛出 QueryParameterException 来提示查询参数出现问题。它表示指定的参数无效或未找到。 此外,“no argument for ordinal parameter(没有设置指定顺序的参数)” 消息表示 Hibernate 无法为定义的参数找到合适的参数。导致这种异常的常见原因是在 Hibernate 查询中忘记了为已定义的参数提供值。 3、实际案例 知道了 Hibernate 出现 QueryParameterException 异常的原因后,让我们通过一个实际的例子来看看如何重现它。 首先,来看看 Employee 实体类: @Entity public class Employee { @Id private int id; private String firstName; private String lastName; // 标准的 Getter 和 Setter } 如上,Employee 有 id、firstName 和 lastName 字段。 @Entity 注解用于指定 Employee 类是一个 JPA 实体,而 @Id 注解则标记了表示主键的字段。

解决 Java 异常:“class file has wrong version”

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.

Spring Boot 中 Circuit Breaker 与 Retry 的区别

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)).

Java 中的 Objects.requireNonNull() 指南

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()。

Spring Boot 中的结构化日志

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 等结构化日志格式通过标准化日志内容解决了这一问题。

ClickHouse 数据库简介

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.