Definire l'handler della funzione Lambda in Java - AWS Lambda

Definire l'handler della funzione Lambda in Java

Il gestore di funzioni Lambda è il metodo nel codice della funzione che elabora gli eventi. Quando viene richiamata la funzione, Lambda esegue il metodo del gestore. La funzione viene eseguita fino a quando il gestore non restituisce una risposta, termina o scade.

Questa pagina descrive come lavorare con i gestori di funzioni Lambda in Go, inclusa la configurazione del progetto, le convenzioni di denominazione e le migliori pratiche. Questa pagina include anche un esempio di funzione Go Lambda che raccoglie informazioni su un ordine, produce una ricevuta in un file di testo e inserisce questo file in un bucket Amazon Simple Storage Service (S3). Per informazioni su come distribuire una funzione dopo averla scritta, consulta o. Distribuisci funzioni Lambda per Java con archivi di file .zip o JAR Distribuisci funzioni Java Lambda con immagini di container

Configurazione di Go Handler

Quando si lavora con le funzioni Lambda in Java, il processo prevede la scrittura del codice, la compilazione e la distribuzione degli artefatti compilati in Lambda. È possibile inizializzare un progetto Java Lambda in vari modi. Ad esempio, puoi utilizzare strumenti come le funzioni Maven Archetype for Lambda, AWS SAM il comando CLI sam init o persino una configurazione standard di progetto Java nel tuo IDE preferito, come IntelliJ IDEA o Visual Studio Code. In alternativa, puoi creare manualmente la struttura di file richiesta.

Un tipico progetto di funzione Java Lambda segue questa struttura generale:

/project-root └ src └ main └ java └ example └ OrderHandler.java (contains main handler) └ <other_supporting_classes> └ build.gradle OR pom.xml

Puoi usare Maven o Gradle per creare il tuo progetto e gestire le dipendenze.

La logica principale del gestore per la funzione risiede in un file Java all'interno della directory. src/main/java/example In questo esempio viene assegnato un nome a questo fileOrderHandler.java. Oltre a questo file, è possibile includere classi Java aggiuntive in base alle esigenze. Quando distribuisci la tua funzione in Lambda, assicurati di specificare la classe Java che contiene il metodo del gestore principale che Lambda deve invocare durante una chiamata.

Esempio di codice della funzione Lambda

Il seguente esempio di codice della funzione Go Lambda raccoglie le informazioni su un ordine, produce una ricevuta in un file di testo e inserisce questo file in un bucket Amazon S3.

Esempio OrderHandler.javaFunzione 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); } } }

Questo file OrderHandler.java contiene le sezioni seguenti:

  • package example: In Java, può essere qualsiasi cosa, ma deve corrispondere alla struttura di directory del progetto. Qui, lo usiamo package example perché la struttura delle cartelle èsrc/main/java/example.

  • importistruzioni: consente di importare le classi Java richieste dalla funzione Lambda.

  • public class OrderHandler ...: definisce la classe Java e deve essere una definizione di classe valida.

  • private static final S3Client S3_CLIENT ...: Questo inizializza un client S3 al di fuori di qualsiasi metodo della classe. Questo fa sì che Lambda esegua questo codice durante la fase di inizializzazione.

  • block: definisce la forma dell'evento di input previsto in questa struttura Go.

  • public String handleRequest(Order event, Context context): questo è il metodo dell'handler principale, che contiene la logica principale dell'applicazione.

  • private void uploadReceiptToS3(...) {}: questo è un metodo helper a cui fa riferimento il metodo dell'handler principale handleRequest.

Il seguente pom.xml file build.gradle o accompagna questa funzione.

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>

Affinché questa funzione funzioni correttamente, il suo ruolo di esecuzione deve consentire l's3:PutObjectazione. Inoltre, assicuratevi di definire la variabile di RECEIPT_BUCKET ambiente. Dopo una chiamata riuscita, il bucket Amazon S3 dovrebbe contenere un file di ricevuta.

