Java

获取 Java JAR 文件中资源的路径

1、简介 在 Java 中,通常使用相对于 JAR 文件根目录的路径来访问JAR文件中的资源。 本文将带你了解获取 Java JAR 文件中资源路径的不同方法。 2、使用 Class.getResource() 方法获取资源的 URL Class.getResource() 方法提供了一种获取 JAR 文件中资源 URL 的直接方法。 该方法使用如下: @Test public void givenFile_whenClassUsed_thenGetResourcePath() { URL resourceUrl = GetPathToResourceUnitTest.class.getResource("/myFile.txt"); assertNotNull(resourceUrl); } 如上,调用 GetPathToResourceUnitTest.class 上的 getResource() 方法,并将资源文件路径 "/myFile.txt" 作为参数传递进去。然后,断言获取的 resourceUrl 不为 null,表示成功定位到资源文件。 3、使用 ClassLoader.getResource() 方法 还可以使用 ClassLoader.getResource() 方法访问 JAR 文件中的资源。在编译时不知道资源路径的情况下,该方法非常有用: @Test public void givenFile_whenClassLoaderUsed_thenGetResourcePath() { URL resourceUrl = GetPathToResourceUnitTest.class.getClassLoader().getResource("myFile.txt"); assertNotNull(resourceUrl); } 如上,使用类加载器 GetPathToResourceUnitTest.class.getClassLoader() 来获取资源文件。与前一种方法不同,该方法不依赖于类的包结构,它会在类路径的根级别搜索资源文件。 这意味着,无论资源位于项目结构中的哪个位置,它都能找到资源,从而更灵活地访问位于类包之外的资源。 4、Class.getResource() 和 ClassLoader.getResource() 的区别 Java 中的 Class.

Java 中的 OpenAI API 客户端

1、概览 随着生成式 AI 和 ChatGPT 的广泛应用,许多语言都开始提供与 OpenAI API 交互的库。Java 也不例外。 本文将带你了解 openai-java 库,它是一个开源的 OpenAI API 客户端,可以很方便地与 OpenAI API 通信。 2、依赖 首先,导入项目所需的 依赖,以下这三个模块专门用于交互的不同方面: <dependency> <groupId>com.theokanning.openai-gpt3-java</groupId> <artifactId>service</artifactId> <version>0.18.2</version> </dependency> <dependency> <groupId>com.theokanning.openai-gpt3-java</groupId> <artifactId>api</artifactId> <version>0.18.2</version> </dependency> <dependency> <groupId>com.theokanning.openai-gpt3-java</groupId> <artifactId>client</artifactId> <version>0.18.2</version> </dependency> 请注意,依赖名称中明确提到了 GPT3,但它 也适用于 GPT4。 3、Baeldung 辅导员 接下来,我们要构建一个工具,尝试根据我们最喜欢的学习平台上的文章和教程来创建课程表。虽然互联网为我们提供了无限的资源,我们几乎可以在线找到任何东西,但筛选信息却很困难。 4. OpenAI API Token 第一步是将应用连接到 OpenAI API。为此,需要提供一个 OpenAI Token,该 Token 可在 OpenAI 网站 上生成: 注意,要小心保存你的 Token,避免暴露它。为此,openai-java 示例使用了环境变量。这可能不是用于生产的最佳解决方案,但在小型实验中完全可行。 在运行时,不一定需要为机器配置环境变量;大多数 IDE 都支持在运行时为应用单独设置环境变量,例如 IntelliJ IDEA。 我们可以生成两种 Token:个人(Personal)和服务账户(Service Account)。个人 Token 不言自明。服务账户 Token 用于连接到 OpenAI 项目的机器人或应用。虽然两者都可以使用,但对于我们的目的来说,个人 Token 已经足够了。

解决 java.security.UnrecoverableKeyException: Cannot Recover Key

