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
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 solicitudmodifyRequest: modifica el objeto de solicitud del SDKbeforeMarshalling: se llama antes de que la solicitud se serialice a HTTPafterMarshalling: se llama después de que la solicitud se serialice a HTTPmodifyHttpRequest: modifica la solicitud de HTTPbeforeTransmission: se llama antes de que se envíe la solicitud de HTTPafterTransmission: se llama después de que se reciba la respuesta de HTTPmodifyHttpResponse: modifica la respuesta de HTTPbeforeUnmarshalling: se llama antes de que se deserialice la respuesta de HTTPafterUnmarshalling: se llama después de que deserialice la respuesta de HTTPmodifyResponse: modifica el objeto de respuesta del SDKafterExecution: se llama después de la ejecución correcta de la solicitudonExecutionFailure: 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
ExecutionAttributeslugar 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