Nota

Questa funzione può richiedere impostazioni di configurazione aggiuntive per funzionare correttamente senza timeout. Si consiglia di configurare 256 MB di memoria e un timeout di 10 secondi. La prima chiamata potrebbe richiedere più tempo a causa di un avvio a freddo. Le chiamate successive dovrebbero essere eseguite molto più velocemente grazie al riutilizzo dell'ambiente di esecuzione.

Definizioni di classe valide per i gestori Java

La libreria aws-lambda-java-core definisce due interfacce per i metodi del gestore. Utilizzare le interfacce fornite per semplificare la configurazione del gestore e convalidare la firma del metodo del gestore in fase di compilazione.

L'interfaccia RequestHandler è un tipo generico che accetta due parametri: il tipo di input e il tipo di output. Entrambi i tipi devono essere oggetti. In questo esempio, la nostra OrderHandler classe implementa. RequestHandler<OrderHandler.Order, String> Il tipo di input è il Order record che definiamo all'interno della classe e il tipo di output èString.

public class OrderHandler implements RequestHandler<OrderHandler.Order, String> { ... }

Quando si utilizza questa interfaccia, il runtime Java deserializza l'evento in un oggetto con il tipo di input e serializza l'output in testo. Utilizzare questa interfaccia quando la serializzazione integrata funziona con i tipi di input e output.

Per utilizzare la propria serializzazione, implementare l'interfaccia RequestStreamHandler. Con questa interfaccia, Lambda passa al gestore un flusso di input e un flusso di output. Il gestore legge i byte dal flusso di input, scrive nel flusso di output e restituisce il valore void. Per un esempio di questo utilizzo del runtime Java 21, vedete HandlerStream.java.

Se lavori solo con tipi di base e generici (ad esempio String IntegerList, oMap) nella tua funzione Java, non è necessario implementare un'interfaccia. Ad esempio, se la funzione riceve un Map<String, String> input e restituisce unString, la definizione della classe e la firma del gestore potrebbero essere simili alle seguenti:

public class ExampleHandler { public String handleRequest(Map<String, String> input, Context context) { ... } }

Inoltre, quando non implementate un'interfaccia, l'oggetto context è facoltativo. Ad esempio, la definizione della classe e la firma dell'handler potrebbero essere simili alle seguenti:

public class NoContextHandler { public String handleRequest(Map<String, String> input) { ... } }

Convenzioni di denominazione dei gestori

Per le funzioni Lambda in Java, se si implementa l'RequestStreamHandlerinterfaccia RequestHandler or, è necessario denominare il metodo del gestore principale. handleRequest Inoltre, includi il @Override tag sopra il handleRequest metodo. Quando distribuisci la tua funzione in Lambda, specifica il gestore principale nella configurazione della funzione nel seguente formato:

  • <package>. <Class>— Ad esempio,example.OrderHandler.

Per le funzioni Lambda in Java che non implementano l'RequestStreamHandlerinterfaccia RequestHandler or, puoi usare qualsiasi nome per l'handler. Quando distribuisci la tua funzione in Lambda, specifica il gestore principale nella configurazione della funzione nel seguente formato:

  • <package>. <Class>:: <handler_method_name>— Ad esempio,example.Handler::mainHandler.

Definizione e accesso all'oggetto evento di input

JSON è il formato di input più comune e standard per le funzioni Lambda. In questo esempio, la funzione prevede un input simile a quanto segue:

{ "orderId": "12345", "amount": 199.99, "item": "Wireless Headphones" }

Quando si utilizzano le funzioni Lambda in Go, è possibile definire la forma dell'evento di input previsto come struttura Go. In questo esempio, definiamo un record all'interno della OrderHandler classe per rappresentare un Order oggetto:

public record Order(String orderId, double amount, String item) {}