1、简介 本文将带你了解如 java.security.UnrecoverableKeyException 异常出现的原因以及如何解决该异常。 2、背景 在 Java 中,有一个 Keystore 的概念。它本质上是一个包含一些 secret 的文件。它可以包含证书链以及与之对应的私钥。由于证书只是一个带有公钥的 包装器,我们可以简单地说 Keystore 包含了一对非对称密钥。 通常,用密码(“password ”通 常也称为 “passphrase”)保护私钥是一种很好的做法。这不仅是 Java Keystore 的良好做法,也是网络安全的一般做法。实现这种保护的方法通常是使用对称密钥加密算法(如各种 AES 实例)对私钥和密码进行加密。 在这里对我们来说需要注意的是,Keystore 中的私钥可以使用密码进行加密,如上所述。这个特性并不是所有类型的 Keystore 都支持,例如,JKS Keystore 支持私钥密码保护,但 PKCS12 Keystore 不支持。在我们的示例中,我们需要密码保护功能,因此我们使用 JKS Keystore。 3、UnrecoverableKeyException java.security.UnrecoverableKeyException 通常发生在使用 KeyManagerFactory 时,特别是调用 init() 方法时。这是 JSSE 中的一个类,允许我们检索 KeyManager 实例。KeyManager 是一个接口,它代表了一个抽象概念,负责将我们作为客户端向对等方进行身份验证。 init() 方法需要两个参数 - 用于获取认证凭证的 Keystore 和用于私钥解密的密码。当 KeyManagerFactory 无法恢复证书链的私钥时,就会出现 java.security.UnrecoverableKeyException 异常。问题来了 - UnrecoverableKeyException 中的 “recover” 到底是什么意思?这意味着证书链的私钥无法用给定的密码解密。因此,java.security.UnrecoverableKeyException 最常见的原因是 Keystore 中的私钥密码错误。 总之,如果为 KeyManagerFactory 提供的私钥密码/口令不正确,那么 KeyManagerFactory 将无法解密密钥,因此会出现此异常。

把 Google Protobuf Timestamp 转换为 LocalDate

1、概览 Protocol Buffer (Protobuf) 是一种用于序列化结构化数据的二进制格式。它是由 Google 开发的,并且被广泛应用于跨平台和跨语言的数据通信。Protocol Buffer 使用简洁、高效的编码方案,可以将结构化数据定义为消息类型,并生成针对不同编程语言的数据访问代码。这使得在不同的系统之间传输和解析数据变得更加简单和高效。Protocol Buffer 具有广泛的支持,包括 Java、C++、Python、Golang 等编程语言。 Protobuf Timestamp 类型代表一个时间点,与任何特定时区无关。本文将带你了解如何把 Protobuf Timestamp 实例转换为 Java 的本地时间类型,如 LocalDate。 2、Maven 依赖 在 pom.xml 中添加 protobuf-java 依赖: <dependency> <groupId>com.google.protobuf</groupId> <artifactId>protobuf-java</artifactId> <version>4.26.1</version> </dependency> 该依赖提供了 Timestamp 和其他与 Protobuf 相关的类。 3、Timestamp 类 Protobuf Timestamp 类表示自 Unix 纪元以来的时间点。与时区或本地日历没有关系。 它表示的是某个时间点的秒数和纳秒数。下面是使用 Java Instant 对象计算当前时间戳的示例: Instant currentTimestamp = Instant.now(); Timestamp timestamp = Timestamp.newBuilder() .setSeconds(currentTimestamp.getEpochSecond()) .setNanos(currentTimestamp.getNano()) .build(); 如上,通过 Instant 对象计算时间戳。首先,创建一个 Instant 对象,表示给定点的日期和时间。然后,提取秒和纳秒,并将它们传递给 Timestamp 实例。 4、把 Timestamp 实例转换为 LocalDate 在将 Timestamp 转换为 LocalDate 时,必须考虑时区及其与 UTC 的相关偏移,以准确表示本地日期。要将 Timestamp 转换为 LocalDate,首先要创建一个具有特定秒和纳秒的 Timestamp 实例:

如何在 Spring Boot 中指定 logback.xml 文件的位置?

1、概览 日志记录是任何软件应用的重要组件,用于监控、调试和维护系统的健康状况。在 Spring Boot 生态系统中,Logback 作为默认的日志记录框架,提供了灵活和强大的功能。虽然 Spring Boot 简化了应用的许多方面,但有时仍然需要通过 logback.xml 配置文件来配置 Logback 以满足特定要求。 本文将会带你了解如何在 Java、Spring Boot 应用中指定 logback.xml 配置文件的位置。 2、logback.xml logback.xml 文件是 Logback 的配置文件,用于定义日志记录规则、appender 和日志格式(log format)。 默认情况下,Logback 会在 classpath 根目录中搜索该文件。这意味着将 logback.xml 文件放在 Spring Boot 项目的 src/main/resources 目录中就足够了,因为 Logback 会在运行时自动检测到它。不过,在某些情况下,自定义其位置也是必要的。 3、指定 logback.xml 位置 3.1、使用 System Properties 如果 logback.xml 配置文件不在打包后的 Jar 中,可以使用 System Properties 指定其位置。例如,在运行 Spring Boot 应用时,可以使用 JVM 参数: java -Dlogback.configurationFile=/path/to/logback.xml -jar application.jar 命令 -Dlogback.configurationFile=/path/to/logback.xml 将系统属性 logback.configurationFile 设置为指定路径,指定 Logback 使用提供的配置文件。 3.2、编程式配置 logback.

