View a markdown version of this page

在 Java 中定義 Lambda 函數處理常式 - AWS Lambda

本文為英文版的機器翻譯版本,如內容有任何歧義或不一致之處,概以英文版為準。

在 Java 中定義 Lambda 函數處理常式

Lambda 函數處理常式是您的函數程式碼中處理事件的方法。當有人呼叫您的函數時,Lambda 會執行處理常式方法。函數會執行,直到處理常式傳回回應、結束或逾時為止。

本頁介紹了如何在 Java 中使用 Lambda 函式處理常式,包括專案設定選項、命名慣例及最佳實務。本頁還提供了一個 Java Lambda 函式範例,該函式會取得訂單的相關資訊、產生文字檔案收據,並將該檔案放入 Amazon Simple Storage Service (Amazon S3) 儲存貯體。如需編寫函數後如何部署函數的詳細資訊,請參閱使用 .zip 或 JAR 封存檔部署 Java Lambda 函數使用容器映像部署 Java Lambda 函數

設定 Java 處理常式專案

使用 Java 開發 Lambda 函式時,流程涉及編寫程式碼、編譯程式碼,以及將編譯後的成品部署至 Lambda。可以透過各種方式初始化 Java Lambda 專案。例如,您可以使用適用於 Lambda 函數的 Maven Archetype、CLI sam init AWS SAM 命令,或甚至是您偏好 IDE 中的標準 Java 專案設定等工具,例如 IntelliJ IDEA 或 Visual Studio Code。 https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-init.html或者,您可以手動建立所需的檔案結構。

一個典型的 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.gradlepom.xml 檔案。