Questa struttura corrisponde alla forma di input prevista. Dopo aver definito il record, puoi scrivere una firma dell'handler che includa un input JSON conforme alla definizione del record. Il runtime Java deserializza automaticamente questo JSON in un oggetto Java. È quindi possibile accedere ai campi dell'oggetto. Ad esempio, event.orderId recupera il valore di orderId dall'input originale.

Nota

I record Java sono una funzionalità dei runtime Java 17 e versioni successive. In tutti i runtime di Java, è possibile utilizzare una classe per rappresentare i dati degli eventi. In questi casi, puoi usare una libreria come jackson per deserializzare gli input JSON.

Altri tipi di eventi di input

Esistono molti eventi di input possibili per le funzioni Lambda in Java:

  • Integer, Long, Double, e così via. – L' evento è un numero senza formattazione aggiuntiva, ad esempio ., 3.5. Il runtime converte il valore in un oggetto del tipo specificato.

  • String – l'evento è una stringa JSON, incluse le virgolette, ad esempio, “My string”. Il runtime converte il valore (senza virgolette) in un oggetto String.

  • List<Integer>, List<String>, List<Object>, e così via. – L'evento è un array JSON. Il runtime lo deserializza in un oggetto del tipo o dell'interfaccia specificati.

  • InputStream: l'evento è un tipo qualsiasi di JSON. Il runtime passa un flusso di byte del documento al gestore senza modifiche. Si deserializza l'output di input e scrittura in un flusso di output.

  • Tipo di libreria: per gli eventi inviati dai servizi AWS, utilizzare i tipi nella libreria aws-lambda-java-events. Ad esempio, se la tua funzione Lambda viene richiamata da Amazon Simple Queue Service (SQS), usa l'oggetto come input. SQSEvent

Accesso e utilizzo dell'oggetto contestuale Lambda

L'oggetto contesto: contiene informazioni sulla chiamata, sulla funzione e sull'ambiente di esecuzione. In questo esempio, l'oggetto di contesto è di tipo com.amazonaws.services.lambda.runtime.Context ed è il secondo argomento della funzione di gestione principale.

public String handleRequest(Order event, Context context) { ... }

Se la classe implementa l'interfaccia RequestHandler o RequestStreamHandler, l'oggetto context è un argomento obbligatorio. In caso contrario, l'oggetto oggetto oggetto è facoltativo. Per ulteriori informazioni sulle firme dell'handler accettate, consulta Definizioni di classe valide per i gestori Java.

Se si effettuano chiamate ad altri servizi utilizzando l'AWSSDK, l'oggetto context è necessario in alcune aree chiave. Ad esempio, per produrre log di funzioni per Amazon CloudWatch, puoi context.getLogger() utilizzare il metodo per LambdaLogger ottenere un oggetto per la registrazione. In questo esempio, possiamo usare il logger per registrare un messaggio di errore se l'elaborazione fallisce per qualsiasi motivo:

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

Oltre alle richieste SDK, puoi anche utilizzare l'oggetto context per il monitoraggio delle funzioni. Per ulteriori informazioni sulla copia di oggetti, consulta la sezione Utilizzo dell'oggetto di contesto Lambda per recuperare le informazioni sulla funzione Java.

Utilizzo della AWS versione v2 nell'handler

Spesso, utilizzerai le funzioni Lambda per interagire o aggiornare altre AWS risorse. Il modo più semplice per interfacciarsi con queste risorse è usare la AWS v2.

Nota

La AWS (v1) è in modalità manutenzione e terminerà il supporto il 31 luglio 2025. In futuro, si consiglia di utilizzare solo la AWS v2.

Per aggiungere dipendenze SDK alla tua funzione, aggiungile nel tuo build.gradle for Gradle o pom.xml nel file per Maven. Ti consigliamo di aggiungere solo le librerie necessarie per la tua funzione. Nel codice di esempio precedente, abbiamo usato la libreria e la libreria. In Gradle, puoi aggiungere questa dipendenza aggiungendo la seguente riga nella sezione delle dipendenze del tuo: build.gradle

