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

Le traduzioni sono generate tramite traduzione automatica. In caso di conflitto tra il contenuto di una traduzione e la versione originale in Inglese, quest'ultima prevarrà.

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.

Il repository GitHub per questa guida fornisce applicazioni di esempio di facile distribuzione che illustrano vari tipi di gestore. Per informazioni dettagliate, consulta la parte finale di questo argomento.

Gestore di esempio: runtime di Java 17

Nell'esempio seguente di Java 17, una classe denominata HandlerIntegerJava17 definisce un metodo del gestore denominato handleRequest. Il metodo del gestore accetta i seguenti input:

  • Un IntegerRecord, che è un record Java personalizzato che rappresenta i dati degli eventi. In questo esempio, definiamo IntegerRecord come segue:

    record IntegerRecord(int x, int y, String message) { }
  • Un oggetto di contesto, che fornisce i metodi e le proprietà che forniscono le informazioni sull'invocazione, sulla funzione e sull'ambiente di esecuzione.

Supponiamo di voler scrivere una funzione che registri il message dall'IntegerRecord di input e restituisca la somma di x e y. Di seguito è riportato il codice della funzione:

Esempio HandlerIntegerJava17.java
package example; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.LambdaLogger; import com.amazonaws.services.lambda.runtime.RequestHandler; // Handler value: example.HandlerInteger public class HandlerIntegerJava17 implements RequestHandler<IntegerRecord, Integer>{ @Override /* * Takes in an InputRecord, which contains two integers and a String. * Logs the String, then returns the sum of the two Integers. */ public Integer handleRequest(IntegerRecord event, Context context) { LambdaLogger logger = context.getLogger(); logger.log("String found: " + event.message()); return event.x() + event.y(); } } record IntegerRecord(int x, int y, String message) { }

Specifica quale metodo Lambda deve richiamare impostando il parametro del gestore sulla configurazione della funzione. È possibile specificare il gestore nei seguenti formati:

  • package.Class::method: formato completo . Ad esempio: example.Handler::handleRequest.

  • package.Class: formato abbreviato per le classi che implementano un'interfaccia del gestore. Ad esempio: example.Handler.

Quando Lambda richiama il gestore, il runtime Lambda riceve un evento come stringa in formato JSON e lo converte in un oggetto. Nell'esempio precedente, un evento di esempio potrebbe essere simile al seguente:

Esempio event.json
{ "x": 1, "y": 20, "message": "Hello World!" }

Puoi salvare questo file e testare la funzione localmente con il seguente comando dell'AWS Command Line Interface (CLI):

aws lambda invoke --function-name function_name --payload file://event.json out.json

Gestore di esempio: runtime di Java 11 e versioni precedenti

Lambda supporta i record nei runtime di Java 17 e successivi. In tutti i runtime di Java, è possibile utilizzare una classe per rappresentare i dati degli eventi. L'esempio seguente utilizza un elenco di numeri interi e un oggetto di contesto come input e restituisce la somma di tutti i numeri interi nell'elenco.

Esempio Handler.java

Nell'esempio seguente, una classe denominata Handler definisce un metodo del gestore denominato handleRequest. Il metodo del gestore accetta un oggetto evento e contesto come input e restituisce una stringa.

Esempio HandlerList.java
package example; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.LambdaLogger; import com.amazonaws.services.lambda.runtime.RequestHandler; import java.util.List; // Handler value: example.HandlerList public class HandlerList implements RequestHandler<List<Integer>, Integer>{ @Override /* * Takes a list of Integers and returns its sum. */ public Integer handleRequest(List<Integer> event, Context context) { LambdaLogger logger = context.getLogger(); logger.log("EVENT TYPE: " + event.getClass().toString()); return event.stream().mapToInt(Integer::intValue).sum(); } }

Per altri esempi, consulta la pagina Codice del gestore di esempio.

Codice di inizializzazione

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 un'invocazione e l'altra e possono essere riutilizzate dal gestore migliaia di volte. È possibile aggiungere il codice di inizializzazione al di fuori del metodo del gestore per risparmiare tempo di calcolo e riutilizzare le risorse tra più invocazioni.

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 esegua il suo primo evento. Gli eventi successivi sono molto più veloci perché Lambda non ha bisogno di inizializzare nuovamente il client.

Esempio Handler.java
package example; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.LambdaLogger; import com.amazonaws.services.lambda.runtime.RequestHandler; import java.util.Map; import software.amazon.awssdk.services.lambda.LambdaClient; import software.amazon.awssdk.services.lambda.model.GetAccountSettingsResponse; import software.amazon.awssdk.services.lambda.model.LambdaException; // Handler value: example.Handler public class Handler implements RequestHandler<Map<String,String>, String> { private static final LambdaClient lambdaClient = LambdaClient.builder().build(); @Override public String handleRequest(Map<String,String> event, Context context) { LambdaLogger logger = context.getLogger(); logger.log("Handler invoked"); GetAccountSettingsResponse response = null; try { response = lambdaClient.getAccountSettings(); } catch(LambdaException e) { logger.log(e.getMessage()); } return response != null ? "Total code size for your account is " + response.accountLimit().totalCodeSize() + " bytes" : "Error"; } }

