Utilice interceptores de ejecución en AWS SDK for Java 2.x - AWS SDK for Java 2.x

Utilice interceptores de ejecución en AWS SDK for Java 2.x

Los interceptores de ejecución de AWS SDK for Java 2.x se enlazan en el ciclo de vida de las solicitudes y las respuestas para realizar lógica personalizada en distintas etapas de la ejecución de las llamadas a la API. Utilice los interceptores para implementar aspectos transversales, como registro, recopilación de métricas, modificación de solicitudes, depuración y tratamiento de errores.

Los interceptores implementan la interfaz de ExecutionInterceptor, que proporciona enlaces para las diferentes fases de la ejecución de solicitudes.

Ciclo de vida de los interceptores

La interfaz de ExecutionInterceptor proporciona métodos a los que se llama en puntos específicos durante la ejecución de solicitudes:

  • beforeExecution: se llama antes de que se ejecute la solicitud

  • modifyRequest: modifica el objeto de solicitud del SDK

  • beforeMarshalling: se llama antes de que la solicitud se serialice a HTTP

  • afterMarshalling: se llama después de que la solicitud se serialice a HTTP

  • modifyHttpRequest: modifica la solicitud de HTTP

  • beforeTransmission: se llama antes de que se envíe la solicitud de HTTP

  • afterTransmission: se llama después de que se reciba la respuesta de HTTP

  • modifyHttpResponse: modifica la respuesta de HTTP

  • beforeUnmarshalling: se llama antes de que se deserialice la respuesta de HTTP

  • afterUnmarshalling: se llama después de que deserialice la respuesta de HTTP

  • modifyResponse: modifica el objeto de respuesta del SDK

  • afterExecution: se llama después de la ejecución correcta de la solicitud

  • onExecutionFailure: se llama cuando falla la ejecución de la solicitud

Registro de interceptores

Registre interceptores al crear un cliente de servicio mediante el método overrideConfiguration. Puede registrar varios interceptores y se ejecutarán en el orden en el que los registre.

// Register a single interceptor. SqsClient client = SqsClient.builder() .overrideConfiguration(c -> c.addExecutionInterceptor(new LoggingInterceptor())) .build(); // Register multiple interceptors. S3Client s3Client = S3Client.builder() .overrideConfiguration(config -> config .addExecutionInterceptor(new TimingInterceptor()) .addExecutionInterceptor(new LoggingInterceptor()) .addExecutionInterceptor(new RequestModificationInterceptor())) .build();

Ejemplo de interceptor

La siguiente clase demuestra cómo utilizar los interceptores de ejecución para añadir aspectos transversales como registro, supervisión del rendimiento y modificación de solicitudes a las operaciones de S3 sin cambiar la lógica empresarial principal.

En este ejemplo se muestra cómo registrar varios interceptores en un cliente de S3 y verlos en acción durante llamadas a la AWS API reales.