implementation 'software.amazon.awssdk:s3:2.28.29'

In Maven, aggiungi le seguenti righe nella sezione del tuo: <dependencies> pom.xml

<dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>s3</artifactId> <version>2.28.29</version> </dependency>
Nota

Questa potrebbe non essere la versione più recente dell'SDK. Scegli la versione dell'SDK appropriata per la tua applicazione.

Quindi, importa le dipendenze direttamente nella tua classe Java:

import software.amazon.awssdk.services.s3.S3Client; import software.amazon.awssdk.services.s3.model.PutObjectRequest; import software.amazon.awssdk.services.s3.model.S3Exception;

Il codice di esempio inizializza quindi un client Amazon S3 come segue:

private static final S3Client S3_CLIENT = S3Client.builder().build();

In questo esempio, abbiamo inizializzato il nostro client Amazon S3 nella funzione per evitare di doverlo inizializzare ogni volta che richiamiamo la nostra funzione. Dopo aver configurato e inizializzato il client SDK, puoi utilizzarlo per interagire con altri servizi. AWS Il codice di esempio richiama l'PutObjectAPI Amazon S3 nel modo seguente:

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)));

Accesso alle variabili d'ambiente

Nel codice dell'handler, puoi fare riferimento a qualsiasi variabile di ambiente utilizzando il metodo System.getenv(). In questo esempio, facciamo riferimento alla variabile di RECEIPT_BUCKET ambiente definita utilizzando la seguente riga di codice:

String bucketName = System.getenv("RECEIPT_BUCKET"); if (bucketName == null || bucketName.isEmpty()) { throw new IllegalArgumentException("RECEIPT_BUCKET environment variable is not set"); }

Utilizzo dello stato globale

Lambda esegue il codice statico e il costruttore della classe durante la fase di inizializzazione prima di richiamare la funzione per la prima volta. Le risorse create durante l'inizializzazione restano in memoria tra le chiamate, in modo da evitare di doverle creare ogni volta che si richiama la funzione.

Nell'esempio seguente, il codice di inizializzazione del client non rientra nel metodo del gestore principale. Il runtime inizializza il client prima che la funzione gestisca il suo primo evento e il client rimane disponibile per il riutilizzo in tutte le chiamate.

Best practice di codice per funzioni Lambda in Java

Segui le linee guida riportate nell'elenco seguente per utilizzare le best practice di codifica durante la creazione delle funzioni Lambda:

  • Separare il gestore Lambda dalla logica principale. In questo modo è possibile creare una funzione di cui è più semplice eseguire l'unit test.

  • Controllare le dipendenze nel pacchetto di distribuzione della funzione. L'ambiente di esecuzione AWS Lambda contiene diverse librerie. Per abilitare il set di caratteristiche e aggiornamenti della sicurezza più recenti, Lambda aggiorna periodicamente tali librerie. Tali aggiornamenti possono introdurre lievi modifiche al comportamento della funzione Lambda. Per mantenere il controllo completo delle dipendenze utilizzate dalla funzione, inserire tutte le dipendenze nel pacchetto di implementazione.

  • Ridurre la complessità delle dipendenze. Preferire framework più semplici che si caricano velocemente all'avvio del contesto di esecuzione. Preferire ad esempio l'utilizzo di framework di inserimento di dipendenze Java, come Dagger o Guice, rispetto a framework più complessi come Spring Framework.

  • Ridurre al minimo le dimensioni del pacchetto di implementazione al fine di soddisfare le esigenze di runtime. In questo modo viene ridotta la quantità di tempo necessaria per il download del pacchetto e per la relativa decompressione prima dell'invocazione. Per le funzioni create in Java, evitare di caricare l'intera libreria dell'SDK AWS come parte del pacchetto di implementazione. Utilizzare invece in modo selettivo i moduli che prelevano i componenti dell'SDK necessari (ad esempio i moduli SDK Dynamo DB e Amazon S3 e le librerie di base Lambda).

