在 Java 生态中,上传文件到 S3 存储桶有多种成熟方案。本文将重点介绍 jclouds 库 提供的解决方案,涵盖四种不同层级的 API 实现。

要使用本文讨论的 jclouds API,只需在项目中添加以下 Maven 依赖

<dependency>
   <groupId>org.jclouds</groupId>
   <artifactId>jclouds-allblobstore</artifactId>
   <version>1.5.10</version>
</dependency>

1. 上传到 Amazon S3

使用 jclouds 的第一步是创建认证上下文。基础实现如下:

BlobStoreContext context = 
  ContextBuilder.newBuilder("aws-s3").credentials(identity, credentials)
    .buildView(BlobStoreContext.class);

这个上下文是访问通用键值存储服务(如 Amazon S3)的入口点,但不仅限于 S3。

如果需要 S3 专用实现,可以这样创建:

BlobStoreContext context = 
  ContextBuilder.newBuilder("aws-s3").credentials(identity, credentials)
    .buildView(S3BlobStoreContext.class);

更具体的 AWS S3 实现:

BlobStoreContext context = 
  ContextBuilder.newBuilder("aws-s3").credentials(identity, credentials)
    .buildView(AWSS3BlobStoreContext.class);

⚠️ 重要提示:认证上下文使用完毕后必须关闭,否则会泄漏连接和线程资源。

2. jclouds 的四种 S3 API

jclouds 提供四种不同层级的上传 API,从简单到强大依次递进。所有 API 都通过 BlobStoreContext 获取。

2.1 通过 Map API 上传

最简单的 API 是将 S3 存储桶抽象为 Map。获取方式如下:

InputStreamMap bucket = context.createInputStreamMap("bucketName");

上传 HTML 文件示例:

bucket.putString("index1.html", "<html><body>hello world1</body></html>");

InputStreamMap 还支持文件、字节数组等多种上传方式,包括批量操作。

典型集成测试示例:

@Test
public void whenFileIsUploadedToS3WithMapApi_thenNoExceptions() {
   BlobStoreContext context = 
      ContextBuilder.newBuilder("aws-s3").credentials(identity, credentials)
         .buildView(AWSS3BlobStoreContext.class);

   InputStreamMap bucket = context.createInputStreamMap("bucketName");  

   bucket.putString("index1.html", "<html><body>hello world1</body></html>");
   context.close();
}

2.2 通过 BlobMap 上传

Map API 虽然简单但功能有限(无法设置元数据)。当需要更精细控制时,使用 BlobMap API:

BlobMap bucket = context.createBlobMap("bucketName");

该 API 允许设置底层细节:

  • Content-Length
  • Content-Type
  • Content-Encoding
  • eTag 哈希值等

上传文件示例:

Blob blob = bucket.blobBuilder().name("index2.html").
   payload("<html><body>hello world2</body></html>").
      contentType("text/html").calculateMD5().build();

集成测试示例:

@Test
public void whenFileIsUploadedToS3WithBlobMap_thenNoExceptions() throws IOException {
   BlobStoreContext context = 
      ContextBuilder.newBuilder("aws-s3").credentials(identity, credentials)
         .buildView(AWSS3BlobStoreContext.class);

   BlobMap bucket = context.createBlobMap("bucketName");

   Blob blob = bucket.blobBuilder().name("index2.html").
      payload("<html><body>hello world2</body></html>").
         contentType("text/html").calculateMD5().build();
   bucket.put(blob.getMetadata().getName(), blob);

   context.close();
}

2.3 通过 BlobStore 上传

前两种 API 不支持分块上传,不适合大文件场景。BlobStore API 解决了这个问题:

BlobStore blobStore = context.getBlobStore();

启用分块上传示例:

Blob blob = blobStore.blobBuilder("index3.html").
   payload("<html><body>hello world3</body></html>").contentType("text/html").build();
blobStore.putBlob("bucketName", blob, PutOptions.Builder.multipart());

关键点:

  • 使用与 BlobMap 相同的 payload 构建器
  • 通过 PutOptions 启用分块上传功能

集成测试示例:

@Test
public void whenFileIsUploadedToS3WithBlobStore_thenNoExceptions() {
   BlobStoreContext context = 
      ContextBuilder.newBuilder("aws-s3").credentials(identity, credentials)
         .buildView(AWSS3BlobStoreContext.class);

   BlobStore blobStore = context.getBlobStore();

   Blob blob = blobStore.blobBuilder("index3.html").
      payload("<html><body>hello world3</body></html>").contentType("text/html").build();
   blobStore.putBlob("bucketName", blob, PutOptions.Builder.multipart());
   context.close();
}

2.4 通过 AsyncBlobStore 上传

对于异步场景,jclouds 提供了 AsyncBlobStore API:

AsyncBlobStore blobStore = context.getAsyncBlobStore();

与同步版本的核心区别在于:

  • 返回 ListenableFuture 对象
  • 需要调用 .get() 获取最终结果

异步上传示例:

Blob blob = blobStore.blobBuilder("index4.html").
   payload("<html><body>hello world4</body></html>").build();
blobStore.putBlob("bucketName", blob).get();

集成测试示例:

@Test
public void whenFileIsUploadedToS3WithBlobStore_thenNoExceptions() {
   BlobStoreContext context = 
      ContextBuilder.newBuilder("aws-s3").credentials(identity, credentials)
         .buildView(AWSS3BlobStoreContext.class);

   BlobStore blobStore = context.getBlobStore();

   Blob blob = blobStore.blobBuilder("index4.html").
      payload("<html><body>hello world4</body></html>").contentType("text/html").build();
   Future<String> putOp = blobStore.putBlob("bucketName", blob, PutOptions.Builder.multipart());
   putOp.get();
   context.close();
}

3. 总结

我们分析了 jclouds 提供的四种 S3 上传 API

  1. Map API:最简单但功能有限
  2. BlobMap:支持元数据设置
  3. BlobStore:支持分块上传(大文件必备)
  4. AsyncBlobStore:异步非阻塞操作

这些 API 是通用设计,同样适用于其他键值存储服务(如 Microsoft Azure Storage)。

下一篇我们将深入探讨 jclouds 的 AWS 专用 API AWSS3Client,实现:

  • 大文件动态分块优化
  • 并行上传所有分块
  • 智能计算最优分块数量

踩坑提醒:实际使用时务必记得关闭 BlobStoreContext,否则会导致资源泄漏!


原始标题:Upload on S3 With the jclouds Library Baeldung