import org.slf4j.Logger; import org.slf4j.LoggerFactory; import software.amazon.awssdk.awscore.exception.AwsServiceException; import software.amazon.awssdk.core.ApiName; import software.amazon.awssdk.core.SdkRequest; import software.amazon.awssdk.core.interceptor.Context; import software.amazon.awssdk.core.interceptor.ExecutionAttributes; import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; import software.amazon.awssdk.http.SdkHttpRequest; import software.amazon.awssdk.http.SdkHttpResponse; import software.amazon.awssdk.services.s3.S3Client; import software.amazon.awssdk.services.s3.model.HeadBucketRequest; import software.amazon.awssdk.services.s3.model.ListBucketsResponse; import java.time.Duration; import java.time.Instant;
public class S3InterceptorsDemo { private static final Logger logger = LoggerFactory.getLogger(S3InterceptorsDemo.class); public static void main(String[] args) { logger.info("=== AWS SDK for Java v2 - S3 Interceptors Demo ==="); // Create an S3 client with multiple interceptors. S3Client s3Client = S3Client.builder() .overrideConfiguration(config -> config .addExecutionInterceptor(new TimingInterceptor()) .addExecutionInterceptor(new LoggingInterceptor()) .addExecutionInterceptor(new RequestModificationInterceptor())) .build(); try { logger.info("Starting S3 operations with interceptors..."); // Operation 1: List buckets. logger.info("Operation 1: Listing S3 buckets"); logger.info("----------------------------------------"); ListBucketsResponse listBucketsResponse = s3Client.listBuckets(); logger.info("Found {} buckets", listBucketsResponse.buckets().size()); // Operation 2: Try to access a bucket that likely doesn't exist. logger.info("Operation 2: Checking non-existent bucket (demonstrating error interceptor)"); logger.info("----------------------------------------"); try { s3Client.headBucket(HeadBucketRequest.builder() .bucket("amzn-s3-demo-bucket-that-does-not-exist-1234") .build()); } catch (Exception e) { logger.info("Expected error occurred (interceptor should have logged it)"); } } catch (Exception e) { logger.error(" Error during S3 operations: {}", e.getMessage(), e); } finally { s3Client.close(); logger.info("Demo completed - S3 client closed"); } } // Logging interceptor. private static class LoggingInterceptor implements ExecutionInterceptor { private static final Logger logger = LoggerFactory.getLogger(LoggingInterceptor.class); @Override public void beforeExecution(Context.BeforeExecution context, ExecutionAttributes executionAttributes) { logger.info("[LOGGING] Starting request: {}", context.request().getClass().getSimpleName()); } @Override public void afterExecution(Context.AfterExecution context, ExecutionAttributes executionAttributes) { logger.info("[LOGGING] Completed request: {}", context.request().getClass().getSimpleName()); } @Override public void onExecutionFailure(Context.FailedExecution context, ExecutionAttributes executionAttributes) { logger.error("[LOGGING] Request failed: {}", context.request().getClass().getSimpleName()); if (context.exception() instanceof AwsServiceException) { AwsServiceException ase = (AwsServiceException) context.exception(); if (ase.awsErrorDetails().errorCode() != null) { SdkHttpResponse httpResponse = ase.awsErrorDetails().sdkHttpResponse(); logger.error(" HTTP Status: {}", httpResponse.statusCode()); logger.error(" Error Code: {}", ase.awsErrorDetails().errorCode()); logger.error(" Error Message: {}", ase.awsErrorDetails().errorMessage()); } } } } // Performance timing interceptor. private static class TimingInterceptor implements ExecutionInterceptor { private static final Logger logger = LoggerFactory.getLogger(TimingInterceptor.class); private Instant startTime; @Override public void beforeExecution(Context.BeforeExecution context, ExecutionAttributes executionAttributes) { startTime = Instant.now(); logger.info("⏱️ [TIMING] Request started at: {}", startTime); } @Override public void afterExecution(Context.AfterExecution context, ExecutionAttributes executionAttributes) { if (startTime != null) { Duration duration = Duration.between(startTime, Instant.now()); logger.info("⏱️ [TIMING] Request completed in: {}ms", duration.toMillis()); } } @Override public void onExecutionFailure(Context.FailedExecution context, ExecutionAttributes executionAttributes) { if (startTime != null) { Duration duration = Duration.between(startTime, Instant.now()); logger.warn("⏱️ [TIMING] Request failed after: {}ms", duration.toMillis()); } } } // Request modification interceptor private static class RequestModificationInterceptor implements ExecutionInterceptor { private static final Logger logger = LoggerFactory.getLogger(RequestModificationInterceptor.class); @Override public SdkRequest modifyRequest(Context.ModifyRequest context, ExecutionAttributes executionAttributes) { SdkRequest originalRequest = context.request(); logger.info("[MODIFY] Modifying request: {}", originalRequest.getClass().getSimpleName()); // For ListBucketsRequest, we can't modify much since it has no settable properties // For HeadBucketRequest, we can demonstrate modifying the request if (originalRequest instanceof HeadBucketRequest) { HeadBucketRequest headRequest = (HeadBucketRequest) originalRequest; // Create a new request with an API name added. return HeadBucketRequest.builder() .bucket(headRequest.bucket()) .overrideConfiguration(b -> b.addApiName(ApiName.builder() .name("My-API") .version("1.0") .build())) .build(); } logger.info("Not a HeadBucketRequest, returning original request"); return originalRequest; } @Override public SdkHttpRequest modifyHttpRequest(Context.ModifyHttpRequest context, ExecutionAttributes executionAttributes) { logger.info("[MODIFY] Adding custom HTTP headers"); return context.httpRequest().toBuilder() .putHeader("X-Custom-Header", "S3InterceptorDemo") .putHeader("X-Request-ID", java.util.UUID.randomUUID().toString()) .build(); } } }
<?xml version="1.0" encoding="UTF-8"?> <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/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>interceptors-examples</artifactId> <version>1.0.0</version> <packaging>jar</packaging> <name>interceptors-examples</name> <description>Demonstration of execution interceptors in AWS SDK for Java v2</description> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <aws.java.sdk.version>2.31.62</aws.java.sdk.version> <exec.mainClass>org.example.S3InterceptorsDemo</exec.mainClass> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>bom</artifactId> <version>${aws.java.sdk.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-bom</artifactId> <version>2.23.1</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>s3</artifactId> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>2.0.13</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j2-impl</artifactId> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-1.2-api</artifactId> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.10.1</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <!-- Compiler Plugin --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.11.0</version> <configuration> <source>17</source> <target>17</target> </configuration> </plugin> <!-- Surefire Plugin for running tests --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>3.2.2</version> </plugin> <!-- Exec Plugin for running the main class --> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>3.1.0</version> <configuration> <mainClass>${exec.mainClass}</mainClass> </configuration> </plugin> <!-- Shade Plugin to create executable JAR --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.4.1</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>${exec.mainClass}</mainClass> </transformer> </transformers> <createDependencyReducedPom>false</createDependencyReducedPom> </configuration> </execution> </executions> </plugin> </plugins> </build> </project>

Prácticas recomendadas

  • Mantenga los interceptores livianos: los interceptores se ejecutan para cada solicitud, por lo que debe evitar computación intensa u operaciones de bloqueo que podrían afectar al rendimiento.

  • Trate las excepciones con cuidado: si el interceptor lanza una excepción, se produce un error en toda la solicitud. Utilice siempre bloques try-catch para operaciones que puedan fallar.

  • Importancia del orden: los interceptores se ejecutan en el orden en que los registra. Tenga en cuenta las dependencias entre los interceptores al registrarlos.

  • Use ExecutionAttributes para el estado: si necesita pasar datos entre diferentes métodos de interceptor, use ExecutionAttributes lugar de variables de instancia para garantizar la seguridad de los subprocesos.

  • Preste atención a los datos confidenciales: cuando registre solicitudes y respuestas, tenga cuidado de no registrar información confidencial, como credenciales o datos personales.

Objetos de contexto

Cada método interceptor recibe un objeto de contexto que proporciona acceso a la información de solicitud y respuesta en diferentes etapas de ejecución:

  • Context.BeforeExecution: proporciona acceso a la solicitud original del SDK

  • Context.ModifyRequest: modifica la solicitud del SDK

  • Context.ModifyHttpRequest: modifica la solicitud de HTTP

  • Context.AfterExecution: proporciona acceso a la solicitud y la respuesta

  • Context.FailedExecution: proporciona acceso a la solicitud y a la excepción que se ha producido