Scelta dei tipi di input e di output

Specificare il tipo di oggetto a cui l'evento si mappa nella firma del metodo del gestore. Nell'esempio precedente, il runtime di Java deserializza l'evento in un tipo che implementa l'interfaccia Map<String,String>. Le mappature stringa a stringa funzionano per eventi flat come i seguenti:

Esempio Event.json – Dati meteo
{ "temperatureK": 281, "windKmh": -3, "humidityPct": 0.55, "pressureHPa": 1020 }

Tuttavia, il valore di ogni campo deve essere una stringa o un numero. Se l'evento include un campo con un oggetto come valore, il runtime non può deserializzarlo e restituisce un errore.

Scegliere un tipo di input che funzioni con i dati degli eventi elaborati dalla funzione. È possibile utilizzare un tipo di base, un tipo generico o un tipo ben definito.

Tipi di input
  • 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.

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

  • 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, utilizza i tipi nella libreria aws-lambda-java-events.

Se si definisce il proprio tipo di input, dovrebbe essere un vecchio oggetto Java (POJO) deserializzabile e mutabile, con un costruttore predefinito e proprietà per ogni campo nell'evento. Le chiavi nell'evento che non si mappano a una proprietà e le proprietà che non sono incluse nell'evento vengono eliminate senza errori.

Il tipo di output può essere un oggetto o void. Il runtime serializza i valori restituiti in testo. Se l'output è un oggetto con campi, il runtime lo serializza in un documento JSON. Se è un tipo che esegue il wrapping di un valore primitivo, il runtime restituisce una rappresentazione testuale di tale valore.

Interfacce del gestore

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. 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.

Esempio Handler.java – Interfaccia del gestore
// Handler value: example.Handler public class Handler implements RequestHandler<Map<String,String>, String>{ @Override public String handleRequest(Map<String,String> event, Context context)

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.

Nell'esempio seguente di Java 21 viene illustrato come utilizzare una funzione Lambda per elaborare gli ordini. Nell'esempio vengono utilizzati tipi di reader e writer memorizzati per utilizzare i flussi di input e output e viene illustrato come definire record Java personalizzati da utilizzare all'interno della funzione.

Esempio HandlerStream.java
import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestStreamHandler; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.List; public class HandlerStream implements RequestStreamHandler { private static final ObjectMapper objectMapper = new ObjectMapper(); @Override public void handleRequest(InputStream input, OutputStream output, Context context) throws IOException { Order order = objectMapper.readValue(input, Order.class); processOrder(order); OrderAccepted orderAccepted = new OrderAccepted(order.orderId); objectMapper.writeValue(output, orderAccepted); } private void processOrder(Order order) { // business logic } public record Order(@JsonProperty("orderId") String orderId, @JsonProperty("items") List<Item> items) { } public record Item(@JsonProperty("name") String name, @JsonProperty("quantity") Integer quantity) { } public record OrderAccepted(@JsonProperty("orderId") String orderId) { } }

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.

Codice di esempio del gestore

Il repository GitHub per questa guida include applicazioni di esempio che illustrano l'uso di vari tipi di gestori e interfacce. Ogni applicazione di esempio include script per semplificare la distribuzione e la pulizia, un modello AWS SAM e risorse di supporto.

Applicazioni Lambda di esempio in Java
  • java17-examples: una funzione Java che dimostra come utilizzare un record Java per rappresentare un oggetto di dati dell'evento di input.

  • java-basic: una raccolta di funzioni Java minimali con unit test e configurazione della registrazione dei log delle variabili.

  • java-events: una raccolta di funzioni Java che contengono codice skeleton per la gestione degli eventi di vari servizi, ad esempio Gateway Amazon API, Amazon SQS e Amazon Kinesis. Queste funzioni utilizzano la versione più recente della libreria aws-lambda-java-events (3.0.0 e versioni successive). Questi esempi non richiedono SDK AWS come dipendenza.

  • s3-java – Una funzione Java che elabora gli eventi di notifica da Amazon S3 e utilizza la Java Class Library (JCL) per creare anteprime dai file di immagine caricati.

  • serializzazione personalizzata: esempi di come implementare la serializzazione personalizzata utilizzando librerie popolari come fastJson, Gson, Moshi e jackson-jr.

  • Utilizza API Gateway per richiamare una funzione Lambda: una funzione Java che esegue la scansione di una tabella Amazon DynamoDB che contiene informazioni sui dipendenti. Quindi utilizza Amazon Simple Notification Service per inviare un messaggio di testo ai dipendenti per festeggiare i loro anniversari di lavoro. Questo esempio usa API Gateway per richiamare la funzione.

Le applicazioni java-events e s3-java prendono un evento di servizio AWS come input e restituiscono una stringa. L'applicazione java-basic include vari tipi di gestori:

Per testare diversi tipi di gestore, è sufficiente modificare il valore del gestore nel modello AWS SAM. Per istruzioni dettagliate, consultare il file readme dell'applicazione di esempio.