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.
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.
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.
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.
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 的值。稍后,我们将使用这两个环境变量把参数传递给这个类。
1、概览 本文将带你了解 Simple Authentication and Security Layer(简单身份验证和安全层,SASL)的基础知识,以及如何在 Java 中使用 SASL 确保通信安全。
主要是使用 SASL 来确保客户端和服务端通信的安全。
2、SASL 是什么? SASL 是互联网协议中的身份验证和数据安全框架。它旨在将互联网协议与特定的身份验证机制分离开来。
通信安全的需求是不言而喻的。从客户端和服务器通信的角度可以更好的理解这一点。客户端和服务器通常通过网络交换数据。双方必须相互信任并安全地发送数据。
2.1、SASL 的定位是什么? 在应用中,我们可能使用 SMTP 发送电子邮件,使用 LDAP 访问目录服务。但每种协议都可能支持另一种身份验证机制,如 Digest-MD5 或 Kerberos。
如果有一种方法可以让协议以声明的方式交换身份验证机制,那会怎么样呢?这正是 SASL 的用武之地。支持 SASL 的协议总是可以支持任何一种 SASL 机制。
因此,应用可以协商一个合适的机制,并采用该机制进行身份验证和安全通信。
2.2、SASL 的原理是什么? 了解了 SASL 在整个 安全通信 中的位置后,让我们来了解一下它是如何工作的。
SASL 是一种 challenge-response 框架。在这里,服务器向客户端发出 challenge,客户端根据 challenge 发送响应。challenge 和响应是任意长度的字节数组,因此可以携带任何特定机制的数据。
这种交换会持续多次,最后在服务器不再发出 exchange 时结束。
此外,客户端和服务器还可以在验证后协商一个安全层(Security Layer)。所有后续通信都可以利用这个安全层。不过,需要注意的是,有些机制可能只支持身份验证。
重要的是要明白,SASL 只提供了一个交换 exchange 和响应数据的框架。它没有提及任何有关数据本身或如何交换数据的内容。这些细节留给采用 SASL 的应用去处理。
3、Java 中的 SASL 支持 Java 中有一些 API,可支持使用 SASL 开发客户端和服务器端应用。API 并不依赖于实际机制本身。使用 Java SASL API 的应用可根据所需的安全功能选择一种机制。
1、概览 在使用 Java 和 JDBC 处理数据库时,有时我们需要将多条 SQL 语句作为单个操作来执行。这可以提高应用性能、确保原子性或更有效地管理复杂的工作流。
本文将带你了解如何使用 Statement 对象、批处理和存储过程来演示如何高效执行多个 SQL 查询。
在本教程中,我们将使用 MySQL 作为数据库。
2、JDBC 和数据库设置 2.1、Maven 依赖 首先,在 pom.xml 中添加以下依赖项,以包含 MySQL JDBC 驱动:
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId <version>8.0.33</version> </dependency> 2.2、数据库配置 在本例中,我们要创建一个名为 user_db 的 MySQL 数据库和一个名为 users 的表,并为其执行多个 insert 查询:
CREATE DATABASE user_db; USE user_db; CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL, email VARCHAR(100) NOT NULL UNIQUE ); 数据库设置完毕后,我们就可以使用 JDBC 运行多条 SQL 语句了。
1、概览 JSR 354 定义了 Java 中涉及 “货币和金钱” 的标准 API。
其目标是为 Java 生态系统添加一个灵活、可扩展的 API,使货币的处理更简单、更安全。
该 JSR 并未进入 JDK 9,但已成为未来 JDK 版本的候选。
2、设置 首先,在 pom.xml 中定义依赖:
<dependency> <groupId>org.javamoney</groupId> <artifactId>moneta</artifactId> <version>1.1</version> </dependency> 最新版的依赖可以 这里 找到。
3、JSR-354 特性 货币 API 的目标是:
提供处理和计算货币金额的 API 定义表示货币和货币金额以及货币舍入的类 处理货币汇率 处理货币和金额的格式化和解析 4、Model JSR-354 规范的主要类如下图所示:
Model 包含两个主要接口:货币单位(CurrencyUnit)和货币数量(MonetaryAmount),下文将对此进行解释。
5、CurrencyUnit CurrencyUnit 模拟了货币的最基本属性。可使用 Monetary.getCurrency 方法获取其实例:
@Test public void givenCurrencyCode_whenString_thanExist() { CurrencyUnit usd = Monetary.getCurrency("USD"); assertNotNull(usd); assertEquals(usd.getCurrencyCode(), "USD"); assertEquals(usd.getNumericCode(), 840); assertEquals(usd.getDefaultFractionDigits(), 2); } 我们创建 CurrencyUnit 时使用的是货币的字符串表示法,如果使用不存在的货币代码创建货币会导致 UnknownCurrency 异常:
1、简介 本文将带你了解如何解决 JDBC 异常:“Cannot issue data manipulation statements with executeQuery()”。
在使用 JDBC 与数据库交互时,这个异常并不常见,但这个问题很容易解决。
2、理解异常 2.1、导致这个异常的原因 当我们尝试使用 executeQuery() 方法执行 INSERT、UPDATE 或 DELETE 语句时,就会出现错误 “Cannot issue data manipulation statements with executeQuery()” 异常。
Statement 或 PreparedStatement 对象中的 executeQuery() 方法专门用于处理 SELECT 查询。如果检查一下该方法的签名,就会发现它返回的是 ResultSet 实例,其中包含从数据库中获取的记录。
使用 Connector/J 连接 MySQL 时会出现这种异常,但其他数据库也执行相同的规则。在这种情况下,它们会以不同的错误信息抛出类似的错误。
需要注意的是,在较新版本的 MySQL Connector/J 中,该错误信息已略有更新。现在是这样:
java.sql.SQLException: Statement.executeQuery() cannot issue statements that do not produce result sets. 2.2、导致该异常的场景 来看一个代码示例,以便更好地理解是什么触发了异常。如前所述,我们使用 MySQL 数据库。
首先,为示例创建一个简单的数据表:
CREATE TABLE IF NOT EXISTS users ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50), email VARCHAR(50) ) 现在我们可以尝试执行一个非 SELECT 语句的查询。
1、概览 WAR 文件是 Web 应用程序归档文件(Web Application Archive)或 Web 应用程序资源文件(Web Application Resource)的简称,用于存储 Java Web 应用程序的资源。WAR 将所有 Web 组件打包成一个单元。它包含 JAR 文件、JavaServer Page(JSP)、Java Servlet、Java 类文件、XML 文件、HTML 文件以及 Web 应用程序所需的其他资源。
本文将带你了解如何使用 CLI 调用在 WAR 文件中的类。
2、WAR 文件的结构 WAR 文件使用 .war 扩展名并打包 Web 应用程序,我们可以将其部署到任何 Servlet/JSP 容器上。
下面是一个典型的 WAR 文件结构布局示例:
META-INF/ MANIFEST.MF WEB-INF/ web.xml jsp/ helloWorld.jsp classes/ com/baeldung/*.class application.properties static/ templates/ lib/ // third party *.jar files as libs index.html 内部有一个 META-INF 目录,在 MANIFEST.MF 中保存了有关 Web 存档的有用信息。META-INF 目录是私有的,外部无法访问。