Lambda 函数处理程序是函数代码中处理事件的方法。当调用函数时,Lambda 运行处理程序方法。您的函数会一直运行,直到处理程序返回响应、退出或超时。
本页介绍如何使用 Java Lambda 函数处理程序,包括项目设置选项、命名约定和最佳实践。本页还包括 Java Lambda 函数的示例,该函数接收订单信息,生成文本文件收据,然后将此文件放入 Amazon Simple Storage Service(S3)存储桶中。有关如何在编写函数后部署函数的信息,请参阅使用 .zip 或 JAR 文件归档部署 Java Lambda 函数或使用容器镜像部署 Java Lambda 函数。
Sections
设置 Java 处理程序项目
使用 Java Lambda 函数时,该过程涉及编写代码、对其进行编译,以及将编译后的构件部署到 Lambda。您可以通过多种方式初始化 Java Lambda 项目。例如,您可以使用 Lambda 函数的 Maven Archetype
典型的 Java Lambda 函数项目遵循以下一般结构:
/project-root
└ src
└ main
└ java
└ example
└ OrderHandler.java (contains main handler)
└ <other_supporting_classes>
└ build.gradle OR pom.xml
您可以使用 Maven 或 Gradle 来构建项目和管理依赖项。
函数的主处理程序逻辑位于 src/main/java/example
目录下的 Java 文件中。在本页的示例中,我们将此文件命名为 OrderHandler.java
。除此文件外,您还可以根据需要包含其他 Java 类。将函数部署到 Lambda 时,请务必指定包含 Lambda 在调用过程中应调用的主处理程序方法的 Java 类。
示例 Java Lambda 函数代码
以下示例 Java 21 Lambda 函数代码接收有关订单的信息,生成文本文件收据,并将此文件放入 Amazon S3 存储桶中。
例 OrderHandler.java
Lambda 函数
package example;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import software.amazon.awssdk.services.s3.model.S3Exception;
import java.nio.charset.StandardCharsets;
/**
* Lambda handler for processing orders and storing receipts in S3.
*/
public class OrderHandler implements RequestHandler<OrderHandler.Order, String> {
private static final S3Client S3_CLIENT = S3Client.builder().build();
/**
* Record to model the input event.
*/
public record Order(String orderId, double amount, String item) {}
@Override
public String handleRequest(Order event, Context context) {
try {
// Access environment variables
String bucketName = System.getenv("RECEIPT_BUCKET");
if (bucketName == null || bucketName.isEmpty()) {
throw new IllegalArgumentException("RECEIPT_BUCKET environment variable is not set");
}
// Create the receipt content and key destination
String receiptContent = String.format("OrderID: %s\nAmount: $%.2f\nItem: %s",
event.orderId(), event.amount(), event.item());
String key = "receipts/" + event.orderId() + ".txt";
// Upload the receipt to S3
uploadReceiptToS3(bucketName, key, receiptContent);
context.getLogger().log("Successfully processed order " + event.orderId() +
" and stored receipt in S3 bucket " + bucketName);
return "Success";
} catch (Exception e) {
context.getLogger().log("Failed to process order: " + e.getMessage());
throw new RuntimeException(e);
}
}
private void uploadReceiptToS3(String bucketName, String key, String receiptContent) {
try {
PutObjectRequest putObjectRequest = PutObjectRequest.builder()
.bucket(bucketName)
.key(key)
.build();
// Convert the receipt content to bytes and upload to S3
S3_CLIENT.putObject(putObjectRequest, RequestBody.fromBytes(receiptContent.getBytes(StandardCharsets.UTF_8)));
} catch (S3Exception e) {
throw new RuntimeException("Failed to upload receipt to S3: " + e.awsErrorDetails().errorMessage(), e);
}
}
}
此 OrderHandler.java
文件包含以下代码部分:
-
package example
:在 Java 中,此部分可以是任何内容,但必须与项目的目录结构相匹配。在这里,我们之所以使用package example
,是因为目录结构是src/main/java/example
。 -
import
语句:使用这些语句可导入 Lambda 函数所需的 Java 类。 -
public class OrderHandler ...
:此部分定义您的 Java 类,并且必须是有效的类定义。 -
private static final S3Client S3_CLIENT ...
:此部分在类的任何方法外部初始化 S3 客户端。这会导致 Lambda 在初始化阶段运行此代码。 -
public record Order ...
:在此自定义 Java 记录中定义预期输入事件的形状。 -
public String handleRequest(Order event, Context context)
:这是包含主应用程序逻辑的主处理程序方法。 -
private void uploadReceiptToS3(...) {}
:这是主handleRequest
处理程序方法引用的帮助程序方法。
此函数随附以下 build.gradle
或 pom.xml
文件。
要使此函数正常运行,其执行角色必须允许 s3:PutObject
操作。此外,请确保您定义了 RECEIPT_BUCKET
环境变量。成功调用后,Amazon S3 存储桶应包含接收文件。
注意
此函数可能需要额外的配置设置才能成功运行而不会超时。建议配置 256 MB 的内存和 10 秒的超时时间。由于是冷启动,第一次调用可能需要额外的时间。由于重复使用执行环境,后续调用的运行速度应该会快得多。
Java 处理程序的有效类定义
为了定义您的类,aws-lambda-java-core
RequestHandler
接口是一个泛型类型,它采用两个参数:输入类型和输出类型。这两种类型都必须是对象。在此示例中,我们的 OrderHandler
类实现 RequestHandler<OrderHandler.Order, String>
。输入类型是我们在类中定义的 Order
记录,输出类型是 String
。
public class OrderHandler implements RequestHandler<OrderHandler.Order, String> {
...
}
使用此接口时,Java 运行时会将事件反序列化为具有输入类型的对象,并将输出序列化为文本。当内置序列化与输入和输出类型配合使用时,请使用此接口。
要使用您自己的序列化,您可以实现 RequestStreamHandler
接口。使用此接口,Lambda 将向您的处理程序传递输入流和输出流。处理程序从输入流读取字节,写到输出流,并返回 void。有关使用 Java 21 运行时的示例,请参阅 HandlerStream.java
如果您在 Java 函数中只使用基本类型和泛型类型(即 String
、Integer
、=List
或 Map
),则无需实现接口。例如,如果您的函数接受 Map<String, String>
输入并返回 String
,则您的类定义和处理程序签名可能如下所示:
public class ExampleHandler {
public String handleRequest(Map<String, String> input, Context context) {
...
}
}
此外,当您不实现接口时,上下文对象是可选的。例如,您的类定义和处理程序签名可能如下所示:
public class NoContextHandler {
public String handleRequest(Map<String, String> input) {
...
}
}
处理程序命名约定
对于 Java Lambda 函数,如果您要实现 RequestHandler
或 RequestStreamHandler
接口,则主处理程序方法必须命名为 handleRequest
。此外,请在 handleRequest
方法上方添加 @Override
标签。将函数部署到 Lambda 时,请按以下格式在函数配置中指定主处理程序:
-
<package>
.<Class>
:例如,example.OrderHandler
。
对于未实现 RequestHandler
或 RequestStreamHandler
接口的 Java Lambda 函数,您可以使用任何处理程序名称。将函数部署到 Lambda 时,请按以下格式在函数配置中指定主处理程序:
-
<package>
.<Class>
::<handler_method_name>
:例如,example.Handler::mainHandler
。
定义和访问输入事件对象
JSON 是 Lambda 函数最常用且最标准的输入格式。在此示例中,该函数需要类似于下方的输入:
{
"order_id": "12345",
"amount": 199.99,
"item": "Wireless Headphones"
}
在使用 Java 17 或更高版本的 Lambda 函数时,您可以将预期输入事件的形状定义为 Java 记录。在此示例中,我们在 OrderHandler
类中定义了一条记录来表示 Order
对象:
public record Order(String orderId, double amount, String item) {}
此记录与预期的输入形状相匹配。定义记录后,您可以编写一个处理程序签名,接收符合记录定义的 JSON 输入。Java 运行时会自动将此 JSON 反序列化为 Java 对象。然后,您可以访问该对象的字段。例如,event.orderId
从原始输入中检索 orderId
的值。
注意
Java 记录是 Java 17 运行时及更高版本独有的一项功能。在所有 Java 运行时系统中,您可以使用类来表示事件数据。在这种情况下,您可以使用如 jackson
其他输入事件类型
Java Lambda 函数有许多可能的输入事件:
-
Integer
Long
、Double
、等 – 事件是一个没有其他格式的数字(例如,3.5
)。Java 运行时将该值转换为指定类型的对象。 -
String
– 事件是一个 JSON 字符串,包括引号(例如,“My string”
)。运行时将该值转换为String
对象(不带引号)。 -
List<Integer>
List<String>
、List<Object>
、等 – 事件是一个 JSON 数组。运行时将其反序列化为指定类型或接口的对象。 -
InputStream
– 事件是任何 JSON 类型。运行时将文档的字节流传递给处理程序而不进行修改。您可以对输入进行反序列化并将输出写到输出流。 -
库类型:对于其他 AWS 服务发送的事件,请使用 aws-lambda-java-events
库中的类型。例如,如果 Amazon Simple Queue Service(SQS)调用了您的 Lambda 函数,则使用 SQSEvent
对象作为输入。
访问和使用 Lambda 上下文对象
Lambda 上下文对象包含有关调用、函数和执行环境的信息。在此示例中,上下文对象的类型为 com.amazonaws.services.lambda.runtime.Context
,是主处理程序函数的第二个参数。
public String handleRequest(Order event, Context context) {
...
}
如果您的类实现了 RequestHandler
如果您使用 AWS SDK 调用其他服务,则多个关键区域中均需要上下文对象。例如,要为 Amazon CloudWatch 生成函数日志,您可以使用 context.getLogger()
方法获取用于日志记录的 LambdaLogger
对象。在此示例中,如果由于任何原因处理失败,我们可以使用记录器记录错误消息:
context.getLogger().log("Failed to process order: " + e.getMessage());
除了日志记录之外,您还可以使用上下文对象进行函数监控。有关上下文对象的更多信息,请参阅使用 Lambda 上下文对象检索 Java 函数信息。
在处理程序中使用 AWS SDK for Java v2
通常,您将使用 Lambda 函数与其他 AWS 资源进行交互或对其进行更新。与此类资源最简单的交互方法是使用 AWS SDK for Java v2。
注意
AWS SDK for Java(v1)处于维护模式,其支持的终止日期为 2025 年 12 月 31 日。建议您今后仅使用 AWS SDK for Java v2。
要向函数添加 SDK 依赖项,请将其添加到 build.gradle
(Gradle)或 pom.xml
文件(Maven)。建议仅添加函数所需的库。在上述示例代码中,我们使用了 software.amazon.awssdk.services.s3
库。在 Gradle 中,您可以通过在 build.gradle
的依赖项部分添加以下行来添加此依赖项:
implementation 'software.amazon.awssdk:s3:2.28.29'
在 Maven 中,在 pom.xml
的 <dependencies>
部分添加以下行:
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
<version>2.28.29</version>
</dependency>
注意
这可能不是最新版本的 SDK。为您的应用程序选择合适的 SDK 版本。
然后,直接在 Java 类中导入依赖项:
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import software.amazon.awssdk.services.s3.model.S3Exception;
示例代码随即按如下方法初始化 Amazon S3 客户端:
private static final S3Client S3_CLIENT = S3Client.builder().build();
在此示例中,我们在主处理程序函数之外初始化了 Amazon S3 客户端,以免每次调用函数时都必须对其进行初始化。初始化 SDK 客户端后,您可以使用该客户端与其他 AWS 服务进行交互。示例代码按如下方式调用 Amazon S3 PutObject
API:
PutObjectRequest putObjectRequest = PutObjectRequest.builder()
.bucket(bucketName)
.key(key)
.build();
// Convert the receipt content to bytes and upload to S3
S3_CLIENT.putObject(putObjectRequest, RequestBody.fromBytes(receiptContent.getBytes(StandardCharsets.UTF_8)));
评估环境变量
在处理程序代码中,您可以使用 System.getenv()
方法引用任何环境变量。在此示例中,我们使用以下代码行引用已定义的 RECEIPT_BUCKET
环境变量:
String bucketName = System.getenv("RECEIPT_BUCKET");
if (bucketName == null || bucketName.isEmpty()) {
throw new IllegalArgumentException("RECEIPT_BUCKET environment variable is not set");
}
使用全局状态
在首次调用您的函数之前,Lambda 会在初始化阶段运行您的静态代码和类构造函数。初始化期间创建的资源在两次调用之间保留在内存中,因此您可以避免每次调用函数时都必须创建这些资源的情况。
在示例代码中,S3 客户端初始化代码位于主处理程序方法之外。运行时会在函数处理其第一个事件之前初始化客户端,这可能会导致更长的处理时间。后续事件的处理速度则要快得多,因为 Lambda 不需要再次初始化客户端。
Java Lambda 函数的代码最佳实践
在构建 Lambda 函数时,请遵循以下列表中的指南,采用最佳编码实践:
-
从核心逻辑中分离 Lambda 处理程序。这样您可以创建更容易进行单元测试的函数。
-
控制函数部署程序包中的依赖关系。AWS Lambda 执行环境包含许多库。Lambda 会定期更新这些库,以支持最新的功能组合和安全更新。这些更新可能会使 Lambda 函数的行为发生细微变化。要完全控制您的函数所用的依赖项,请使用部署程序包来打包所有依赖项。
-
将依赖关系的复杂性降至最低。首选在执行环境启动时可以快速加载的更简单的框架。例如,首选更简单的 Java 依赖关系注入 (IoC) 框架,如 Dagger
或 Guice ,而不是更复杂的 Spring Framework 。 -
将部署程序包大小精简为只包含运行时必要的部分。这样会减少调用前下载和解压缩部署程序包所需的时间。对于用 Java 编写的函数,请勿将整个 AWS SDK 库作为部署包的一部分上传,而是要根据所需的模块有选择地挑选软件开发工具包中的组件(例如 DynamoDB、Simple Storage Service (Amazon S3) 软件开发工具包模块和 Lambda 核心库
)。
-
利用执行环境重用来提高函数性能。连接软件开发工具包 (SDK) 客户端和函数处理程序之外的数据库,并在
/tmp
目录中本地缓存静态资产。由函数的同一实例处理的后续调用可重用这些资源。这样就可以通过缩短函数运行时间来节省成本。为了避免调用之间潜在的数据泄露,请不要使用执行环境来存储用户数据、事件或其他具有安全影响的信息。如果您的函数依赖于无法存储在处理程序的内存中的可变状态,请考虑为每个用户创建单独的函数或单独的函数版本。
-
使用 keep-alive 指令来维护持久连接。Lambda 会随着时间的推移清除空闲连接。在调用函数时尝试重用空闲连接会导致连接错误。要维护您的持久连接,请使用与运行时关联的 keep-alive 指令。有关示例,请参阅在 Node.js 中通过 Keep-Alive 重用连接。
-
使用环境变量将操作参数传递给函数。例如,您在写入 Amazon S3 存储桶时,不应对要写入的存储桶名称进行硬编码,而应将存储桶名称配置为环境变量。
-
避免在 Lambda 函数中使用递归调用,在这种情况下,函数会调用自己或启动可能再次调用该函数的进程。这可能会导致意想不到的函数调用量和升级成本。如果您看到意外的调用量,请立即将函数保留并发设置为
0
来限制对函数的所有调用,同时更新代码。 -
Lambda 函数代码中不要使用非正式的非公有 API。对于 AWS Lambda 托管式运行时,Lambda 会定期为 Lambda 的内部 API 应用安全性和功能更新。这些内部 API 更新可能不能向后兼容,会导致意外后果,例如,假设您的函数依赖于这些非公有 API,则调用会失败。请参阅 API 参考以查看公开发布的 API 列表。
-
编写幂等代码。为您的函数编写幂等代码可确保以相同的方式处理重复事件。您的代码应该正确验证事件并优雅地处理重复事件。有关更多信息,请参阅如何使我的 Lambda 函数具有幂等性?
。
-
避免使用 Java DNS 缓存。Lambda 函数已经缓存了 DNS 响应。如果您使用了另一个 DNS 缓存,则可能会出现连接超时。
java.util.logging.Logger
类可以间接启用 JVM DNS 缓存。要覆盖默认设置,请在初始化logger
之前将 networkaddress.cache.ttl设置为 0。示例: public class MyHandler { // first set TTL property static{ java.security.Security.setProperty("networkaddress.cache.ttl" , "0"); } // then instantiate logger var logger = org.apache.logging.log4j.LogManager.getLogger(MyHandler.class); }
-
将依赖关系
.jar
文件置于单独的 /lib 目录中,可减少 Lambda 解压缩部署程序包(用 Java 编写)所需的时间。这样比将函数的所有代码置于具有大量.class
文件的同一 jar 中要快。有关说明,请参阅使用 .zip 或 JAR 文件归档部署 Java Lambda 函数: