

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# 使用 Amazon S3
<a name="examples-s3"></a>

本节提供有关使用 AWS SDK for Java 2.x来处理 Amazon S3 的背景信息。本节对本指南的*代码示例*部分中介绍的 [Amazon S3 Java v2 示例](java_s3_code_examples.md)作出了补充。

## 中的 S3 客户端 AWS SDK for Java 2.x
<a name="s3-clients"></a>

 AWS SDK for Java 2.x 提供了不同类型的 S3 客户端。下表显示了不同客户端的差异，可以帮助您确定哪种客户端更适合您的使用案例。


**Amazon S3 客户端的不同类型**  

| S3 客户端 | 简短描述 | 何时使用 | 限制/缺点 | 
| --- | --- | --- | --- | 
|  **AWS 基于CRT的S3客户端** 接口：[S3 AsyncClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3AsyncClient.html) 生成器：[S3 CrtAsyncClientBuilder](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3AsyncClient.html)  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/sdk-for-java/latest/developer-guide/examples-s3.html) 请参阅[使用高性能 S3 客户端： AWS 基于 CRT 的 S3 客户端](crt-based-s3-client.md)。  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/sdk-for-java/latest/developer-guide/examples-s3.html)  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/sdk-for-java/latest/developer-guide/examples-s3.html)  | 
|  **基于 Java 的 S3 异步客户端，*启用了*分段功能** 接口：[S3 AsyncClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3AsyncClient.html) 生成器：[S3 AsyncClientBuilder](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3AsyncClientBuilder.html)  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/sdk-for-java/latest/developer-guide/examples-s3.html) 请参阅[将基于 Java 的 S3 异步客户端配置为使用并行传输](s3-async-client-multipart.md)。  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/sdk-for-java/latest/developer-guide/examples-s3.html)  | 性能不如基于 AWS CRT 的 S3 客户端。 | 
|  **基于 Java 的 S3 异步客户端，*未启用*分段功能** 接口：[S3 AsyncClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3AsyncClient.html) 生成器：[S3 AsyncClientBuilder](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3AsyncClientBuilder.html) |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/sdk-for-java/latest/developer-guide/examples-s3.html)  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/sdk-for-java/latest/developer-guide/examples-s3.html)  |  没有性能优化。  | 
|  **基于 Java 的 S3 同步客户端** 接口：[S3Client](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3Client.html) 生成器：[S3 ClientBuilder](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3ClientBuilder.html) |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/sdk-for-java/latest/developer-guide/examples-s3.html)  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/sdk-for-java/latest/developer-guide/examples-s3.html)  |  没有性能优化。  | 

**注意**  
从版本 2.18.x 及更高版本开始，在包含终端 AWS SDK for Java 2.x 节点覆盖时使用[虚拟托管式寻址](https://docs.aws.amazon.com/AmazonS3/latest/userguide/VirtualHosting.html#virtual-hosted-style-access)。这适用于只要桶名称是有效 DNS 标签的所有情况。  
在客户端生成器中使用 `true` 调用 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3BaseClientBuilder.html#forcePathStyle(java.lang.Boolean](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3BaseClientBuilder.html#forcePathStyle(java.lang.Boolean) 方法，可强制客户端对桶使用路径式寻址。  
以下示例显示了配置了端点覆盖并使用路径式寻址的服务客户端。  

```
S3Client client = S3Client.builder()
                          .region(Region.US_WEST_2)
                          .endpointOverride(URI.create("https://s3.us-west-2.amazonaws.com"))
                          .forcePathStyle(true)
                          .build();
```

**Topics**
+ [SDK 中的 S3 客户端](#s3-clients)
+ [将流上传到 S3](best-practices-s3-uploads.md)
+ [预先签名 URLs](examples-s3-presign.md)
+ [跨区域访问](s3-cross-region.md)
+ [使用校验和实现数据完整性保护](s3-checksums.md)
+ [使用高性能 S3 客户端](crt-based-s3-client.md)
+ [配置并行传输支持](s3-async-client-multipart.md)
+ [传输文件和目录](transfer-manager.md)
+ [S3 事件通知](examples-s3-event-notifications.md)

# 使用将直播上传到 Amazon S3 AWS SDK for Java 2.x
<a name="best-practices-s3-uploads"></a>

当您通过 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3Client.html#putObject(software.amazon.awssdk.services.s3.model.PutObjectRequest,software.amazon.awssdk.core.sync.RequestBody)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3Client.html#putObject(software.amazon.awssdk.services.s3.model.PutObjectRequest,software.amazon.awssdk.core.sync.RequestBody)) 或 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3Client.html#uploadPart(software.amazon.awssdk.services.s3.model.UploadPartRequest,software.amazon.awssdk.core.sync.RequestBody)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3Client.html#uploadPart(software.amazon.awssdk.services.s3.model.UploadPartRequest,software.amazon.awssdk.core.sync.RequestBody)) 方法，使用流将内容上传到 S3 时，可以使用 `RequestBody` 工厂类作为同步 API 来提供流。对于异步 API，`AsyncRequestBody` 是等效的工厂类。

## 哪些方法可上传流？
<a name="s3-stream-upload-methods"></a>

对于同步 API，您可以使用 `RequestBody` 的以下工厂方法来提供流：
+ `[fromInputStream](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/sync/RequestBody.html#fromInputStream(java.io.InputStream,long))(InputStream inputStream, long contentLength)`

  `[fromContentProvider](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/sync/RequestBody.html#fromContentProvider(software.amazon.awssdk.http.ContentStreamProvider,long,java.lang.String))(ContentStreamProvider provider, long contentLength, String mimeType)`
  + `ContentStreamProvider` 有 `fromInputStream(InputStream inputStream)` 工厂方法
+ `[fromContentProvider](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/sync/RequestBody.html#fromContentProvider(software.amazon.awssdk.http.ContentStreamProvider,java.lang.String))(ContentStreamProvider provider, String mimeType)`

对于异步 API，您可以使用 `AsyncRequestBody` 的以下工厂方法：
+ `[fromInputStream](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/async/AsyncRequestBody.html#fromInputStream(java.io.InputStream,java.lang.Long,java.util.concurrent.ExecutorService))(InputStream inputStream, Long contentLength, ExecutorService executor)` 
+ `[fromInputStream](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/async/AsyncRequestBody.html#fromInputStream(software.amazon.awssdk.core.async.AsyncRequestBodyFromInputStreamConfiguration))(AsyncRequestBodyFromInputStreamConfiguration configuration)`
  + 你可以使用 AsyncRequestBodyFromInputStreamConfiguration .Builder 来提供直播
+ `[fromInputStream](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/async/AsyncRequestBody.html#fromInputStream(java.util.function.Consumer))(Consumer<AsyncRequestBodyFromInputStreamConfiguration.Builder> configuration)`
+ `[forBlockingInputStream](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/async/AsyncRequestBody.html#forBlockingInputStream(java.lang.Long))(Long contentLength)`
  + 生成的 `[BlockingInputStreamAsyncRequestBody](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/async/BlockingInputStreamAsyncRequestBody.html)` 包含 `writeInputStream(InputStream inputStream)` 方法，您可以使用该方法来提供流

## 执行上传
<a name="s3-upload-stream-perform"></a>

### 如果您知道流的长度
<a name="s3-stream-upload-supply-content-length"></a>

从前面所示方法的签名中可以看出，大多数方法都接受内容长度参数。

如果您知道以字节为单位的内容长度，请提供确切值：

```
// Always provide the exact content length when it's available.
long contentLength = 1024; // Exact size in bytes.
s3Client.putObject(req -> req
    .bucket("amzn-s3-demo-bucket")
    .key("my-key"),
RequestBody.fromInputStream(inputStream, contentLength));
```

**警告**  
 当您从输入流上传时，如果指定的内容长度与实际字节数不匹配，则您可能会遇到以下情况：  
如果指定的长度太小，则将截断对象
如果指定的长度太大，则上传失败或连接挂起

### 如果您不知道流的长度
<a name="s3-stream-upload-unknown-length"></a>

#### 使用同步 API
<a name="s3-upload-unknown-sync-client"></a>

使用 `fromContentProvider(ContentStreamProvider provider, String mimeType)`：

```
public PutObjectResponse syncClient_stream_unknown_size(String bucketName, String key, InputStream inputStream) {

    S3Client s3Client = S3Client.create();

    RequestBody body = RequestBody.fromContentProvider(ContentStreamProvider.fromInputStream(inputStream), "text/plain");
    PutObjectResponse putObjectResponse = s3Client.putObject(b -> b.bucket(BUCKET_NAME).key(KEY_NAME), body);
    return putObjectResponse;
}
```

由于 SDK 会将整个流缓冲到内存中来计算内容长度，所以在处理大型流时，可能会遇到内存不足的问题。如果您需要使用同步客户端上传大型流，请考虑使用分段 API：

##### 使用同步客户端 API 和分段 API 上传流
<a name="sync-multipart-upload-stream"></a>

```
public static void uploadStreamToS3(String bucketName, String key, InputStream inputStream) {
    // Create S3 client
    S3Client s3Client = S3Client.create();
    try {
        // Step 1: Initiate the multipart upload
        CreateMultipartUploadRequest createMultipartUploadRequest = CreateMultipartUploadRequest.builder()
                .bucket(bucketName)
                .key(key)
                .build();

        CreateMultipartUploadResponse createResponse = s3Client.createMultipartUpload(createMultipartUploadRequest);
        String uploadId = createResponse.uploadId();
        System.out.println("Started multipart upload with ID: " + uploadId);

        // Step 2: Upload parts
        List<CompletedPart> completedParts = new ArrayList<>();
        int partNumber = 1;
        byte[] buffer = new byte[PART_SIZE];
        int bytesRead;

        try {
            while ((bytesRead = readFullyOrToEnd(inputStream, buffer)) > 0) {
                // Create request to upload a part
                UploadPartRequest uploadPartRequest = UploadPartRequest.builder()
                        .bucket(bucketName)
                        .key(key)
                        .uploadId(uploadId)
                        .partNumber(partNumber)
                        .build();

                // If we didn't read a full buffer, create a properly sized byte array
                RequestBody requestBody;
                if (bytesRead < PART_SIZE) {
                    byte[] lastPartBuffer = new byte[bytesRead];
                    System.arraycopy(buffer, 0, lastPartBuffer, 0, bytesRead);
                    requestBody = RequestBody.fromBytes(lastPartBuffer);
                } else {
                    requestBody = RequestBody.fromBytes(buffer);
                }

                // Upload the part and save the response's ETag
                UploadPartResponse uploadPartResponse = s3Client.uploadPart(uploadPartRequest, requestBody);
                CompletedPart part = CompletedPart.builder()
                        .partNumber(partNumber)
                        .eTag(uploadPartResponse.eTag())
                        .build();
                completedParts.add(part);

                System.out.println("Uploaded part " + partNumber + " with size " + bytesRead + " bytes");
                partNumber++;
            }

            // Step 3: Complete the multipart upload
            CompletedMultipartUpload completedMultipartUpload = CompletedMultipartUpload.builder()
                    .parts(completedParts)
                    .build();

            CompleteMultipartUploadRequest completeRequest = CompleteMultipartUploadRequest.builder()
                    .bucket(bucketName)
                    .key(key)
                    .uploadId(uploadId)
                    .multipartUpload(completedMultipartUpload)
                    .build();

            CompleteMultipartUploadResponse completeResponse = s3Client.completeMultipartUpload(completeRequest);
            System.out.println("Multipart upload completed. Object URL: " + completeResponse.location());

        } catch (Exception e) {
            // If an error occurs, abort the multipart upload
            System.err.println("Error during multipart upload: " + e.getMessage());
            AbortMultipartUploadRequest abortRequest = AbortMultipartUploadRequest.builder()
                    .bucket(bucketName)
                    .key(key)
                    .uploadId(uploadId)
                    .build();
            s3Client.abortMultipartUpload(abortRequest);
            System.err.println("Multipart upload aborted");
        } finally {
            try {
                inputStream.close();
            } catch (IOException e) {
                System.err.println("Error closing input stream: " + e.getMessage());
            }
        }
    } finally {
        s3Client.close();
    }
}

/**
 * Reads from the input stream into the buffer, attempting to fill the buffer completely
 * or until the end of the stream is reached.
 *
 * @param inputStream the input stream to read from
 * @param buffer      the buffer to fill
 * @return the number of bytes read, or -1 if the end of the stream is reached before any bytes are read
 * @throws IOException if an I/O error occurs
 */
private static int readFullyOrToEnd(InputStream inputStream, byte[] buffer) throws IOException {
    int totalBytesRead = 0;
    int bytesRead;

    while (totalBytesRead < buffer.length) {
        bytesRead = inputStream.read(buffer, totalBytesRead, buffer.length - totalBytesRead);
        if (bytesRead == -1) {
            break; // End of stream
        }
        totalBytesRead += bytesRead;
    }

    return totalBytesRead > 0 ? totalBytesRead : -1;
}
```

**注意**  
对于大多数使用案例，建议为未知大小的流使用异步客户端 API。这种方法支持并行传输，并提供更简单的编程接口，因为如果流很大，SDK 在处理流时会将其分割成多个分段。  
启用了分段功能的标准 S3 异步客户端和基于 AWS CRT 的 S3 客户端都实现了这种方法。我们将在下一节介绍此方法的示例。

#### 使用异步 API
<a name="s3-stream-upload-unknown-async-client"></a>

您可以将 `contentLength` 参数 `null` 提供给 `fromInputStream(InputStream inputStream, Long contentLength, ExecutorService executor)`

**Example 使用基于 C AWS RT 的异步客户端：**  

```
public PutObjectResponse crtClient_stream_unknown_size(String bucketName, String key, InputStream inputStream) {

    S3AsyncClient s3AsyncClient = S3AsyncClient.crtCreate();
    ExecutorService executor = Executors.newSingleThreadExecutor();
    AsyncRequestBody body = AsyncRequestBody.fromInputStream(inputStream, null, executor);  // 'null' indicates that the
                                                                                            // content length is unknown.
    CompletableFuture<PutObjectResponse> responseFuture =
            s3AsyncClient.putObject(r -> r.bucket(bucketName).key(key), body)
                    .exceptionally(e -> {
                        if (e != null){
                            logger.error(e.getMessage(), e);
                        }
                        return null;
                    });

    PutObjectResponse response = responseFuture.join(); // Wait for the response.
    executor.shutdown();
    return response;
}
```

**Example 使用启用了分段的标准异步客户端：**  

```
public PutObjectResponse asyncClient_multipart_stream_unknown_size(String bucketName, String key, InputStream inputStream) {

    S3AsyncClient s3AsyncClient = S3AsyncClient.builder().multipartEnabled(true).build();
    ExecutorService executor = Executors.newSingleThreadExecutor();
    AsyncRequestBody body = AsyncRequestBody.fromInputStream(inputStream, null, executor); // 'null' indicates that the
                                                                                           // content length is unknown.
    CompletableFuture<PutObjectResponse> responseFuture =
            s3AsyncClient.putObject(r -> r.bucket(bucketName).key(key), body)
                    .exceptionally(e -> {
                        if (e != null) {
                            logger.error(e.getMessage(), e);
                        }
                        return null;
                    });

    PutObjectResponse response = responseFuture.join(); // Wait for the response.
    executor.shutdown();
    return response;
}
```

# 使用 Amazon S3 预签名 URLs
<a name="examples-s3-presign"></a>

预签名 URLs 提供对私有 S3 对象的临时访问权限，而无需用户拥有 AWS 证书或权限。

例如，假设 Alice 有权访问 S3 对象，并希望临时与 Bob 分享对该对象的访问权限。Alice 可以生成预签名的 GET 请求来与 Bob 分享，这样 Bob 就可以下载该对象而无需访问 Alice 的凭证。您可以 URLs 为 HTTP GET 和 HTTP PUT 请求生成预签名。

## 为对象生成预签名 URL，然后下载对象（GET 请求）
<a name="get-presignedobject"></a>

以下示例由两部分组成。
+ 第 1 部分：Alice 为对象生成预签名 URL。
+ 第 2 部分：Bob 使用预签名 URL 下载对象。

### 第 1 部分：生成 URL
<a name="get-presigned-object-part1"></a>

Alice 在 S3 桶中已有一个对象。她使用以下代码生成一个 URL 字符串，Bob 可以在后续的 GET 请求中使用该字符串。

#### 导入
<a name="get-presigned-example-imports"></a>

```
import com.example.s3.util.PresignUrlUtils;
import org.slf4j.Logger;
import software.amazon.awssdk.http.HttpExecuteRequest;
import software.amazon.awssdk.http.HttpExecuteResponse;
import software.amazon.awssdk.http.SdkHttpClient;
import software.amazon.awssdk.http.SdkHttpMethod;
import software.amazon.awssdk.http.SdkHttpRequest;
import software.amazon.awssdk.http.apache.ApacheHttpClient;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
import software.amazon.awssdk.services.s3.model.S3Exception;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest;
import software.amazon.awssdk.services.s3.presigner.model.PresignedGetObjectRequest;
import software.amazon.awssdk.utils.IoUtils;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.UUID;
```

```
    /* Create a pre-signed URL to download an object in a subsequent GET request. */
    public String createPresignedGetUrl(String bucketName, String keyName) {
        try (S3Presigner presigner = S3Presigner.create()) {

            GetObjectRequest objectRequest = GetObjectRequest.builder()
                    .bucket(bucketName)
                    .key(keyName)
                    .build();

            GetObjectPresignRequest presignRequest = GetObjectPresignRequest.builder()
                    .signatureDuration(Duration.ofMinutes(10))  // The URL will expire in 10 minutes.
                    .getObjectRequest(objectRequest)
                    .build();

            PresignedGetObjectRequest presignedRequest = presigner.presignGetObject(presignRequest);
            logger.info("Presigned URL: [{}]", presignedRequest.url().toString());
            logger.info("HTTP method: [{}]", presignedRequest.httpRequest().method());

            return presignedRequest.url().toExternalForm();
        }
    }
```

### 第 2 部分：下载对象
<a name="get-presigned-object-part2"></a>

Bob 使用以下三个代码选项之一来下载对象。或者，他可以使用浏览器来执行 GET 请求。

#### 使用 JDK `HttpURLConnection`（自 v1.1 起）
<a name="get-presigned-example-useHttpUrlConnection"></a>

```
    /* Use the JDK HttpURLConnection (since v1.1) class to do the download. */
    public byte[] useHttpUrlConnectionToGet(String presignedUrlString) {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); // Capture the response body to a byte array.

        try {
            URL presignedUrl = new URL(presignedUrlString);
            HttpURLConnection connection = (HttpURLConnection) presignedUrl.openConnection();
            connection.setRequestMethod("GET");
            // Download the result of executing the request.
            try (InputStream content = connection.getInputStream()) {
                IoUtils.copy(content, byteArrayOutputStream);
            }
            logger.info("HTTP response code is " + connection.getResponseCode());

        } catch (S3Exception | IOException e) {
            logger.error(e.getMessage(), e);
        }
        return byteArrayOutputStream.toByteArray();
    }
```

#### 使用 JDK `HttpClient`（自 v11 起）
<a name="get-presigned-example-useHttpClient"></a>

```
    /* Use the JDK HttpClient (since v11) class to do the download. */
    public byte[] useHttpClientToGet(String presignedUrlString) {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); // Capture the response body to a byte array.

        HttpRequest.Builder requestBuilder = HttpRequest.newBuilder();
        HttpClient httpClient = HttpClient.newHttpClient();
        try {
            URL presignedUrl = new URL(presignedUrlString);
            HttpResponse<InputStream> response = httpClient.send(requestBuilder
                            .uri(presignedUrl.toURI())
                            .GET()
                            .build(),
                    HttpResponse.BodyHandlers.ofInputStream());

            IoUtils.copy(response.body(), byteArrayOutputStream);

            logger.info("HTTP response code is " + response.statusCode());

        } catch (URISyntaxException | InterruptedException | IOException e) {
            logger.error(e.getMessage(), e);
        }
        return byteArrayOutputStream.toByteArray();
    }
```

#### 使用 SDK for Java 中的 `SdkHttpClient`
<a name="get-presigned-example-useSdkHttpClient"></a>

```
    /* Use the AWS SDK for Java SdkHttpClient class to do the download. */
    public byte[] useSdkHttpClientToGet(String presignedUrlString) {

        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); // Capture the response body to a byte array.
        try {
            URL presignedUrl = new URL(presignedUrlString);
            SdkHttpRequest request = SdkHttpRequest.builder()
                    .method(SdkHttpMethod.GET)
                    .uri(presignedUrl.toURI())
                    .build();

            HttpExecuteRequest executeRequest = HttpExecuteRequest.builder()
                    .request(request)
                    .build();

            try (SdkHttpClient sdkHttpClient = ApacheHttpClient.create()) {
                HttpExecuteResponse response = sdkHttpClient.prepareRequest(executeRequest).call();
                response.responseBody().ifPresentOrElse(
                        abortableInputStream -> {
                            try {
                                IoUtils.copy(abortableInputStream, byteArrayOutputStream);
                            } catch (IOException e) {
                                throw new RuntimeException(e);
                            }
                        },
                        () -> logger.error("No response body."));

                logger.info("HTTP Response code is {}", response.httpResponse().statusCode());
            }
        } catch (URISyntaxException | IOException e) {
            logger.error(e.getMessage(), e);
        }
        return byteArrayOutputStream.toByteArray();
    }
```

请查看[完整的示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/d73001daea05266eaa9e074ccb71b9383832369a/javav2/example_code/s3/src/main/java/com/example/s3/GeneratePresignedGetUrlAndRetrieve.java)并[进行测试](https://github.com/awsdocs/aws-doc-sdk-examples/blob/d73001daea05266eaa9e074ccb71b9383832369a/javav2/example_code/s3/src/test/java/com/example/s3/presignurl/GeneratePresignedGetUrlTests.java) GitHub。

## 为上传生成预签名 URL，然后上传文件（PUT 请求）
<a name="put-presignedobject"></a>

以下示例由两部分组成。
+ 第 1 部分：Alice 生成用于上传对象的预签名 URL。
+ 第 2 部分：Bob 使用预签名 URL 上传文件。

### 第 1 部分：生成 URL
<a name="put-presigned-object-part1"></a>

Alice 已有一个 S3 桶。她使用以下代码生成一个 URL 字符串，Bob 可以在后续的 PUT 请求中使用该字符串。

#### 导入
<a name="put-presigned-example-imports"></a>

```
import com.example.s3.util.PresignUrlUtils;
import org.slf4j.Logger;
import software.amazon.awssdk.core.internal.sync.FileContentStreamProvider;
import software.amazon.awssdk.http.HttpExecuteRequest;
import software.amazon.awssdk.http.HttpExecuteResponse;
import software.amazon.awssdk.http.SdkHttpClient;
import software.amazon.awssdk.http.SdkHttpMethod;
import software.amazon.awssdk.http.SdkHttpRequest;
import software.amazon.awssdk.http.apache.ApacheHttpClient;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import software.amazon.awssdk.services.s3.model.S3Exception;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import software.amazon.awssdk.services.s3.presigner.model.PresignedPutObjectRequest;
import software.amazon.awssdk.services.s3.presigner.model.PutObjectPresignRequest;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.Map;
import java.util.UUID;
```

```
    /* Create a presigned URL to use in a subsequent PUT request */
    public String createPresignedUrl(String bucketName, String keyName, Map<String, String> metadata) {
        try (S3Presigner presigner = S3Presigner.create()) {

            PutObjectRequest objectRequest = PutObjectRequest.builder()
                    .bucket(bucketName)
                    .key(keyName)
                    .metadata(metadata)
                    .build();

            PutObjectPresignRequest presignRequest = PutObjectPresignRequest.builder()
                    .signatureDuration(Duration.ofMinutes(10))  // The URL expires in 10 minutes.
                    .putObjectRequest(objectRequest)
                    .build();


            PresignedPutObjectRequest presignedRequest = presigner.presignPutObject(presignRequest);
            String myURL = presignedRequest.url().toString();
            logger.info("Presigned URL to upload a file to: [{}]", myURL);
            logger.info("HTTP method: [{}]", presignedRequest.httpRequest().method());

            return presignedRequest.url().toExternalForm();
        }
    }
```

### 第 2 部分：上传文件对象
<a name="put-presigned-object-part2"></a>

Bob 使用以下三个代码选项之一来上传文件。

#### 使用 JDK `HttpURLConnection`（自 v1.1 起）
<a name="put-presigned-example-useHttpUrlConnection"></a>

```
    /* Use the JDK HttpURLConnection (since v1.1) class to do the upload. */
    public void useHttpUrlConnectionToPut(String presignedUrlString, File fileToPut, Map<String, String> metadata) {
        logger.info("Begin [{}] upload", fileToPut.toString());
        try {
            URL presignedUrl = new URL(presignedUrlString);
            HttpURLConnection connection = (HttpURLConnection) presignedUrl.openConnection();
            connection.setDoOutput(true);
            metadata.forEach((k, v) -> connection.setRequestProperty("x-amz-meta-" + k, v));
            connection.setRequestMethod("PUT");
            OutputStream out = connection.getOutputStream();

            try (RandomAccessFile file = new RandomAccessFile(fileToPut, "r");
                 FileChannel inChannel = file.getChannel()) {
                ByteBuffer buffer = ByteBuffer.allocate(8192); //Buffer size is 8k

                while (inChannel.read(buffer) > 0) {
                    buffer.flip();
                    for (int i = 0; i < buffer.limit(); i++) {
                        out.write(buffer.get());
                    }
                    buffer.clear();
                }
            } catch (IOException e) {
                logger.error(e.getMessage(), e);
            }

            out.close();
            connection.getResponseCode();
            logger.info("HTTP response code is " + connection.getResponseCode());

        } catch (S3Exception | IOException e) {
            logger.error(e.getMessage(), e);
        }
    }
```

#### 使用 JDK `HttpClient`（自 v11 起）
<a name="put-presigned-example-useHttpClient"></a>

```
    /* Use the JDK HttpClient (since v11) class to do the upload. */
    public void useHttpClientToPut(String presignedUrlString, File fileToPut, Map<String, String> metadata) {
        logger.info("Begin [{}] upload", fileToPut.toString());

        HttpRequest.Builder requestBuilder = HttpRequest.newBuilder();
        metadata.forEach((k, v) -> requestBuilder.header("x-amz-meta-" + k, v));

        HttpClient httpClient = HttpClient.newHttpClient();
        try {
            final HttpResponse<Void> response = httpClient.send(requestBuilder
                            .uri(new URL(presignedUrlString).toURI())
                            .PUT(HttpRequest.BodyPublishers.ofFile(Path.of(fileToPut.toURI())))
                            .build(),
                    HttpResponse.BodyHandlers.discarding());

            logger.info("HTTP response code is " + response.statusCode());

        } catch (URISyntaxException | InterruptedException | IOException e) {
            logger.error(e.getMessage(), e);
        }
    }
```

#### 使用 SDK for Java 中的 `SdkHttpClient`
<a name="put-presigned-example-useSdkHttpClient"></a>

```
    /* Use the AWS SDK for Java V2 SdkHttpClient class to do the upload. */
    public void useSdkHttpClientToPut(String presignedUrlString, File fileToPut, Map<String, String> metadata) {
        logger.info("Begin [{}] upload", fileToPut.toString());

        try {
            URL presignedUrl = new URL(presignedUrlString);

            SdkHttpRequest.Builder requestBuilder = SdkHttpRequest.builder()
                    .method(SdkHttpMethod.PUT)
                    .uri(presignedUrl.toURI());
            // Add headers
            metadata.forEach((k, v) -> requestBuilder.putHeader("x-amz-meta-" + k, v));
            // Finish building the request.
            SdkHttpRequest request = requestBuilder.build();

            HttpExecuteRequest executeRequest = HttpExecuteRequest.builder()
                    .request(request)
                    .contentStreamProvider(new FileContentStreamProvider(fileToPut.toPath()))
                    .build();

            try (SdkHttpClient sdkHttpClient = ApacheHttpClient.create()) {
                HttpExecuteResponse response = sdkHttpClient.prepareRequest(executeRequest).call();
                logger.info("Response code: {}", response.httpResponse().statusCode());
            }
        } catch (URISyntaxException | IOException e) {
            logger.error(e.getMessage(), e);
        }
    }
```

请查看[完整的示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/d73001daea05266eaa9e074ccb71b9383832369a/javav2/example_code/s3/src/main/java/com/example/s3/GeneratePresignedUrlAndPutFileWithMetadata.java)并[进行测试](https://github.com/awsdocs/aws-doc-sdk-examples/blob/d73001daea05266eaa9e074ccb71b9383832369a/javav2/example_code/s3/src/test/java/com/example/s3/presignurl/GeneratePresignedPutUrlTests.java) GitHub。

# Amazon S3 的跨区域访问
<a name="s3-cross-region"></a>

您在使用 Amazon Simple Storage Service (Amazon S3) 桶时，通常会知道桶的 AWS 区域。您使用的区域是在您创建 S3 客户端时确定的。

但有时，您可能需要使用一个特定的桶，但您不知道该桶是否位于为 S3 客户端设置的区域中。

您可以使用 SDK 启用跨不同区域访问 S3 桶的功能，而不必进行更多调用来确定桶区域。

## 设置
<a name="s3-cross-region-setup"></a>

SDK 版本 `2.20.111` 已支持跨区域访问。请在 Maven 构建文件中使用此版本或更高版本作为 `s3` 依赖项，如以下代码段所示。

```
<dependency>
  <groupId>software.amazon.awssdk</groupId>
  <artifactId>s3</artifactId>
  <version>2.27.21</version>
</dependency>
```

接下来，在创建 S3 客户端时，启用跨区域访问，如代码段所示。默认情况下，未启用此访问。

```
S3AsyncClient client = S3AsyncClient.builder()
                                    .crossRegionAccessEnabled(true)
                                    .build();
```

## SDK 如何提供跨区域访问
<a name="s3-cross-region-routing"></a>

当您在请求中引用现有桶时（例如使用 `putObject` 方法），SDK 会向为客户端配置的区域发起请求。

如果该特定区域中不存在该桶，则错误响应将包括该桶所在的实际区域。然后，SDK 在第二个请求中使用正确的区域。

为了优化将来对同一个桶的请求，SDK 会在客户端中缓存此区域映射。

## 注意事项
<a name="s3-cross-region-considerations"></a>

启用跨区域桶访问时，请注意，如果桶不在客户端配置的区域中，则可能会导致第一次 API 调用延迟增加。但是，后续调用会受益于缓存的区域信息，从而提高性能。

启用跨区域访问后，对桶的访问权限不会受到影响。无论桶位于哪个区域，用户都必须获得访问桶的授权。

# 使用校验和实现数据完整性保护
<a name="s3-checksums"></a>

Amazon Simple Storage Service (Amazon S3) 允许您在上传对象时指定校验和。当您指定校验和时，校验和与对象一起存储，并且可以在下载对象时验证该校验和。

传输文件时，校验和可提供额外的数据层完整性。使用校验和，您可以通过确认收到文件与原始文件是否匹配来验证数据一致性。有关 Amazon S3 校验和的更多信息，请参阅 [Amazon Simple Storage Service 用户指南](https://docs.aws.amazon.com/AmazonS3/latest/userguide/checking-object-integrity.html)，包括[支持的算法](https://docs.aws.amazon.com/AmazonS3/latest/userguide/checking-object-integrity.html#using-additional-checksums)。

您可以灵活地选择最适合自己需求的算法，并让 SDK 计算校验和。或者，您也可以使用任一受支持的算法，提供预先计算好的校验和值。

**注意**  
从 AWS SDK for Java 2.x的版本 2.30.0 开始，SDK 通过自动计算上传文件的 `CRC32` 校验和来提供默认的完整性保护。如果您未提供预先计算的校验和值，或者没有指定 SDK 计算校验和时应使用的算法，SDK 就会计算此校验和。   
SDK 还提供数据完整性保护的全局设置，您可以在外部进行设置，您可以在[AWS SDKs 和工具参考指南](https://docs.aws.amazon.com/sdkref/latest/guide/feature-dataintegrity.html)中阅读这些设置。

我们在两个请求阶段讨论校验和：上传对象和下载对象。

## 上传对象
<a name="use-service-S3-checksum-upload"></a>

使用 `putObject` 方法上传对象并提供校验和算法时，SDK 会计算指定算法的校验和。

以下代码段展示了上传具有 `SHA256` 校验和的对象的请求。当 SDK 发送请求时，它会计算 `SHA256` 校验和并上传对象。Amazon S3 会计算校验和并将其与 SDK 提供的校验和进行对比，从而验证内容的完整性。然后 Amazon S3 将校验和与对象一起存储。

```
public void putObjectWithChecksum() {
        s3Client.putObject(b -> b
                .bucket(bucketName)
                .key(key)
                .checksumAlgorithm(ChecksumAlgorithm.SHA256),
            RequestBody.fromString("This is a test"));
}
```

如果您未在请求中提供校验和算法，则校验和行为将根据您使用的 SDK 版本而异，具体如下表所示。

**未提供校验和算法时的校验和行为**


| Java SDK 版本 | 校验和行为 | 
| --- | --- | 
| 2.30.0 之前的版本 | SDK 不会自动计算基于 CRC 的校验和，也不会在请求中提供该值。 | 
| 2.30.0 或更高版本 | SDK 使用 `CRC32` 算法计算校验和，并在请求中提供该值。Amazon S3 通过计算自己的 `CRC32` 校验和并将其与 SDK 提供的校验和进行对比，从而验证传输的完整性。如果校验和匹配，则校验和将与对象一起保存。 | 

### 使用预先计算的校验和值
<a name="use-service-S3-checksum-upload-pre"></a>

与请求一起提供的预先计算校验和值会禁用 SDK 的自动计算，而是使用提供的值。

以下示例显示了具有预先计算的 SHA256校验和的请求。

```
    public void putObjectWithPrecalculatedChecksum(String filePath) {
        String checksum = calculateChecksum(filePath, "SHA-256");

        s3Client.putObject((b -> b
                .bucket(bucketName)
                .key(key)
                .checksumSHA256(checksum)),
            RequestBody.fromFile(Paths.get(filePath)));
    }
```

如果 Amazon S3 确定指定算法的校验和值不正确，服务就会返回错误响应。

### 分段上传
<a name="use-service-S3-checksum-upload-multi"></a>

您也可以将校验和用于分段上传。

 适用于 Java 2.x 的 SDK 提供了两个选项，用于在分段上传中使用校验和。第一个选项使用 `S3TransferManager`。

以下传输管理器示例指定了上传 SHA1 算法。

```
    public void multipartUploadWithChecksumTm(String filePath) {
        S3TransferManager transferManager = S3TransferManager.create();
        UploadFileRequest uploadFileRequest = UploadFileRequest.builder()
            .putObjectRequest(b -> b
                .bucket(bucketName)
                .key(key)
                .checksumAlgorithm(ChecksumAlgorithm.SHA1))
            .source(Paths.get(filePath))
            .build();
        FileUpload fileUpload = transferManager.uploadFile(uploadFileRequest);
        fileUpload.completionFuture().join();
        transferManager.close();
    }
```

如果您在使用传输管理器上传时未提供校验和算法，则 SDK 会根据 `CRC32` 算法自动计算校验和。SDK 会对所有版本的 SDK 执行此计算。

第二个选项使用 [`S3Client` API](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3Client.html)（或 [`S3AsyncClient` API](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3AsyncClient.html)）来执行分段上传。如果用此方法来指定校验和，则您必须指定在启动上传时使用的算法。您还必须为每个分段请求指定算法，并提供每个分段在上传后计算得出的校验和。

```
    public void multipartUploadWithChecksumS3Client(String filePath) {
        ChecksumAlgorithm algorithm = ChecksumAlgorithm.CRC32;

        // Initiate the multipart upload.
        CreateMultipartUploadResponse createMultipartUploadResponse = s3Client.createMultipartUpload(b -> b
            .bucket(bucketName)
            .key(key)
            .checksumAlgorithm(algorithm)); // Checksum specified on initiation.
        String uploadId = createMultipartUploadResponse.uploadId();

        // Upload the parts of the file.
        int partNumber = 1;
        List<CompletedPart> completedParts = new ArrayList<>();
        ByteBuffer bb = ByteBuffer.allocate(1024 * 1024 * 5); // 5 MB byte buffer

        try (RandomAccessFile file = new RandomAccessFile(filePath, "r")) {
            long fileSize = file.length();
            long position = 0;
            while (position < fileSize) {
                file.seek(position);
                long read = file.getChannel().read(bb);

                bb.flip(); // Swap position and limit before reading from the buffer.
                UploadPartRequest uploadPartRequest = UploadPartRequest.builder()
                    .bucket(bucketName)
                    .key(key)
                    .uploadId(uploadId)
                    .checksumAlgorithm(algorithm) // Checksum specified on each part.
                    .partNumber(partNumber)
                    .build();

                UploadPartResponse partResponse = s3Client.uploadPart(
                    uploadPartRequest,
                    RequestBody.fromByteBuffer(bb));

                CompletedPart part = CompletedPart.builder()
                    .partNumber(partNumber)
                    .checksumCRC32(partResponse.checksumCRC32()) // Provide the calculated checksum.
                    .eTag(partResponse.eTag())
                    .build();
                completedParts.add(part);

                bb.clear();
                position += read;
                partNumber++;
            }
        } catch (IOException e) {
            System.err.println(e.getMessage());
        }

        // Complete the multipart upload.
        s3Client.completeMultipartUpload(b -> b
            .bucket(bucketName)
            .key(key)
            .uploadId(uploadId)
            .multipartUpload(CompletedMultipartUpload.builder().parts(completedParts).build()));
    }
```

[完整示例和[测试](https://github.com/awsdocs/aws-doc-sdk-examples/blob/main/javav2/example_code/s3/src/test/java/com/example/s3/PerformMultiPartUploadTests.java)的代码](https://github.com/awsdocs/aws-doc-sdk-examples/blob/main/javav2/example_code/s3/src/main/java/com/example/s3/PerformMultiPartUpload.java)位于 GitHub 代码示例存储库中。

## 下载对象
<a name="use-service-S3-checksum-download"></a>

使用 [getObject](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3Client.html#getObject(software.amazon.awssdk.services.s3.model.GetObjectRequest)) 方法下载对象时，在以下情况下 SDK 会自动验证校验和：如果适用于 `GetObjectRequest` 的生成器的 `checksumMode` 方法设置为 `ChecksumMode.ENABLED`。

以下代码段中的请求引导 SDK 通过计算校验和并比较值来验证响应中的校验和。

```
    public GetObjectResponse getObjectWithChecksum() {
        return s3Client.getObject(b -> b
                        .bucket(bucketName)
                        .key(key)
                        .checksumMode(ChecksumMode.ENABLED))
                .response();
    }
```

**注意**  
如果上传对象时没有使用校验和，则不会进行验证。

## 其他校验和计算选项
<a name="S3-checsum-calculation-options"></a>

**注意**  
为了验证已传输数据的数据完整性以及识别任何传输错误，我们建议用户为校验和计算选项保留 SDK 默认设置。默认情况下，SDK 会为许多 S3 操作（包括 `PutObject` 和 `GetObject`）添加这项重要检查。

但如果您希望在使用 Amazon S3 时仅进行最少的校验和验证，则可以通过更改默认配置设置来禁用许多检查。

### 除非必要，否则请禁用自动校验和计算
<a name="S3-minimize-checksum-calc-global"></a>

对于支持校验和的操作（例如 `PutObject` 和 `GetObject`），您可以禁用 SDK 的自动校验和计算。但某些 S3 操作需要校验和计算；您不能为这些操作禁用校验和计算。

SDK 为请求的有效载荷和响应的有效载荷的校验和计算提供了单独的设置。

下面的列表说明了可用于在不同范围内更大限度地减少校验和计算的设置。
+ **所有应用程序范围**-通过更改环境变量或共享 AWS `config`文件中的配置`credentials`文件中的设置，所有应用程序都可以使用这些设置。除非在应用程序或服务客户端范围内被覆盖，否则这些设置会影响所有 AWS SDK 应用程序中的所有服务客户端。
  + 在配置文件中添加设置：

    ```
    [default]
    request_checksum_calculation = WHEN_REQUIRED
    response_checksum_validation = WHEN_REQUIRED
    ```
  + 添加环境变量：

    ```
    AWS_REQUEST_CHECKSUM_CALCULATION=WHEN_REQUIRED
    AWS_RESPONSE_CHECKSUM_VALIDATION=WHEN_REQUIRED
    ```
+ **当前应用程序范围** - 您可以将 Java 系统属性 `aws.requestChecksumCalculation` 设置为 `WHEN_REQUIRED`，以便限制校验和计算。响应的相应系统属性为 `aws.responseChecksumValidation`。

  除非在服务客户端创建期间已覆盖，否则这些设置会影响应用程序中的所有 SDK 服务客户端。

  在应用程序启动时设置系统属性：

  ```
  import software.amazon.awssdk.core.SdkSystemSetting;
  import software.amazon.awssdk.core.checksums.RequestChecksumCalculation;
  import software.amazon.awssdk.core.checksums.ResponseChecksumValidation;
  import software.amazon.awssdk.services.s3.S3Client;
  
  class DemoClass {
      public static void main(String[] args) {
  
          System.setProperty(SdkSystemSetting.AWS_REQUEST_CHECKSUM_CALCULATION.property(), // Resolves to "aws.requestChecksumCalculation".
                  "WHEN_REQUIRED");
          System.setProperty(SdkSystemSetting.AWS_RESPONSE_CHECKSUM_VALIDATION.property(), // Resolves to "aws.responseChecksumValidation".
                  "WHEN_REQUIRED");
  
          S3Client s3Client = S3Client.builder().build();
  
          // Use s3Client.
      }
  }
  ```
+ **单个 S3 服务客户端范围** - 您可以使用生成器方法配置单个 S3 服务客户端，使其仅计算最少必要数量的校验和：

  ```
  import software.amazon.awssdk.core.checksums.RequestChecksumCalculation;
  import software.amazon.awssdk.services.s3.S3Client;
  
  public class RequiredChecksums {
      public static void main(String[] args) {
          S3Client s3 = S3Client.builder()
                  .requestChecksumCalculation(RequestChecksumCalculation.WHEN_REQUIRED)
                  .responseChecksumValidation(ResponseChecksumValidation.WHEN_REQUIRED)
                  .build();
  
          // Use s3Client. 
      }
  // ...
  }
  ```

### 使用以简`LegacyMd5Plugin`化 MD5 兼容性
<a name="S3-checksum-legacy-md5"></a>

随着 2.30.0 版中 CRC32 校验和行为的发布，SDK 停止计算所需操作的 MD5 校验和。

如果您需要对 S3 操作进行传统 MD5 校验和行为，则可以使用 SDK 版本 2.31.32 中发布的。`LegacyMd5Plugin`

当您需要保持与依赖传统 MD5 校验和行为的应用程序的兼容性时，尤其`LegacyMd5Plugin`是在使用与 S3 兼容的第三方存储提供程序（例如与 S3A 文件系统连接器（Apache Spark、Iceberg）一起使用的存储提供程序时，尤其如此。

要使用 `LegacyMd5Plugin`，请将其添加到 S3 客户端生成器中：

```
// For synchronous S3 client.
S3Client s3Client = S3Client.builder()
                           .addPlugin(LegacyMd5Plugin.create())
                           .build();

// For asynchronous S3 client.
S3AsyncClient asyncClient = S3AsyncClient.builder()
                                       .addPlugin(LegacyMd5Plugin.create())
                                       .build();
```

如果要向需要 MD5 校验和的操作添加校验和，并希望跳过为支持校验和但不是必需的操作添加 SDK 默认校验和，则可以启用选项和 as。`ClientBuilder` `requestChecksumCalculation` `responseChecksumValidation` `WHEN_REQUIRED`这样就只会将 SDK 默认校验和添加到需要校验和的操作中：

```
// Use the `LegacyMd5Plugin` with `requestChecksumCalculation` and `responseChecksumValidation` set to WHEN_REQUIRED.
S3AsyncClient asyncClient = S3AsyncClient.builder()
                                       .addPlugin(LegacyMd5Plugin.create())
                                       .requestChecksumCalculation(RequestChecksumCalculation.WHEN_REQUIRED)
                                       .responseChecksumValidation(ResponseChecksumValidation.WHEN_REQUIRED)
                                       .build();
```

当使用与 S3 兼容的第三方存储系统时，此配置特别有用，这些存储系统可能不完全支持较新的校验和算法，但某些操作仍需要 MD5 校验和。

# 使用高性能 S3 客户端： AWS 基于 CRT 的 S3 客户端
<a name="crt-based-s3-client"></a>

 AWS 基于 CRT 的 S3 客户端（建立在[AWS 公共运行时 (CRT) 之上）](https://docs.aws.amazon.com/sdkref/latest/guide/common-runtime.html)是替代的 S3 异步客户端。它通过自动使用 Amazon S3 的[分段上传 API](https://docs.aws.amazon.com/AmazonS3/latest/userguide/mpuoverview.html) 和[字节范围提取](https://docs.aws.amazon.com/AmazonS3/latest/userguide/optimizing-performance-guidelines.html#optimizing-performance-guidelines-get-range)，将对象传入或传出 Amazon Simple Storage Service (Amazon S3)，从而提高了性能和可靠性。

 AWS 基于 CRT 的 S3 客户端可提高网络故障时的传输可靠性。通过重试文件传输中失败的各个分段，而无需从头开始重新启动传输，从而提高可靠性。

此外， AWS 基于 CRT 的 S3 客户端还提供了增强的连接池和域名系统 (DNS) 负载平衡，这也提高了吞吐量。

您可以使用 AWS 基于 CRT 的 S3 客户端来代替 SDK 的标准 S3 异步客户端，并立即利用其提高的吞吐量。

**重要**  
 AWS 基于 CRT 的 S3 客户端目前不支持在客户端级别和请求级别[收集 SDK 指标](metrics.md)。

**AWS SDK 中基于 CRT 的组件**

本主题中介绍 AWS 的基于 CRT 的 *S3* 客户端和 AWS 基于 CRT 的 *HTTP* 客户端是软件开发工具包中的不同组件。

**AWS 基于 CRT 的 S3 客户端**是 S [3 AsyncClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3AsyncClient.html) 接口的实现，用于与 Amazon S3 服务配合使用。它是基于 Java 的 `S3AsyncClient` 接口实现的替代方案，具有多种优势。

[AWS 基于 CRT 的 HTTP 客户端](http-configuration-crt.md)是该[SdkAsyncHttpClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/http/async/SdkAsyncHttpClient.html)接口的实现，用于一般的 HTTP 通信。它是 Netty `SdkAsyncHttpClient` 接口实现的替代方案，具有多种优势。

尽管两个组件都使用[AWS 公共运行时](https://docs.aws.amazon.com/sdkref/latest/guide/common-runtime.html)中的库，但 AWS 基于 CRT 的 S3 客户端使用 [aws-c-s3 库](https://github.com/awslabs/aws-c-s3)并支持 [S3 分段上传 API](https://docs.aws.amazon.com/AmazonS3/latest/userguide/mpuoverview.html) 功能。由于 AWS 基于 CRT 的 HTTP 客户端仅供一般用途，因此它不支持 S3 分段上传 API 功能。

## 添加依赖项以使用 AWS 基于 CRT 的 S3 客户端
<a name="crt-based-s3-client-depend"></a>

要使用 AWS 基于 CRT 的 S3 客户端，请将以下两个依赖项添加到您的 Maven 项目文件中。示例显示了要使用的最低版本。在 Maven Central 存储库中搜索 [s3](https://central.sonatype.com/artifact/software.amazon.awssdk/s3) 和 [aws-crt](https://central.sonatype.com/artifact/software.amazon.awssdk.crt/aws-crt) 构件的最新版本。

```
<dependency>
  <groupId>software.amazon.awssdk</groupId>
  <artifactId>s3</artifactId>
  <version>2.27.21</version>
</dependency>
<dependency>
  <groupId>software.amazon.awssdk.crt</groupId>
  <artifactId>aws-crt</artifactId>
  <version>0.30.11</version>
</dependency>
```

## 创建 AWS 基于 CRT 的 S3 客户端的实例
<a name="crt-based-s3-client-create"></a>

 使用默认设置创建 AWS 基于 CRT 的 S3 客户端的实例，如以下代码片段所示。

```
S3AsyncClient s3AsyncClient = S3AsyncClient.crtCreate();
```

要配置客户端，请使用 AWS CRT 客户端生成器。您可以通过更改构建器方法从标准 S3 异步客户端切换到 AWS 基于 CRT 的客户端。

```
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3AsyncClient;


S3AsyncClient s3AsyncClient = 
        S3AsyncClient.crtBuilder()
                     .credentialsProvider(DefaultCredentialsProvider.create())
                     .region(Region.US_WEST_2)
                     .targetThroughputInGbps(20.0)
                     .minimumPartSizeInBytes(8 * 1025 * 1024L)
                     .build();
```

**注意**  
 AWS CRT 客户端生成器目前可能不支持标准生成器中的某些设置。通过调用 `S3AsyncClient#builder()` 获取标准生成器。

## 使用 AWS 基于 CRT 的 S3 客户端
<a name="crt-based-s3-client-use"></a>

使用 AWS 基于 CRT 的 S3 客户端调用 Amazon S3 API 操作。以下示例演示了可通过提供的[PutObject](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3AsyncClient.html#putObject(java.util.function.Consumer,software.amazon.awssdk.core.async.AsyncRequestBody))和[GetObject](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3AsyncClient.html#getObject(java.util.function.Consumer,software.amazon.awssdk.core.async.AsyncResponseTransformer))操作 适用于 Java 的 AWS SDK。

```
import software.amazon.awssdk.core.async.AsyncRequestBody;
import software.amazon.awssdk.core.async.AsyncResponseTransformer;
import software.amazon.awssdk.services.s3.S3AsyncClient;
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
import software.amazon.awssdk.services.s3.model.PutObjectResponse;


S3AsyncClient s3Client = S3AsyncClient.crtCreate();

// Upload a local file to Amazon S3.
PutObjectResponse putObjectResponse = 
      s3Client.putObject(req -> req.bucket(<BUCKET_NAME>)
                                   .key(<KEY_NAME>),
                        AsyncRequestBody.fromFile(Paths.get(<FILE_NAME>)))
              .join();

// Download an object from Amazon S3 to a local file.
GetObjectResponse getObjectResponse = 
      s3Client.getObject(req -> req.bucket(<BUCKET_NAME>)
                                   .key(<KEY_NAME>),
                        AsyncResponseTransformer.toFile(Paths.get(<FILE_NAME>)))
              .join();
```

## 上传未知大小的流
<a name="crt-stream-unknown-size"></a>

 AWS AWS 基于 CRT 的 S3 客户端的一个显著优势是它能够高效处理大小未知的输入流。当您需要从无法事先确定总大小的来源上传数据时，这特别有用。

```
public PutObjectResponse crtClient_stream_unknown_size(String bucketName, String key, InputStream inputStream) {

    S3AsyncClient s3AsyncClient = S3AsyncClient.crtCreate();
    ExecutorService executor = Executors.newSingleThreadExecutor();
    AsyncRequestBody body = AsyncRequestBody.fromInputStream(inputStream, null, executor);  // 'null' indicates that the
                                                                                            // content length is unknown.
    CompletableFuture<PutObjectResponse> responseFuture =
            s3AsyncClient.putObject(r -> r.bucket(bucketName).key(key), body)
                    .exceptionally(e -> {
                        if (e != null){
                            logger.error(e.getMessage(), e);
                        }
                        return null;
                    });

    PutObjectResponse response = responseFuture.join(); // Wait for the response.
    executor.shutdown();
    return response;
}
```

此功能有助于避免传统上传中的常见问题，即不正确的内容长度规格会导致对象被截断或上传失败。

## 配置限制
<a name="crt-based-s3-client-limitations"></a>

 AWS 基于 CRT 的 S3 客户端和基于 Java 的 S3 异步客户端[提供了类似的功能](examples-s3.md#s3-clients)，而 AWS 基于 CRT 的 S3 客户端则提供了性能优势。但是， AWS 基于 CRT 的 S3 客户端缺少基于 Java 的 S3 异步客户端所具有的配置设置。这些设置包括：
+ *客户端级别配置：*API 调用尝试超时、压缩执行拦截器、指标发布者、自定义执行属性、自定义高级选项、自定义计划执行器服务、自定义标头
+ *请求级别配置：*自定义签名者、API 调用尝试超时

有关配置差异的完整列表，请参阅 API 参考。


| 基于 Java 的 S3 异步客户端 | AWS 基于CRT的S3客户端 | 
| --- | --- | 
| 客户端级别配置[\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/sdk-for-java/latest/developer-guide/crt-based-s3-client.html)请求级别配置[\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/sdk-for-java/latest/developer-guide/crt-based-s3-client.html) | 客户端级别配置[\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/sdk-for-java/latest/developer-guide/crt-based-s3-client.html)请求级别配置[\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/sdk-for-java/latest/developer-guide/crt-based-s3-client.html) | 

# 将基于 Java 的 S3 异步客户端配置为使用并行传输
<a name="s3-async-client-multipart"></a>

从版本 2.27.5 开始，基于 Java 的标准 S3 异步客户端支持自动并行传输（分段上传和下载）。在创建基于 Java 的 S3 异步客户端时，您可以配置对并行传输的支持。

本节介绍如何启用并行传输以及如何自定义配置。

## 创建 `S3AsyncClient` 的实例
<a name="s3-async-client-multipart-create"></a>

当您不在[生成器](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3AsyncClientBuilder.html)上调用任何 `multipart*` 方法的情况下创建 `S3AsyncClient` 实例时，不会启用并行传输。以下每条语句都创建一个基于 Java 的 S3 异步客户端，不支持分段上传和下载。

### 在*没有*分段支持的情况下创建
<a name="s3-async-client-mp-off"></a>

**Example**  

```
import software.amazon.awssdk.auth.credentials.ProcessCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3AsyncClient;


S3AsyncClient s3Client = S3AsyncClient.create();

S3AsyncClient s3Client2 = S3AsyncClient.builder().build();

S3AsyncClient s3Client3 = S3AsyncClient.builder()
        .credentialsProvider(ProcessCredentialsProvider.builder().build())
        .region(Region.EU_NORTH_1)
        .build();
```

### 在*有*分段支持的情况下创建
<a name="s3-async-client-mp-on"></a>

要使用默认设置启用并行传输，请在生成器上调用 `multipartEnabled` 并传入 `true`，如下例所示。

**Example**  

```
S3AsyncClient s3AsyncClient2 = S3AsyncClient.builder()
        .multipartEnabled(true)
        .build();
```

`thresholdInBytes` 和 `minimumPartSizeInBytes` 设置的默认值为 8 MiB。

如果您自定义分段设置，则会自动启用并行传输，如下所示。

**Example**  

```
import software.amazon.awssdk.services.s3.S3AsyncClient;
import static software.amazon.awssdk.transfer.s3.SizeConstant.MB;


S3AsyncClient s3AsyncClient2 = S3AsyncClient.builder()
        .multipartConfiguration(b -> b
                .thresholdInBytes(16 * MB)
                .minimumPartSizeInBytes(10 * MB))
        .build();
```

## 上传未知大小的流
<a name="java-async-client-stream-unknown-size"></a>

启用了分段功能的基于 Java 的 S3 异步客户端可以有效地处理事先不知道总大小的输入流：

```
public PutObjectResponse asyncClient_multipart_stream_unknown_size(String bucketName, String key, InputStream inputStream) {

    S3AsyncClient s3AsyncClient = S3AsyncClient.builder().multipartEnabled(true).build();
    ExecutorService executor = Executors.newSingleThreadExecutor();
    AsyncRequestBody body = AsyncRequestBody.fromInputStream(inputStream, null, executor); // 'null' indicates that the
                                                                                           // content length is unknown.
    CompletableFuture<PutObjectResponse> responseFuture =
            s3AsyncClient.putObject(r -> r.bucket(bucketName).key(key), body)
                    .exceptionally(e -> {
                        if (e != null) {
                            logger.error(e.getMessage(), e);
                        }
                        return null;
                    });

    PutObjectResponse response = responseFuture.join(); // Wait for the response.
    executor.shutdown();
    return response;
}
```

这种方法可以防止手动指定错误的内容长度时可能出现的问题，例如对象截断或上传失败。

# 使用 Amazon S3 Transfer Manager 传输文件和目录
<a name="transfer-manager"></a>

Amazon S3 Transfer Manager 是针对 AWS SDK for Java 2.x的一款开源高级文件传输工具。可以使用它将文件和目录传入和传出 Amazon Simple Storage Service (Amazon S3)。

在[基于AWS CRT 的 S3 客户端](crt-based-s3-client.md)或[启用了分段的基于 Java 的标准 S3 异步客户端](s3-async-client-multipart.md)基础上构建时，S3 Transfer Manager 可以利用[分段上传 API](https://docs.aws.amazon.com/AmazonS3/latest/userguide/mpuoverview.html) 和[字节范围提取](https://docs.aws.amazon.com/AmazonS3/latest/userguide/optimizing-performance-guidelines.html#optimizing-performance-guidelines-get-range)等性能改进。

使用 S3 Transfer Manager，您还可以实时监控传输进度，并暂停传输以便日后执行。

## 开始使用
<a name="transfer-manager-prerequisites"></a>

### 将依赖项添加到构建文件中
<a name="transfer-manager-add-dependency"></a>

要在使用 S3 Transfer Manager 时获得增强的分段性能，请使用必要的依赖项配置构建文件。

------
#### [ Use the AWS CRT-based S3 client ]

```
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>software.amazon.awssdk</groupId>
            <artifactId>bom</artifactId>
            <version>2.27.211</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
<dependencies>
    <dependency>
        <groupId>software.amazon.awssdk</groupId>
        <artifactId>s3-transfer-manager</artifactId>
    </dependency>
    <dependency>
        <groupId>software.amazon.awssdk.crt</groupId>
        <artifactId>aws-crt</artifactId>
        <version>0.29.1432</version>
    </dependency>
</dependencies>
```

1 [最新版本](https://central.sonatype.com/artifact/software.amazon.awssdk/bom)。2[最新版本](https://central.sonatype.com/artifact/software.amazon.awssdk.crt/aws-crt)。

------
#### [ Use the Java-based S3 async client ]

```
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>software.amazon.awssdk</groupId>
            <artifactId>bom</artifactId>
            <version>2.27.211</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
<dependencies>
    <dependency>
        <groupId>software.amazon.awssdk</groupId>
        <artifactId>s3-transfer-manager</artifactId>
    </dependency>
</dependencies>
```

1 [最新版本](https://central.sonatype.com/artifact/software.amazon.awssdk/bom)。

------

### 创建 S3 Transfer Manager 的实例
<a name="transfer-manager-create"></a>

要启用并行传输，必须传入 AWS 基于 CRT 的 S3 客户端或启用了多部分功能的基于 Java 的 S3 异步客户端。以下示例演示如何使用自定义设置来配置 S3 Transfer Manager。

------
#### [ Use the AWS CRT-based S3 client ]

```
        S3AsyncClient s3AsyncClient = S3AsyncClient.crtBuilder()
                .credentialsProvider(DefaultCredentialsProvider.create())
                .region(Region.US_EAST_1)
                .targetThroughputInGbps(20.0)
                .minimumPartSizeInBytes(8 * MB)
                .build();

        S3TransferManager transferManager = S3TransferManager.builder()
                .s3Client(s3AsyncClient)
                .build();
```

------
#### [ Use the Java-based S3 async client ]

如果构建文件中未包含 `aws-crt` 依赖项，则 S3 Transfer Manager 将在适用于 Java 的 SDK 2.x 中使用的基于 Java 的标准 S3 异步客户端的基础上构建。

**S3 客户端的自定义配置 - 需要启用分段功能**

```
        S3AsyncClient s3AsyncClient = S3AsyncClient.builder()
                .multipartEnabled(true)
                .credentialsProvider(DefaultCredentialsProvider.create())
                .region(Region.US_EAST_1)
                .build();

        S3TransferManager transferManager = S3TransferManager.builder()
                .s3Client(s3AsyncClient)
                .build();
```

**没有 S3 客户端配置 - 自动启用分段支持**

```
S3TransferManager transferManager = S3TransferManager.create();
```

------

## 将文件上传到 S3 桶
<a name="transfer-manager-upload"></a>

以下示例显示了文件上传示例 [LoggingTransferListener](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/progress/LoggingTransferListener.html)，以及记录上传进度的可选用法。

要使用 S3 Transfer Manager 将文件上传到 Amazon S3，请将 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/model/UploadFileRequest.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/model/UploadFileRequest.html) 对象传递给 `S3TransferManager` 的 [uploadFile](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/S3TransferManager.html#uploadFile(software.amazon.awssdk.transfer.s3.model.UploadFileRequest)) 方法。

从该`uploadFile`方法返回的[FileUpload](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/model/FileUpload.html)对象表示上传过程。请求完成后，该[CompletedFileUpload](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/model/CompletedFileUpload.html)对象将包含有关上传的信息。

```
    public void trackUploadFile(S3TransferManager transferManager, String bucketName,
                             String key, URI filePathURI) {
        UploadFileRequest uploadFileRequest = UploadFileRequest.builder()
                .putObjectRequest(b -> b.bucket(bucketName).key(key))
                .addTransferListener(LoggingTransferListener.create())  // Add listener.
                .source(Paths.get(filePathURI))
                .build();

        FileUpload fileUpload = transferManager.uploadFile(uploadFileRequest);

        fileUpload.completionFuture().join();
        /*
            The SDK provides a LoggingTransferListener implementation of the TransferListener interface.
            You can also implement the interface to provide your own logic.

            Configure log4J2 with settings such as the following.
                <Configuration status="WARN">
                    <Appenders>
                        <Console name="AlignedConsoleAppender" target="SYSTEM_OUT">
                            <PatternLayout pattern="%m%n"/>
                        </Console>
                    </Appenders>

                    <Loggers>
                        <logger name="software.amazon.awssdk.transfer.s3.progress.LoggingTransferListener" level="INFO" additivity="false">
                            <AppenderRef ref="AlignedConsoleAppender"/>
                        </logger>
                    </Loggers>
                </Configuration>

            Log4J2 logs the progress. The following is example output for a 21.3 MB file upload.
                Transfer initiated...
                |                    | 0.0%
                |====                | 21.1%
                |============        | 60.5%
                |====================| 100.0%
                Transfer complete!
        */
    }
```

### 导入
<a name="transfer-manager-upload-imports"></a>

```
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import software.amazon.awssdk.transfer.s3.S3TransferManager;
import software.amazon.awssdk.transfer.s3.model.CompletedFileUpload;
import software.amazon.awssdk.transfer.s3.model.FileUpload;
import software.amazon.awssdk.transfer.s3.model.UploadFileRequest;
import software.amazon.awssdk.transfer.s3.progress.LoggingTransferListener;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Paths;
import java.util.UUID;
```

## 从 S3 桶下载文件
<a name="transfer-manager-download"></a>

以下示例显示了下载示例以及记录下载进度的可选用法。[LoggingTransferListener](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/progress/LoggingTransferListener.html)

要使用 S3 传输管理器从 S3 存储桶下载对象，请生成一个[DownloadFileRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/model/DownloadFileRequest.html)对象并将其传递给 [downloadFile](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/S3TransferManager.html#downloadFile(software.amazon.awssdk.transfer.s3.model.DownloadFileRequest)) 方法。

的`downloadFile`方法返回`S3TransferManager`的[FileDownload](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/model/FileDownload.html)对象表示文件传输。下载完成后，[CompletedFileDownload](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/model/CompletedFileDownload.html)包含对下载相关信息的访问权限。

```
    public void trackDownloadFile(S3TransferManager transferManager, String bucketName,
                             String key, String downloadedFileWithPath) {
        DownloadFileRequest downloadFileRequest = DownloadFileRequest.builder()
                .getObjectRequest(b -> b.bucket(bucketName).key(key))
                .addTransferListener(LoggingTransferListener.create())  // Add listener.
                .destination(Paths.get(downloadedFileWithPath))
                .build();

        FileDownload downloadFile = transferManager.downloadFile(downloadFileRequest);

        CompletedFileDownload downloadResult = downloadFile.completionFuture().join();
        /*
            The SDK provides a LoggingTransferListener implementation of the TransferListener interface.
            You can also implement the interface to provide your own logic.

            Configure log4J2 with settings such as the following.
                <Configuration status="WARN">
                    <Appenders>
                        <Console name="AlignedConsoleAppender" target="SYSTEM_OUT">
                            <PatternLayout pattern="%m%n"/>
                        </Console>
                    </Appenders>

                    <Loggers>
                        <logger name="software.amazon.awssdk.transfer.s3.progress.LoggingTransferListener" level="INFO" additivity="false">
                            <AppenderRef ref="AlignedConsoleAppender"/>
                        </logger>
                    </Loggers>
                </Configuration>

            Log4J2 logs the progress. The following is example output for a 21.3 MB file download.
                Transfer initiated...
                |=======             | 39.4%
                |===============     | 78.8%
                |====================| 100.0%
                Transfer complete!
        */
    }
```

### 导入
<a name="transfer-manager-download-import"></a>

```
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import software.amazon.awssdk.core.exception.SdkException;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.transfer.s3.S3TransferManager;
import software.amazon.awssdk.transfer.s3.model.CompletedFileDownload;
import software.amazon.awssdk.transfer.s3.model.DownloadFileRequest;
import software.amazon.awssdk.transfer.s3.model.FileDownload;
import software.amazon.awssdk.transfer.s3.progress.LoggingTransferListener;

import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.UUID;
```

## 将 Amazon S3 对象复制到另一个桶。
<a name="transfer-manager-copy"></a>

以下示例演示如何使用 S3 Transfer Manager 复制对象。

要开始将对象从 S3 存储桶复制到另一个存储桶，请创建一个基本[CopyObjectRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/model/CopyObjectRequest.html)实例。

接下来，将基本内容封装`CopyObjectRequest`在 S3 传输管理器可以使用的文件中。[CopyRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/model/CopyRequest.html)

`S3TransferManager` 的 `copy` 方法返回的 `Copy` 对象表示复制进程。复制过程完成后，该[CompletedCopy](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/model/CompletedCopy.html)对象将包含有关响应的详细信息。

```
    public String copyObject(S3TransferManager transferManager, String bucketName,
            String key, String destinationBucket, String destinationKey) {
        CopyObjectRequest copyObjectRequest = CopyObjectRequest.builder()
                .sourceBucket(bucketName)
                .sourceKey(key)
                .destinationBucket(destinationBucket)
                .destinationKey(destinationKey)
                .build();

        CopyRequest copyRequest = CopyRequest.builder()
                .copyObjectRequest(copyObjectRequest)
                .build();

        Copy copy = transferManager.copy(copyRequest);

        CompletedCopy completedCopy = copy.completionFuture().join();
        return completedCopy.response().copyObjectResult().eTag();
    }
```

**注意**  
要使用 S3 传输管理器执行跨区域复制，请在 AWS 基于 CRT 的 S3 客户端生成器`crossRegionAccessEnabled`上启用，如以下代码段所示。  

```
S3AsyncClient s3AsyncClient = S3AsyncClient.crtBuilder()
                .crossRegionAccessEnabled(true)
                .build();

S3TransferManager transferManager = S3TransferManager.builder()
                .s3Client(s3AsyncClient)
                .build();
```

### 导入
<a name="transfer-manager-copy-import"></a>

```
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.services.s3.model.CopyObjectRequest;
import software.amazon.awssdk.transfer.s3.S3TransferManager;
import software.amazon.awssdk.transfer.s3.model.CompletedCopy;
import software.amazon.awssdk.transfer.s3.model.Copy;
import software.amazon.awssdk.transfer.s3.model.CopyRequest;

import java.util.UUID;
```

## 将本地目录上传到 S3 桶
<a name="transfer-manager-upload_directory"></a>

以下示例演示如何将本地目录上传到 S3。

首先调用`S3TransferManager`实例的 [uploadDirectory](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/S3TransferManager.html#uploadDirectory(software.amazon.awssdk.transfer.s3.model.UploadDirectoryRequest)) 方法，传入。[UploadDirectoryRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/model/UploadDirectoryRequest.html)

该[DirectoryUpload](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/model/DirectoryUpload.html)对象表示上传过程，该过程会在请求完成[CompletedDirectoryUpload](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/model/CompletedDirectoryUpload.html)时生成。`CompleteDirectoryUpload` 对象包含有关传输结果的信息，包括哪些文件传输失败。

```
    public Integer uploadDirectory(S3TransferManager transferManager,
            URI sourceDirectory, String bucketName) {
        DirectoryUpload directoryUpload = transferManager.uploadDirectory(UploadDirectoryRequest.builder()
                .source(Paths.get(sourceDirectory))
                .bucket(bucketName)
                .build());

        CompletedDirectoryUpload completedDirectoryUpload = directoryUpload.completionFuture().join();
        completedDirectoryUpload.failedTransfers()
                .forEach(fail -> logger.warn("Object [{}] failed to transfer", fail.toString()));
        return completedDirectoryUpload.failedTransfers().size();
    }
```

### 导入
<a name="transfer-manager-upload_directory-import"></a>

```
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import software.amazon.awssdk.services.s3.model.ObjectIdentifier;
import software.amazon.awssdk.transfer.s3.S3TransferManager;
import software.amazon.awssdk.transfer.s3.model.CompletedDirectoryUpload;
import software.amazon.awssdk.transfer.s3.model.DirectoryUpload;
import software.amazon.awssdk.transfer.s3.model.UploadDirectoryRequest;

import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Paths;
import java.util.UUID;
```

## 将 S3 桶对象下载到本地目录
<a name="transfer-manager-download_directory"></a>

您可以将 S3 桶中的对象下载到本地目录，如以下示例所示。

要将 S3 存储桶中的对象下载到本地目录，请首先调用传输管理器的 [downloadDirectory](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/S3TransferManager.html#downloadDirectory(software.amazon.awssdk.transfer.s3.model.DownloadDirectoryRequest)) 方法，传入。[DownloadDirectoryRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/model/DownloadDirectoryRequest.html)

该[DirectoryDownload](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/model/DirectoryDownload.html)对象表示下载过程，该过程会在请求完成[CompletedDirectoryDownload](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/model/CompletedDirectoryDownload.html)时生成。`CompleteDirectoryDownload` 对象包含有关传输结果的信息，包括哪些文件传输失败。

```
    public Integer downloadObjectsToDirectory(S3TransferManager transferManager,
            URI destinationPathURI, String bucketName) {
        DirectoryDownload directoryDownload = transferManager.downloadDirectory(DownloadDirectoryRequest.builder()
                .destination(Paths.get(destinationPathURI))
                .bucket(bucketName)
                .build());
        CompletedDirectoryDownload completedDirectoryDownload = directoryDownload.completionFuture().join();

        completedDirectoryDownload.failedTransfers()
                .forEach(fail -> logger.warn("Object [{}] failed to transfer", fail.toString()));
        return completedDirectoryDownload.failedTransfers().size();
    }
```

### 导入
<a name="transfer-manager-download_directory-import"></a>

```
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.services.s3.model.ObjectIdentifier;
import software.amazon.awssdk.transfer.s3.S3TransferManager;
import software.amazon.awssdk.transfer.s3.model.CompletedDirectoryDownload;
import software.amazon.awssdk.transfer.s3.model.DirectoryDownload;
import software.amazon.awssdk.transfer.s3.model.DownloadDirectoryRequest;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
```

## 查看完整示例
<a name="transfer-manager-example-location"></a>

[GitHub 包含本页上所有示例的完整](https://github.com/awsdocs/aws-doc-sdk-examples/tree/d73001daea05266eaa9e074ccb71b9383832369a/javav2/example_code/s3/src/main/java/com/example/s3/transfermanager)代码。

# 处理 S3 事件通知
<a name="examples-s3-event-notifications"></a>

为了帮助您监控存储桶中的活动，Amazon S3 可以在某些事件发生时发送通知。《Amazon S3 用户指南》提供了有关[存储桶可以发送的通知](https://docs.aws.amazon.com/AmazonS3/latest/userguide/EventNotifications.html#notification-how-to-overview)的信息。

您可以使用适用于 Java 的 SDK 设置存储桶，将事件发送到四个可能的目的地：
+ Amazon Simple Notification Service 主题
+ Amazon Simple Queue Service 队列
+ AWS Lambda 函数
+ Amazon EventBridge

当你设置要向其发送事件的存储桶时 EventBridge，你可以配置 EventBridge 一条规则，将同一个事件分散到多个目的地。将存储桶配置为直接发送到前三个目的地之一时，只能为每个事件指定一种目标类型。

在下一节中，您将了解如何使用适用于 Java 的 SDK 配置存储桶，以便通过两种方式发送 S3 事件通知：直接发送到 Amazon SQS 队列和发送到。 EventBridge

最后一部分说明了如何使用 S3 事件通知 API 来以面向对象的方式处理通知。

## 将存储桶配置为直接发送到目的地
<a name="s3-event-conf-bucket-direct"></a>

以下示例将存储桶配置为在针对存储桶发生*对象创建*事件或*对象标记*事件时发送通知。

```
static void processS3Events(String bucketName, String queueArn) {
    // Configure the bucket to send Object Created and Object Tagging notifications to an existing SQS queue.
    s3Client.putBucketNotificationConfiguration(b -> b
            .notificationConfiguration(ncb -> ncb
                    .queueConfigurations(qcb -> qcb
                            .events(Event.S3_OBJECT_CREATED, Event.S3_OBJECT_TAGGING)
                            .queueArn(queueArn)))
                    .bucket(bucketName)
    );
}
```

上面显示的代码设置了一个队列来接收两种类型的事件。方便的是，`queueConfigurations` 方法让您可以在需要时设置多个队列目的地。此外，在 `notificationConfiguration` 方法中，您可以设置其他目的地，例如一个或多个 Amazon SNS 主题或一个或多个 Lambda 函数。以下代码段展示的示例中包含两个队列和三种类型的目的地。

```
s3Client.putBucketNotificationConfiguration(b -> b
                .notificationConfiguration(ncb -> ncb
                        .queueConfigurations(qcb -> qcb
                                .events(Event.S3_OBJECT_CREATED, Event.S3_OBJECT_TAGGING)
                                .queueArn(queueArn), 
                                qcb2 -> qcb2.<...>)
                        .topicConfigurations(tcb -> tcb.<...>)
                        .lambdaFunctionConfigurations(lfcb -> lfcb.<...>))
                        .bucket(bucketName)
        );
```

代码示例 GitHub 存储库包含直接向队列发送 S3 事件通知的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/main/javav2/example_code/s3/src/main/java/com/example/s3/ProcessS3EventNotification.java)。

## 配置要发送到的存储桶 EventBridge
<a name="s3-event-conf-bucket-eventbridge"></a>

以下示例配置了要向其发送通知的存储桶。 EventBridge

```
public static String setBucketNotificationToEventBridge(String bucketName) {
    // Enable bucket to emit S3 Event notifications to EventBridge.
    s3Client.putBucketNotificationConfiguration(b -> b
            .bucket(bucketName)
            .notificationConfiguration(b1 -> b1
                    .eventBridgeConfiguration(SdkBuilder::build))
    .build());
```

配置要向其发送事件的存储桶时 EventBridge，您只需指明 EventBridge目的地，而不是事件的类型，也不是 EventBridge 要发送到的最终目的地。您可以使用 Java SDK 的 EventBridge客户端配置最终目标和事件类型。

以下代码显示了如何配置 EventBridge 以将*对象创建*的事件分散到主题和队列。

```
   public static String configureEventBridge(String topicArn, String queueArn) {
        try {
            // Create an EventBridge rule to route Object Created notifications.
            PutRuleRequest putRuleRequest = PutRuleRequest.builder()
                    .name(RULE_NAME)
                    .eventPattern("""
                            {
                              "source": ["aws.s3"],
                              "detail-type": ["Object Created"],
                              "detail": {
                                "bucket": {
                                  "name": ["%s"]
                                }
                              }
                            }
                            """.formatted(bucketName))
                    .build();

            // Add the rule to the default event bus.
            PutRuleResponse putRuleResponse = eventBridgeClient.putRule(putRuleRequest)
                    .whenComplete((r, t) -> {
                        if (t != null) {
                            logger.error("Error creating event bus rule: " + t.getMessage(), t);
                            throw new RuntimeException(t.getCause().getMessage(), t);
                        }
                        logger.info("Event bus rule creation request sent successfully. ARN is: {}", r.ruleArn());
                    }).join();

            // Add the existing SNS topic and SQS queue as targets to the rule.
            eventBridgeClient.putTargets(b -> b
                    .eventBusName("default")
                    .rule(RULE_NAME)
                    .targets(List.of (
                            Target.builder()
                                    .arn(queueArn)
                                    .id("Queue")
                                    .build(),
                            Target.builder()
                                    .arn(topicArn)
                                    .id("Topic")
                                    .build())
                            )
                    ).join();
            return putRuleResponse.ruleArn();
        } catch (S3Exception e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
        return null;
    }
```

要在 Java 代码 EventBridge 中使用，请在 Maven `pom.xml` 文件中添加对`eventbridge`工件的依赖关系。

```
<dependency>
    <groupId>software.amazon.awssdk</groupId>
    <artifactId>eventbridge</artifactId>
</dependency>
```

代码示例 GitHub 存储库包含向主题 EventBridge 和队列发送 S3 事件通知的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/main/javav2/example_code/s3/src/main/java/com/example/s3/PutBucketS3EventNotificationEventBridge.java)，然后向主题和队列发送。

## 使用 S3 事件通知 API 来处理事件
<a name="s3-event-notification-read"></a>

目的地收到 S3 通知事件后，您可以使用 S3 事件通知 API 以面向对象的方式处理这些事件。您可以使用 S3 事件通知 API 来处理直接发送到目标的事件通知（如[第一个示例](#s3-event-conf-bucket-direct)所示），但不能处理通过 EventBridge发送的通知。存储桶发送的 S3 事件通知 EventBridge 包含 S3 事件通知 API 当前无法处理的[不同结构](https://docs.aws.amazon.com/AmazonS3/latest/userguide/ev-events.html#ev-events-list)。

### 添加依赖项
<a name="s3-event-notifications-dep"></a>

S3 事件通知 API 随适用于 Java 的 SDK 2.x 的 2.25.11 版本一起发布。

要使用 S3 事件通知 API，请将所需的依赖项元素添加到您的 Maven `pom.xml` 中，如以下代码段所示。

```
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>software.amazon.awssdk</groupId>
            <artifactId>bom</artifactId>
            <version>2.X.X1</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
<dependencies>
    <dependency>
        <groupId>software.amazon.awssdk</groupId>
        <artifactId>s3-event-notifications</artifactId>
    </dependency>
</dependencies>
```

1 [最新版本](https://central.sonatype.com/artifact/software.amazon.awssdk/bom)。

### 使用 `S3EventNotification` 类
<a name="s3-event-notifications-use"></a>

#### 使用 JSON 字符串创建 `S3EventNotification` 实例
<a name="s3-event-notifications-use-from-json"></a>

要将 JSON 字符串转换为 `S3EventNotification` 对象，请使用 `S3EventNotification` 类的静态方法，如下例所示。

```
import software.amazon.awssdk.eventnotifications.s3.model.S3EventNotification
import software.amazon.awssdk.eventnotifications.s3.model.S3EventNotificationRecord
import software.amazon.awssdk.services.sqs.model.Message; 

public class S3EventNotificationExample {
    ...
    
    void receiveMessage(Message message) {
       // Message received from SQSClient.
       String sqsEventBody = message.body();
       S3EventNotification s3EventNotification = S3EventNotification.fromJson(sqsEventBody);
    
       // Use getRecords() to access all the records in the notification.                                                                                                       
       List<S3EventNotificationRecord> records = s3EventNotification.getRecords();   
    
        S3EventNotificationRecord record = records.stream().findFirst();
        // Use getters on the record to access individual attributes.
        String awsRegion = record.getAwsRegion();
        String eventName = record.getEventName();
        String eventSource = record.getEventSource();                                                                                                   
    }
}
```

在此示例中，`fromJson` 方法将 JSON 字符串转换为 `S3EventNotification` 对象。JSON 字符串中缺少字段会导致相应 Java 对象字段设置为 `null` 值，而 JSON 中的任何多余字段则会被忽略。

事件通知记录的其他 APIs 内容可在的 API 参考中找到`[S3EventNotificationRecord](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/eventnotifications/s3/model/S3EventNotificationRecord.html)`。

#### 将 `S3EventNotification` 实例转换为 JSON 字符串
<a name="s3-event-notifications-use-to-json"></a>

使用 `toJson`（或 `toJsonPretty`）方法将 `S3EventNotification` 对象转换为 JSON 字符串，如下例所示。

```
import software.amazon.awssdk.eventnotifications.s3.model.S3EventNotification

public class S3EventNotificationExample {
    ...

    void toJsonString(S3EventNotification event) {

        String json = event.toJson();
        String jsonPretty = event.toJsonPretty();

        System.out.println("JSON: " + json);
        System.out.println("Pretty JSON: " + jsonPretty);
    }
}
```

`GlacierEventData`、`ReplicationEventData`、`IntelligentTieringEventData` 和 `LifecycleEventData` 的字段如果为 `null`，则会从 JSON 中排除。其他 `null` 字段将序列化为 `null`。

下面显示了 S3 对象标记事件的 `toJsonPretty` 方法示例输出。

```
{
  "Records" : [ {
    "eventVersion" : "2.3",
    "eventSource" : "aws:s3",
    "awsRegion" : "us-east-1",
    "eventTime" : "2024-07-19T20:09:18.551Z",
    "eventName" : "ObjectTagging:Put",
    "userIdentity" : {
      "principalId" : "AWS:XXXXXXXXXXX"
    },
    "requestParameters" : {
      "sourceIPAddress" : "XXX.XX.XX.XX"
    },
    "responseElements" : {
      "x-amz-request-id" : "XXXXXXXXXXXX",
      "x-amz-id-2" : "XXXXXXXXXXXXX"
    },
    "s3" : {
      "s3SchemaVersion" : "1.0",
      "configurationId" : "XXXXXXXXXXXXX",
      "bucket" : {
        "name" : "amzn-s3-demo-bucket",
        "ownerIdentity" : {
          "principalId" : "XXXXXXXXXXX"
        },
        "arn" : "arn:aws:s3:::XXXXXXXXXX"
      },
      "object" : {
        "key" : "akey",
        "size" : null,
        "eTag" : "XXXXXXXXXX",
        "versionId" : null,
        "sequencer" : null
      }
    }
  } ]
}
```

中提供了一个[完整的示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/75c3daadf750406156fc87fa30ee499a206b4a36/javav2/example_code/s3/src/main/java/com/example/s3/ProcessS3EventNotification.java#L117) GitHub ，其中显示了如何使用 API 来处理 Amazon SQS 队列收到的通知。

## 使用 Java 库在 Lambda 中处理 S3 事件：以及 AWS SDK for Java 2.x `aws-lambda-java-events`
<a name="s3-event-notif-processing-options"></a>

您可以使用 3.x.x 版本的库，而不是使用`[aws-lambda-java-events](https://github.com/aws/aws-lambda-java-libs/tree/main/aws-lambda-java-events)`适用于 Java 2.x 的 SDK 在 Lambda 函数中处理 Amazon S3 事件通知。 AWS 独立维护该`aws-lambda-java-events`库，并且它有自己的依赖要求。`aws-lambda-java-events` 库仅适用于 Lambda 函数中的 S3 事件，而适用于 Java 的 SDK 2.x 可处理 Lambda 函数、Amazon SNS 和 Amazon SQS 中的 S3 事件。

两种方法都以类似的面向对象的方式建模 JSON 事件通知有效负载。 APIs下表显示了使用这两种方法的显著差异。


****  

|  | 适用于 Java 的 AWS SDK | aws-lambda-java-events 图书馆 | 
| --- | --- | --- | 
| 软件包命名 |  `software.amazon.awssdk.eventnotifications.s3.model.S3EventNotification`  | com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification | 
| RequestHandler 参数 |  编写您的 Lambda 函数的 `RequestHandler` 实现以接收 JSON 字符串： <pre>import com.amazonaws.services.lambda.runtime.Context;<br />import com.amazonaws.services.lambda.runtime.RequestHandler;<br />import software.amazon.awssdk.eventnotifications.s3.model.S3EventNotification;<br /><br />public class Handler implements RequestHandler<String, String> {<br /><br />    @Override<br />        public String handleRequest(String jsonS3Event, Context context) {<br />            S3EventNotification s3Event = S3EventNotification<br />                                             .fromJson(jsonS3Event);<br />            // Work with the s3Event object.        <br />            ...<br />    }<br />}</pre>  | 编写您的 Lambda 函数的 RequestHandler 实现以接收 S3Event 对象：<pre>import com.amazonaws.services.lambda.runtime.Context;<br />import com.amazonaws.services.lambda.runtime.RequestHandler;<br />import com.amazonaws.services.lambda.runtime.events.S3Event;<br /><br />public class Handler implements RequestHandler<S3Event, String> {<br /><br />    @Override<br />        public String handleRequest(S3Event s3event, Context context) {<br />            // Work with the s3Event object.        <br />            ...<br />    }<br />}</pre> | 
| Maven 依赖项 |  <pre><dependencyManagement><br />    <dependencies><br />        <dependency><br />            <groupId>software.amazon.awssdk</groupId><br />            <artifactId>bom</artifactId><br />            <version>2.X.X</version><br />            <type>pom</type><br />            <scope>import</scope><br />        </dependency><br />    </dependencies><br /></dependencyManagement><br /><dependencies><br />    <dependency><br />        <groupId>software.amazon.awssdk</groupId><br />        <artifactId>s3-event-notifications</artifactId><br />    </dependency><br />    <!-- Add other SDK dependencies that you need. --><br /></dependencies></pre>  |  <pre><dependencyManagement><br />    <dependencies><br />        <dependency><br />            <groupId>software.amazon.awssdk</groupId><br />            <artifactId>bom</artifactId><br />            <version>2.X.X</version><br />            <type>pom</type><br />            <scope>import</scope><br />        </dependency><br />    </dependencies><br /></dependencyManagement><br /><dependencies><br />    <!-- The following two dependencies are for the <br />         aws-lambda-java-events library. --><br />    <dependency><br />        <groupId>com.amazonaws</groupId><br />        <artifactId>aws-lambda-java-core</artifactId><br />        <version>1.2.3</version>     <br />    </dependency><br />    <dependency><br />        <groupId>com.amazonaws</groupId><br />        <artifactId>aws-lambda-java-events</artifactId><br />        <version>3.15.0</version><br />    </dependency><br />    <!-- Add other SDK dependencies that you need. --><br /></dependencies></pre>  | 