build.gradle
plugins { id 'java' } repositories { mavenCentral() } dependencies { implementation 'com.amazonaws:aws-lambda-java-core:1.2.3' implementation 'software.amazon.awssdk:s3:2.28.29' implementation 'org.slf4j:slf4j-nop:2.0.16' } task buildZip(type: Zip) { from compileJava from processResources into('lib') { from configurations.runtimeClasspath } } java { sourceCompatibility = JavaVersion.VERSION_21 targetCompatibility = JavaVersion.VERSION_21 } build.dependsOn buildZip
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>example-java</artifactId> <packaging>jar</packaging> <version>1.0-SNAPSHOT</version> <name>example-java-function</name> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>21</maven.compiler.source> <maven.compiler.target>21</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-lambda-java-core</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>s3</artifactId> <version>2.28.29</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-nop</artifactId> <version>2.0.16</version> </dependency> </dependencies> <build> <plugins> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>3.5.2</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.4.1</version> <configuration> <createDependencyReducedPom>false</createDependencyReducedPom> <filters> <filter> <artifact>*:*</artifact> <excludes> <exclude>META-INF/*</exclude> <exclude>META-INF/versions/**</exclude> </excludes> </filter> </filters> </configuration> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.13.0</version> <configuration> <release>21</release> </configuration> </plugin> </plugins> </build> </project>

若要讓此函式正常運作,其執行角色必須允許 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 函數 中使用基本和一般類型 (即 StringList、、 IntegerMap),則不需要實作 界面。例如,如果您的函數接受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 函式,如果您實作 RequestHandlerRequestStreamHandler 介面,則主要處理常式方法必須命名為 handleRequest。此外,請在 handleRequest 方法上方加上 @Override 標籤。當您將函數部署到 Lambda 時,請以下列格式在函數的組態中指定主要處理常式:

  • <package>.<Class> – 例如 example.OrderHandler

對於 Java 中未實作 RequestHandlerRequestStreamHandler 介面的 Lambda 函數,您可以使用處理常式的任何名稱。當您將函數部署到 Lambda 時,請以下列格式在函數的組態中指定主要處理常式:

  • <package>.<Class>::<handler_method_name> – 例如 example.Handler::mainHandler

定義和存取輸入事件物件

JSON 是 Lambda 函數最常見的標準輸入格式。在此範例中,函數預期輸入類似以下內容:

{ "orderId": "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 物件。然後,您可以存取該物件的欄位。例如,使用 orderId 可以從原始輸入擷取 event.orderId 的值。

注意

僅 Java 17 及更新版本的執行時期支援 Java 記錄功能。在所有 Java 執行期中,可以使用類別來表示事件資料。在此類案例中,您可以使用 jackson 之類的程式庫來反序列化 JSON 輸入。

其他輸入事件類型

Java 中的 Lambda 函式支援多種可能的輸入事件:

  • IntegerDoubleLong和其他數值類型 – 事件是沒有其他格式的數字,例如 3.5。Java 執行時期會將值轉換為指定類型的物件。

  • String - 事件是 JSON 字串,包括引號,例如 "My string"。執行時期會將值轉換為不含引號的 String 物件。

  • List<Integer>List<Object>List<String>和類似類型 – 事件是 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 RequestStreamHandler 介面,則內容物件為必要引數。否則,內容物件為選用引數。如需有關可接受的有效處理常式簽章的詳細資訊,請參閱 Java 處理常式的有效類別定義

如果您使用 AWS SDK 呼叫其他 服務,則幾個關鍵區域需要內容物件。例如,若要為 Amazon CloudWatch 生成函式日誌,您可以使用 context.getLogger() 方法取得用於記錄的 LambdaLogger 物件。在此範例中,如果處理因任何原因失敗,我們可以使用記錄器來記錄錯誤訊息:

context.getLogger().log("Failed to process order: " + e.getMessage());

除了記錄日誌之外,您還可以將內容物件用於函式監控。如需內容物件的詳細資訊,請參閱使用 Lambda 內容物件擷取 Java 函數資訊

在處理常式中使用適用於 Java 的 AWS SDK v2

通常,您將使用 Lambda 函數與其他 AWS 資源互動或進行更新。與這些資源互動的最簡單方法是使用適用於 Java 的 AWS SDK v2。

注意

適用於 Java 的 AWS SDK (v1) 處於維護模式,將於 2025 年 12 月 31 日end-of-support。我們建議您未來僅使用適用於 Java v2 的 AWS 開發套件。

若要將 SDK 相依項新增至函式,請在適用於 Gradle 的 build.gradle 檔案或適用於 Maven 的 pom.xml 檔案中納入這些相依項。建議僅新增函式所需的程式庫。在先前的範例程式碼中,我們使用了 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 用戶端初始化程式碼位於主要處理常式方法外部。執行時期會在函式處理第一個事件之前初始化用戶端,且用戶端在所有調用中均可供重複使用。

Java Lambda 函數的程式碼最佳實務

請遵循下列清單中的準則,在建置 Lambda 函數時使用最佳編碼實務:

  • 區隔 Lambda 處理常式與您的核心邏輯。透過此方法,您可以建立更可供單位測試的函數。

  • 控制函數部署套件內的相依性。 AWS Lambda 執行環境包含多個程式庫。為了啟用最新的一組功能和安全性更新,Lambda 會定期更新這些程式庫。這些更新可能會對 Lambda 函數的行為帶來細微的變更。若要完全掌控您函式所使用的相依性,請利用部署套件封裝您的所有相依性。

  • 最小化依存項目的複雜性。偏好更簡易的框架,其可快速在執行環境啟動時載入。例如,偏好更簡易的 Java 相依性置入 (IoC) 架構如 DaggerGuice,勝於複雜的架構如 Spring Framework

  • 將部署套件最小化至執行時間所必要的套件大小。這可減少在叫用之前下載和解壓縮部署套件所需的時間。對於以 Java 撰寫的函數,請避免上傳整個 AWS SDK 程式庫做為部署套件的一部分。相反地,選擇性地依賴接取所需 SDK 元件的模組 (例如,GitHub 網站上的 DynamoDB、Amazon S3 SDK 模組和 Lambda 核心程式庫)。

請利用執行環境重新使用來改看函式的效能。在函式處理常式之外初始化 SDK 用戶端和資料庫連線,並在本機快取 /tmp 目錄中的靜態資產。由您函式的相同執行個體處理的後續叫用可以重複使用這些資源。這可藉由減少函數執行時間來節省成本。

若要避免叫用間洩漏潛在資料,請不要使用執行環境來儲存使用者資料、事件,或其他牽涉安全性的資訊。如果您的函式依賴無法存放在處理常式內記憶體中的可變狀態,請考慮為每個使用者建立個別函式或個別函式版本。

使用 Keep-Alive 指令維持持續連線的狀態。Lambda 會隨著時間的推移清除閒置連線。叫用函數時嘗試重複使用閒置連線將導致連線錯誤。若要維護持續連線,請使用與執行階段相關聯的 keep-alive (保持啟用) 指令。如需範例,請參閱在 Node.js 中重複使用 Keep-Alive 的連線

使用環境變數將操作參數傳遞給您的函數。例如,如果您正在寫入到 Amazon S3 儲存貯體,而非對您正在寫入的儲存貯體名稱進行硬式編碼,請將儲存貯體名稱設定為環境變數。

避免在 Lambda 函數中使用遞迴調用,其中函數會調用自己或啟動可能再次調用函數的程序。這會導致意外的函式呼叫量與升高的成本。若您看到意外的調用數量,當更新程式碼時,請立刻將函數的預留並行設為 0,以調節對函數的所有調用。

請勿在您的 Lambda 函數程式碼中使用未記錄的非公有 API。對於 AWS Lambda 受管執行期,Lambda 會定期將安全性和功能更新套用至 Lambda 的內部 APIs。這些內部 API 更新可能是向後不相容的,這會導致意外結果,例如若您的函數依賴於這些非公有 API,則叫用失敗。請參閱 API 參考查看公開可用 API 的清單。

撰寫等冪程式碼。為函數撰寫等冪程式碼可確保採用相同方式來處理重複事件。程式碼應正確驗證事件並正常處理重複的事件。如需詳細資訊,請參閱 How do I make my Lambda function idempotent? (如何讓 Lambda 函數等冪?)。

  • 避免使用 Java DNS 快取。Lambda 函數已快取 DNS 回應。如果您使用其他 DNS 快取,則可能會發生連線逾時。

    java.util.logging.Logger 類別可以間接啟用 JVM DNS 快取。若要覆寫預設設定,請先將 networkaddress.cache.ttl 設定為 0,再初始化 logger。範例:

    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 函數