Spring Boot 启用虚拟线程

本文将带你了解什么是虚拟线程、虚拟线程是如何提高应用吞吐量的,以及如何为 Spring Boot 应用启用虚拟线程。 并发编程的演化 线程 总所周知,线程(Thread)是计算机中的最小执行单元,由操作系统直接进行调度,每个线程都有自己的执行路径和执行状态,可以独立地运行和并发执行多个任务。 线程是一种重量级的资源,线程的创建、销毁以及在多个线程之间切换都需要耗费 CPU 时间,一个系统可以同时创建、调度的线程数量有限。所以,现在应用基本上都会使用 线程池 来解决这个问题,通过池化线程,可以减少线程频繁 创建 和 销毁 的成本。 例如,Servlet 容器(Tomcat、Undertow、Jetty)的并发模型就是通过线程池,为每一个请求分配一个线程池中的线程进行处理。但是,一旦涉及到阻塞操作(IO、网络请求),当前线程就会被挂起进入等待状态,这个线程就不能去执行其他任务。这就导致了,使用传统线程池并发模型的服务器能同时处理的请求有限。 而,当代 Web 应用基本上都是 IO 密集形应用,请求中执行的业务往往涉及到与数据库进行交互、调用远程服务(Socket IO),本地磁盘文件读写等等,因此使用阻塞式线程是非常低效的。 异步非阻塞编程 为了解决传统线程在执行 IO 操作时由于阻塞导致的低效,于是,开始有了一种 响应式异步非阻塞 的编程模型。 在 Java 界,这类优秀的框架很多,如 Netty、WebFlux、Vert.x 等等。它们都提倡一个东西,那就是:异步非阻塞,只要是涉及到 IO、阻塞的地方,当前线程不会等待操作执行完毕,会立即返回去执行其他可执行的任务。这时候,就需要通过监听器(Listener),或者回调(Callback)来获得操作最终的执行结果。 以 Netty 为例,伪代码如下: Channel channel = ...; // 异步写入数据到 Socket channel.write("Hello").addListener(future -> { if(future.isSuccess()) { // 写入成功 } else { // 写入失败 } }); 其中 channel.write("Hello") 用于向 Socket 写入数据,这就是典型的阻塞操作,在传统阻塞式编程中,该方法就会阻塞,直到数据被完全写入到 Socket 中。但是,在 Netty 中,在 write 方法执行后线程就会立即返回一个 ChannelFuture 对象,不会等待写入完成,因此这个线程仍然可以继续执行其他可执行的任务。

Hello,Java 22

