

기계 번역으로 제공되는 번역입니다. 제공된 번역과 원본 영어의 내용이 상충하는 경우에는 영어 버전이 우선합니다.

# AWS SDK for Java 2.x에서 실행 인터셉터 사용
<a name="interceptors"></a>

요청 및 응답 수명 주기에 연결된 AWS SDK for Java 2.x 후크의 실행 인터셉터는 API 호출 실행의 다양한 단계에서 사용자 지정 로직을 수행합니다. 인터셉터를 사용하여 로깅, 지표 수집, 요청 수정, 디버깅 및 오류 처리 등의 크로스 커팅 문제를 구현합니다.

인터셉터는 다양한 요청 실행 단계에 대한 후크를 제공하는 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/interceptor/ExecutionInterceptor.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/interceptor/ExecutionInterceptor.html) 인터페이스를 구현합니다.

## 인터셉터 수명 주기
<a name="interceptor-lifecycle"></a>

`ExecutionInterceptor` 인터페이스는 요청 실행 중에 특정 시점에서 호출되는 메서드를 제공합니다.
+ `beforeExecution` - 요청이 실행되기 전에 호출됩니다.
+ `modifyRequest` - SDK 요청 객체를 수정합니다.
+ `beforeMarshalling` - HTTP에 대한 요청이 마샬되기 전에 호출됩니다.
+ `afterMarshalling` - HTTP에 대한 요청이 마샬된 후에 호출됩니다.
+ `modifyHttpRequest` - HTTP 요청을 수정합니다.
+ `beforeTransmission` - HTTP 요청이 전송되기 전에 호출됩니다.
+ `afterTransmission` - HTTP 응답을 수신한 후에 호출됩니다.
+ `modifyHttpResponse` - HTTP 응답을 수정합니다.
+ `beforeUnmarshalling` - HTTP 응답이 마샬 해제되기 전에 호출됩니다.
+ `afterUnmarshalling` - HTTP 응답이 마샬 해제된 후에 호출됩니다.
+ `modifyResponse` - SDK 응답 객체를 수정합니다.
+ `afterExecution` - 요청 실행 성공 후에 호출됩니다.
+ `onExecutionFailure` - 요청 실행이 실패할 때 호출됩니다.

## 인터셉터 등록
<a name="registering-interceptors"></a>

`overrideConfiguration` 메서드를 사용하여 서비스 클라이언트를 구축할 때 인터셉터를 등록합니다. 여러 인터셉터를 등록할 수 있으며 등록한 순서대로 실행됩니다.

```
// 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();
```

## 인터셉터 예제
<a name="interceptor-examples"></a>

다음 클래스는 실행 인터셉터를 사용하여 주요 비즈니스 로직을 변경하지 않고 S3 작업에 로깅, 성능 모니터링 및 요청 수정 등의 크로스 커팅 문제를 추가하는 방법을 보여줍니다.

이 예제에서는 S3 클라이언트에 여러 인터셉터를 등록하고 실제 AWS API 호출 중에 해당 인터셉터가 작동하는지 확인하는 방법을 보여줍니다.

### 가져오기
<a name="interceptor-example-imports"></a>

```
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();
        }
    }
}
```

### Maven pom 파일
<a name="interceptor-example-pom"></a>

```
<?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>
```

## 모범 사례
<a name="interceptor-best-practices"></a>
+ **인터셉터를 가볍게 유지** - 인터셉터는 각 요청에 대해 실행되므로 성능에 영향을 미칠 수 있는 과도한 계산이나 차단 작업은 지양합니다.
+ **예외 정상 처리** - 인터셉터에서 예외가 발생하면 전체 요청이 실패합니다. 작업 실패가 발생할 확률이 있는 경우 항상 try-catch 블록을 사용합니다.
+ **순서 중요** - 인터셉터는 등록한 순서대로 실행됩니다. 인터셉터를 등록할 때 인터셉터 간의 종속성을 고려합니다.
+ **상태에 ExecutionAttributes 사용** - 서로 다른 인터셉터 메서드 간에 데이터를 전달해야 하는 경우 인스턴스 변수 대신 `ExecutionAttributes`를 사용하여 스레드 안전을 확보합니다.
+ **민감한 데이터에 유의** - 요청 및 응답을 로그할 때 자격 증명 또는 개인정보와 같은 민감한 정보를 로그하지 않도록 주의합니다.

## 컨텍스트 객체
<a name="interceptor-context-objects"></a>

각 인터셉터 메서드는 다양한 실행 단계에서 요청 및 응답 정보에 대한 액세스를 제공하는 컨텍스트 객체를 수신합니다.
+ `Context.BeforeExecution` - 원래 SDK 요청에 대한 액세스 권한을 제공합니다.
+ `Context.ModifyRequest` - SDK 요청을 수정합니다.
+ `Context.ModifyHttpRequest` - HTTP 요청을 수정합니다.
+ `Context.AfterExecution` - 요청 및 응답 모두에 대한 액세스 권한을 제공합니다.
+ `Context.FailedExecution` - 요청 및 발생한 예외에 대한 액세스 권한을 제공합니다.