Java

Java 关闭 Scanner 的最佳实践

1、简介 当使用 Java 的 Scanner 类读取 System.in(标准输入)输入时,部分 IDE 会提示可能存在资源泄漏。 例如,若未显式关闭 Scanner,可能收到警告:“Resource leak: ‘scanner’ is never closed”。但关闭关联 System.in 的 Scanner 需谨慎处理,以避免意外问题。 本文将带你了解关闭 Scanner 的重要性及在 Java 中的正确操作方式。 2、理解 IDE 提示的异常 在 Java 中,使用文件、网络连接(Socket)和输入流等资源后应关闭资源以释放系统内存。Scanner 类从文件或 System.in 等源读取原始值和字符串输入,由于实现了 Closeable 接口,因此它持有的资源需要在不使用时及时释放。 部分 IDE(如 Eclipse 和 Visual Studio Code)若检测到未正确关闭 Scanner 对象,会显示如下资源泄漏警告: 3、通过 close() 方法关闭 Scanner 在 Java 中,可通过 close() 方法关闭 Scanner 以释放系统资源,这在读取文件或 System.in 时尤为重要。 建议在 finally 代码块中关闭 Scanner,确保即使发生异常也能正确释放资源,避免泄漏: @Test void givenUserName_whenGetGreetingMessage_thenReturnsWelcomeMessage() { String input = "Anees\n"; ByteArrayInputStream inputStream = new ByteArrayInputStream(input.

过滤 HTML 代码以防止 XSS 攻击的几种方案

1、简介 跨站脚本攻击(XSS)是一种安全漏洞,允许攻击者向网页应用中注入恶意脚本。这些脚本能在用户浏览器中执行,导致数据窃取、会话劫持或页面篡改等风险。 本文将带你了解如何在 Java 应用中过滤 HTML 输入以防止 XSS 攻击。 2、项目设置 首先,需要在 pom.xml 中添加 OWASP Java HTML sanitizer 库: <dependency> <groupId>com.googlecode.owasp-java-html-sanitizer</groupId> <artifactId>owasp-java-html-sanitizer</artifactId> <version>20240325.1</version> </dependency> 该库提供高度可配置的策略驱动式 Sanitizer(净化器),既能处理复杂 HTML 内容,又能有效防御 XSS 攻击。 3、实现基础版 OWASP HTML 过滤 添加依赖后,我们定义一个工具方法,利用该库清理可能非法的 HTML 输入。 以下创建了一个可复用的工具类,采用默认策略(仅允许基础格式化标签)实现 HTML 过滤: public class HtmlSanitizerUtil { private static final PolicyFactory POLICY = Sanitizers.FORMATTING.and(Sanitizers.LINKS); public static String sanitize(String htmlContent) { return POLICY.sanitize(htmlContent); } } 上例中,我们通过组合两个内置 Sanitizer(Sanitizers.FORMATTING 和 Sanitizers.LINKS)配置过滤策略。该策略允许基础 HTML 格式化标签(如 <b>、<i>、<u>)以及通过 <a> 标签实现的超链接。随后 sanitize() 方法将此策略应用于输入字符串,返回过滤后的 HTML 内容。

使用 PostgreSQL 提供的 LISTEN/NOTIFY 机制实现事件驱动

1、简介 本文将带你了解 PostgreSQL 中的 LISTEN 和 NOTIFY 命令,包括其功能、使用方法以及在应用中的实际应用。 2、LISTEN 和 NOTIFY 是什么? PostgreSQL 支持使用 LISTEN 和 NOTIFY 命令在服务器和连接的客户端之间进行异步通信。这些特定于 PostgreSQL 的扩展使我们能够将数据库用作一个简单的 MQ 系统,允许我们从数据库中生成客户端可以做出反应的事件。这在很多方面都很有用,如实时仪表盘、缓存失效、数据审计等。 2.1、监听通知 使用 LISTEN 命令注册接收事件的监听,需指定目标频道名称: postgres=# LISTEN my_channel; LISTEN 完成注册后,该连接即可接收该频道的异步事件通知。 所有注册监听的连接都会收到通知,因此该系统实际采用广播机制而非单播。这意味着可通过此方式轻松向所有客户端同步数据库内发生的事件。 注意:若使用 psql 工具,不会自动接收通知。需重新执行 LISTEN 命令,此时会显示自上次监听后触发的所有通知: postgres=# LISTEN my_channel; LISTEN ..... postgres=# LISTEN my_channel; LISTEN Asynchronous notification "my_channel" with payload "Hello, World!" received from server process with PID 66. 此处可见某连接触发了 Payload 为 “Hello, world!” 的事件,监听连接已收到通知。 虽然监听器数量无硬性上限,但每个监听器需保持数据库连接开启以接收通知,因此实际受限于最大连接数限制。此外,每个监听器都会占用资源,过多监听器可能导致性能问题。 2.2、发布通知 了解如何监听事件后,还需掌握如何触发事件。使用 NOTIFY 命令可触发事件,需指定频道名称和发送的消息:

Java Class-File API 指南

1、简介 Java Class-File(类文件) API 是在 JEP-484 中引入的,是 Java 24 的一部分。它旨在创建一个接口,允许在不依赖于 ASM 库的传统 JDK 内部复制实现的情况下进行类文件处理。 本文将带你了解如何从头开始构建类文件,以及如何使用类文件 API 将一个类文件转换为另一个类文件。 2、 Class-File 核心的 API Class-File 有三个核心元素: 元素 - 代表代码的一部分,如变量、指令、方法或类。此外,一个元素可能包含其他元素。例如,一个类元素可能包含方法元素,而方法元素又包括变量或指令元素。 构建器 -(如方法构建器和代码构建器)用于创建每种类型的元素。 转换函数 - 可用于使用构建器将元素转换为其他元素。 3、生成类文件 使用 MethodBuilder 和 CodeBuilder 类生成类文件。 3.1、示例方法 先来看一个简单的代码段,它根据员工的角色和基本工资计算员工的工资: public double calculateAnnualBonus(double baseSalary, String role) { if (role.equals("sales")) { return baseSalary * 0.35; } if (role.equals("engineer")) { return baseSalary * 0.25; } return baseSalary * 0.15; } 3.2、使用 MethodBuilder 和 CodeBuilder 我们可以使用 MethodBuilder 和 CodeBuilder 类来生成与 calculateAnnualBonus() 功能相同的方法

在 Protobuf 中使用 Map

1、简介 ProtoBuf 为结构化数据的序列化提供了一种快速高效的方式,它是 JSON 的紧凑型高性能替代品。 与基于文本并需要解析的 JSON 不同,protobuf 可生成适用于多种语言的优化代码。这使得在不同系统间发送结构化数据变得更加容易。 使用 protobuf,只需在 .proto 文件中定义一次数据结构。然后,就可以使用生成的代码来处理跨流和跨平台的数据传输。在处理类型化、结构化数据时,它是理想的选择,尤其是当 Payload 比较大的时候。 Protobuf 支持字符串、整数、布尔值和浮点数等常见类型。它们还能很好地与 List 和 Map 配合使用,使复杂的数据易于管理。 本文将带你了解如何在 protobuf 中使用 Map。 2、了解 Protobuf 中的 Map 2.1、Map 是什么? Map 是一种键值数据结构,类似于字典。 每个键都映射到一个特定的值,从而使查找快速高效。类似于 DNS 系统:每个域名都指向一个 IP 地址。 2.2、定义 Map 的语法 Protobuf 3 对 Map 提供了开箱即用的支持。 示例如下: message Dictionary { map<string, string> pairs = 1; } 使用 map<key_type, value_type> 定义字段。Key 必须是 scalar 类型,如 string、int32 或 bool。值可以是任何有效的 protobuf 类型 - scalar、enum 或其他消息。

使用 Java Compiler API 编译 Java 代码

1、简介 在 Java 开发中,编译是防止语法错误、类型不匹配和其他可能导致项目失败的问题的第一道防线。传统的工作流程依赖于手动编译,而现代应用程序则需要动态编译检查。例如: 实时验证学生提交的代码的教育平台 在部署前对生成的代码片段进行编译的 CI/CD 流水线 动态编译用户自定义逻辑的低代码工具 热代码重载系统可即时重载开发人员所做的更改 创建 Java 插件 Java Compiler API 允许在 Java 应用程序中以编程式编译代码,从而实现上述应用场景。像 LeetCode 或 Codecademy 等平台可即时验证用户提交的代码。当用户点击 “运行” 时,后台会使用编译器 API 等工具编译代码段、检查错误并在沙盒环境中执行。程序化编译为这一即时反馈循环提供了动力。 本文将带你了解 Java Compiler 这一强大的工具。 2、Java Compiler API 概览 Java Compiler API 位于 javax.tools 包内,提供对 Java 编译器的编程式访问。该 API 对于需要在运行时验证或执行代码的动态编译任务至关重要。 Compiler API 的主要组件包括: JavaCompiler:启动编译任务的主编译器实例 JavaFileObject:代表 Java 源文件或类文件,可以是内存文件,也可以是基于文件的文件 StandardJavaFileManager:编译过程中管理输入和输出文件 DiagnosticCollector:捕获错误和警告等编译诊断信息 这些组件协同工作,可在 Java 应用程序中实现灵活高效的动态编译。 3、实现编译 JDK 环境默认提供 Compiler API,无需任何外部依赖。 现在,来看看如何编译内存中的 Java 代码。 3.1、在内存中创建源码 首先在内存中创建一个 Java 源码类: // 继承自 SimpleJavaFileObject public class InMemoryJavaFile extends SimpleJavaFileObject { private final String code; protected InMemoryJavaFile(String name, String code) { super(URI.

如何在后台运行 Java 程序?

1、简介 有时,我们需要在后台运行 Java 程序,即在 SSH 终端关闭后还能继续运行。 2、将 Java 作为后台进程运行 在后台运行 Java 程序的最简单方法之一是在 shell 脚本中使用 & 操作符: #!/bin/sh java -jar /web/server.jar & echo $! > startupApp.pid 最后的 & 可确保进程在后台运行,而 echo $! > startupApp.pid 的作用是获取最后执行的后台命令的 PID(进程 ID),并且重定向输出到 startupApp.pid 文件中。该 PID 可唯一标识运行中的进程,以后可用于进程管理任务,如监控或停止进程。 不过,这种方法有一个缺点。如果我们断开了 SSH 会话,进程仍可能被终止,这会导致后台程序意外停止。 此外,由于进程不是作为服务来管理的,因此更难对其进行正确控制。我们无法像使用 systemd 等正规服务管理器那样轻松查看日志或启动/停止进程。 3、利用 Nohup 保活进程 如果想在断开 SSH 会话后还让后台进程继续执行,那么 nohup 就可以解决这个问题。nohup(不挂起)可以让进程在关闭会话或终端后继续运行。 nohup 用法如下: nohup java -jar /web/server.jar > output.log 2>&1 & echo $! > startupApp.pid nohup 会让进程继续运行,即使关闭了终端会话。> output.log 2>&1 表示将标准输出和错误输出重定向到一个名为 output.

解决 DateTimeParseException: “Unable to obtain LocalDateTime from TemporalAccessor” 异常

1、概览 在 Java 中使用 java.time 包处理日期和时间非常高效,但有时我们可能会遇到 DateTimeParseException 异常,提示 “Unable to obtain LocalDateTime from TemporalAccessor(无法从 TemporalAccessor 获取 LocalDateTime)”。出现这种问题的原因通常是预期的日期时间格式与实际输入不兼容。 本文将带你了解出现该异常的原因以及解决办法。 2、理解异常 当 Java 的日期时间解析器无法从 TemporalAccessor(如 LocalDate、ZonedDateTime 或 OffsetDateTime)中提取有效的 LocalDateTime 对象时,就会出现 “Unable to obtain LocalDateTime from TemporalAccessor” 异常。根本原因通常是输入字符串格式不当或不完整。 LocalDateTime 需要 日期 和 时间 两个部分。如果输入字符串缺少所需的部分或不符合预期的格式,解析过程就会失败,从而产生此异常。很多人认为 Java 可以自动推断缺少的时间值,但事实并非如此。 示例如下,解析一个日期字符串为 LocalDateTime 的错误例子: public static void main(String[] args) { String dateTimeStr = "20250327"; // 只有日期,没时间 DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd"); LocalDateTime localDateTime = LocalDateTime.parse(dateTimeStr, formatter); } 执行该代码时,会出现以下异常: java.time.format.DateTimeParseException: Text '20250327' could not be parsed: Unable to obtain LocalDateTime from TemporalAccessor: {},ISO resolved to 2025-03-27 of type java.

Java 调用 Google 翻译 API

1、简介 本文将带你了解如何将 Google Translate API 集成到 Java 应用中,谷歌的翻译服务支持 100 多种语言,通过它的 API,我们可以轻松构建一个可以执行实时语言翻译的应用。 2、添加 SDK 依赖 首先,在 pom.xml 文件中添加 Google Cloud Translate 依赖: <dependency> <groupId>com.google.cloud</groupId> <artifactId>google-cloud-translate</artifactId> </dependency> Google Cloud Translate 提供了与 API 交互的简易接口。它会处理我们的应用与 Google 翻译服务之间的所有通信。 3、初始化 Translate 客户端 依赖添加后,下一步就是在 Java 应用中初始化 Translate 客户端。这需要使用从 Google Cloud Console 获取的服务帐户 JSON 文件进行身份验证。这种方法更安全,建议用于服务器端应用。 接下来,需要确保服务帐户已启用云翻译 API 并分配给 Google Cloud 中的云翻译 API 用户角色。 然后,通过 TranslateOptions 设置 API 凭证等配置,然后初始化 Translate 客户端: class Translator { static { initializeTranslateClient(); } public static void initializeTranslateClient() { if (translate == null) { try { GoogleCredentials credentials = GoogleCredentials.

环境变量 JDK_JAVA_OPTIONS 与 JAVA_TOOL_OPTIONS 之间有什么区别?

1、概览 在配置 Java 应用时,我们经常需要在不修改脚本的情况下传递 JVM 选项。 我们可以使用环境变量 JDK_JAVA_OPTIONS 或 JAVA_TOOL_OPTIONS,而不是每次运行 java 命令时都手动添加标记。这两个环境变量的作用相同:动态传递 JVM 选项,但它们的工作方式不同。 本文将带你了解它们的区别、何时使用每种配置以及有效管理 JVM 配置的最佳实践。 2、JDK_JAVA_OPTIONS 和 JAVA_TOOL_OPTIONS 是什么? 这两个环境变量都允许我们在全局范围内指定 JVM 选项,从而省去了每次执行 JDK 工具(如 java、javac、javadoc 等)时修改选项的麻烦。 JAVA_TOOL_OPTIONS 在 Java 5 中引入。它们的行为和目的截然不同。 在深入了解每个环境变量的功能之前,先创建一个简单的 Java 源码文件: package com.baeldung; /** * 一个用于打印某些变量值的简单类 */ public class TestEnvVar { public static void main (String[] args){ System.out.println("var1 = '" + System.getProperty("var1") + "'"); System.out.println("var2 = '" + System.getProperty("var2") + "'"); } } 上面的代码非常简单。我们的 main() 方法会打印出 var1 和 var2 的值。稍后,我们将使用这两个环境变量把参数传递给这个类。