Java 22 是一个重大改进,我认为对于每个人来说都值得升级。其中包含了一些重要的最终发布功能,比如 Project Panama,以及许多更好的预览功能。我无法一一介绍,但我想简要介绍一些我最喜欢的功能。如果你也想尝试一下的话,代码在 这里(https://github.com/spring-tips/java22)。 我喜欢 Java 22,当然也喜欢 GraalVM,这两个版本今天都发布了!Java 当然是我们最喜欢的运行时和语言,而 GraalVM 则是一个高性能的 JDK 发行版,它支持其他语言并允许超前编译(AOT)(它们被称为 GraalVM 原生镜像)。GraalVM 包含新 Java 22 版本的所有功能,还有一些额外的实用工具,所以我一直建议下载此版本。我特别感兴趣的是 GraalVM 原生镜像功能。生成的二进制文件几乎可以立即启动,与 JRE 相比占用的内存也少得多。GraalVM 并不新鲜,但值得注意的是,Spring Boot 拥有一个强大的引擎,支持将 Spring Boot 应用转化为 GraalVM 原生镜像。 简介 我使用的是的 Java SDKMAN 包管理器。在运行 macOS 的苹果 Silicon 芯片上运行。 从 官网 下载安装 GraalVM Community Edition 的预发布版,GraalVM Community 是开源版本。(GraalVM 也有免费的商业版本,但不是开源的)。它允许你通过配置文件引导优化(PGO)等技术构建更快的本地镜像。 解压它,并使用 SDKMAN 命令行工具手动安装,如下所示: sdk install java 22.07-graalce $HOME/bin/graalvm-jdk-22+36.1/Contents/Home` 然后输入:sdk default java 22.07-graalce,打开了一个新的 shell。输入 javac --version 和 java --version 以及 native-image --version 来验证一切正常。

写给 Java / Spring Boot 开发者的 Golang 教程

我使用 Java 很多年了,我非常喜欢 Java 及其生态系统。在 Java 生态系统中,Spring Boot 是我构建 Java 应用的首选框架。 前不久,我在一个项目中使用了 Golang,起初我对它的感觉褒贬不一。但用得越多,就越喜欢它。 每当我尝试学习一种新的语言或框架时,我都会尝试将新框架/语言的概念映射到我已经熟悉的框架/语言中。这有助于我更快地理解新框架/语言的生态系统。 学习任何新知识的最好方法就是用它来构建一些东西。因此,在本文中,我将带你了解如何使用 Go 构建一个 REST API,包括配置管理、日志记录、数据库访问等各个方面。 本文并不会涉及到 Golang 的基础知识,如如何声明变量、循环、函数等。 使用的库 Go 没有类似 Spring Boot 的框架。通常,Go 开发人员喜欢使用标准库,只添加必要的库来构建应用。 本文将会使用到以下库来在 Go 中构建一个 REST API: Gin Web Framework - Web 框架 Viper - 配置库 zap - 日志库 pgx - Go 的 PostgreSQL 驱动程序和工具包 golang-migrate - 数据迁移 安装 Go 和工具 你可以从 https://go.dev/doc/install 下载并安装 Go。安装完成后,将 Go bin 目录添加到 PATH 环境变量中。 export GOPATH=$HOME/go export PATH="$PATH:$GOPATH/bin" 你可以使用 VS Code、IntelliJ IDEA Ultimate(使用 Go 插件)、GoLand 或任何其他 IDE 进行 Go 开发。

在 Java 中使用 Blowfish 加密算法

1、概览 Blowfish 加密算法最初是作为 DES 加密算法的替代方案而设计的,是当今最流行的加密算法之一。Blowfish 是一种对称的分组加密算法,由 Bruce Schneier 于 1993 年设计 。该算法的块大小为 64 位,密钥长度为 446 位,优于 DES 和 3DES 算法。 本文将带你了解如何使用 Java Cryptography Architecture(JCA)中提供的 Blowfish 算法实现加密和解密。 2、生成密钥 由于 Blowfish 是一种对称加密算法,它在加密和解密过程中使用相同的密钥。所以,接下来我们要创建一个密钥来加密文本。这个密钥应该被安全地保存,不应该在公共场合分享。 定义密钥: // 定义密钥 String secretKey = "MyKey123"; byte[] keyData = secretKey.getBytes(); //使用 Blowfish 算法创建 SecretKeySpec SecretKeySpec secretKeySpec = new SecretKeySpec(keyData, "Blowfish"); 接下来,就可以通过加密模式构建 Cipher 了: // 使用 Blowfish 算法创建 Cipher Cipher cipher = Cipher.getInstance("Blowfish"); 然后,使用加密模式(Cipher.ENCRYPT_MODE)初始化 cipher,并指定 Secret Key: // 使用 Secret Key 在加密模式下初始化 cipher cipher.

从 HttpServletRequest 获取查询参数

1、概览 后端 HTTP API 开发最重要的功能之一是解析前端传递的请求查询参数。 本文将带你了解几种直接从 HttpServletRequest 获取查询参数的方法,以及 Spring MVC 提供的一些简洁方法。 2、HttpServletRequest 中的方法 首先,来看看 HttpServletRequest 提供的与参数相关的方法。 2.1、HttpServletRequest#getQueryString() HttpServletRequest#getQueryString() 可以直接从 URL 获取查询字符串信息: @GetMapping("/api/byGetQueryString") public String byGetQueryString(HttpServletRequest request) { return request.getQueryString(); } 使用 curl 向该 API 发送一个包含多个参数的 GET 请求时,getQueryString() 方法会返回 ? 后面的所有字符: $ curl 'http://127.0.0.1:8080/spring-mvc-basics/api/byGetQueryString?username=bob&roles=admin&roles=stuff' username=bob&roles=admin&roles=stuff 如果将 @GetMapping 更改为 @RequestMapping,当使用 POST/PUT/PATCH/DELETE HTTP 方法发送请求时,返回的响应相同。也就是说 HttpServletRequest#getQueryString 始终获取到的是 URL 中的查询参数,无论 HTTP 方法是什么。因此,本教程只关注GET请求。 2.2、HttpServletRequest#getParameter(String) 为了简化参数的解析,HttpServletRequest 提供了一个 getParameter 方法,可以通过参数名获取参数值: @GetMapping("/api/byGetParameter") public String byGetParameter(HttpServletRequest request) { String username = request.