Sfruttare il riutilizzo del contesto di esecuzione per migliorare le prestazioni della funzione. Inizializzare i client SDK e le connessioni al database all'esterno del gestore di funzioni e memorizzare localmente nella cache gli asset statici nella directory /tmp. Le chiamate successive elaborate dalla stessa istanza della funzione possono riutilizzare queste risorse. Ciò consente di risparmiare sui costi riducendo i tempi di esecuzione delle funzioni.

Per evitare potenziali perdite di dati tra le chiamate, non utilizzare il contesto di esecuzione per archiviare dati utente, eventi o altre informazioni con implicazioni di sicurezza. Se la funzione si basa su uno stato mutabile che non può essere archiviato in memoria all'interno del gestore, considerare la possibilità di creare una funzione separata o versioni separate di una funzione per ogni utente.

Utilizzare una direttiva keep-alive per mantenere le connessioni persistenti. Lambda elimina le connessioni inattive nel tempo. Se si tenta di riutilizzare una connessione inattiva quando si richiama una funzione, si verificherà un errore di connessione. Per mantenere la connessione persistente, utilizzare la direttiva keep-alive associata al runtime. Per un esempio, vedere Riutilizzo delle connessioni con Keep-Alive in Node.js.

Utilizzare le variabili di ambiente per passare i parametri operativi alla funzione. Se ad esempio si scrive in un bucket Amazon S3 anziché impostare come hard-coded il nome del bucket in cui si esegue la scrittura, configurare tale nome come una variabile di ambiente.

Evita di usare invocazioni ricorsive nella tua funzione Lambda, in cui la funzione si richiama da sola o avvia un processo che potrebbe richiamare nuovamente la funzione. Ciò potrebbe provocare un volume non desiderato di invocazioni della funzione e un aumento dei costi. Se noti un volume indesiderato di invocazioni, imposta immediatamente la simultaneità riservata della funzione su 0 per interrompere tutte le invocazioni della funzione mentre si aggiorna il codice.

Non utilizzare API non documentate e non pubbliche nel codice della funzione Lambda. Per i tempi di esecuzione gestiti AWS Lambda, Lambda applica periodicamente aggiornamenti di sicurezza e funzionalità alle API interne di Lambda. Questi aggiornamenti API interni potrebbero essere incompatibili con le versioni precedenti, causando conseguenze indesiderate come errori di chiamata se la funzione ha una dipendenza su queste API non pubbliche. Consulta il riferimento all'API per un elenco di API disponibili pubblicamente.

Scrivi un codice idempotente. La scrittura di un codice idempotente per le tue funzioni garantisce che gli eventi duplicati vengano gestiti allo stesso modo. Il tuo codice dovrebbe convalidare correttamente gli eventi e gestire con garbo gli eventi duplicati. Per ulteriori informazioni, consulta Come posso rendere idempotente la mia funzione Lambda?.

  • Evita di usare la cache DNS Java. Le funzioni Lambda memorizzano già nella cache le risposte DNS. Se viene utilizzata un'altra cache DNS, è possibile che si verifichino dei timeout di connessione.

    La classe java.util.logging.Logger può abilitare indirettamente la cache DNS JVM. Per sovrascrivere le impostazioni predefinite, imposta networkaddress.cache.ttl su 0 prima dell'inizializzazione di logger. Esempio:

    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); }
  • Ridurre il tempo necessario a Lambda per decomprimere i pacchetti di distribuzione creati in Java inserendo i file .jar della dipendenza in una directory /lib separata. Questo metodo è più rapido rispetto all'inserimento di tutto il codice della funzione in un unico file .jar con un elevato numero di file .class. Per istruzioni, consulta Distribuisci funzioni Lambda per Java con archivi di file .zip o JAR.