为了访问桶,我们使用AWS Java API。我们将使用API的2.0版本,但在撰写本文时,这是一个非常新的版本,因此您在互联网上找不到许多代码示例。版本1与版本2不兼容,不同的子版本之间也不兼容,因此我们必须非常小心地选择我们使用的API版本。在本单元中,我们将同时使用1.x版本和2.x版本。这是因为AWS上的大多数Java文档都是1.x版本。

       由于用于完成简单任务的类和接口很多。这是因为AWS并非用原生Java构建,因此每个数据结构都将有一个Java类/对象来提供对它的语言级访问。即使是对AWS接口的调用结果也将返回Java对象而不是简单的Java原始数据类型。习惯这一点需要一段时间。您还应该注意,所有类都记录在Java API文档中,因此当您遇到困难时,您需要学会如何导航文档。

        在本实验室剩余的部分,我们将操纵桶中的对象。在本单元中,我们不需要以编程方式操纵桶本身,而是我们将使用AWS控制台界面来操纵桶。但是,也有一整套Java API类和接口,用于在您的Java程序中操纵桶,您可以在自己的时间里尝试。

        我们使用Maven通过从Maven仓库加载所有适当的类来构建我们的Java程序。依赖关系和其他配置信息由pom.xml文件指定。

        我们可以添加与上周我们使用的pom.xml相关的S3依赖,并在本周用于S3。在后续的实验室会话中,我们将仅指定我们使用的亚马逊产品的相关API,您将需要将其插入类似于这个文件中的文件。请注意,此文件包含一些额外的指令,以便于您调试。这不是一个最小的pom.xml文件。例如,它包括shade插件,允许您在target目录运行jar文件。它还指定了UTF-8编码,以消除一些烦人的警告信息。

        第二个重要的“设置”活动是导入您在程序中使用的所有API类、接口、异常等。这些应该逐个导入,以帮助减小代码大小并帮助分析您的代码。AWS API是一个非常大的软件片段,因此这并不像听起来那么容易。您需要非常熟悉API文档,以找出应该从哪里导入类、接口和异常。在线会话中将显示一些这样做的技巧。本周我们需要的S3依赖项是:

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

        我们可以将此依赖项与上周的pom.xml文件一起添加,并在本周用于S3。

        我们已经知道在Cloud9中创建新的maven应用的命令,如下所示。

mvn -B archetype:generate -DarchetypeGroupId=org.apache.maven.archetypes -DgroupId=au.edu.scu.app -DartifactId=s3app

        附加文件:

  • pom.xml 使用ReadSpeaker docReader打开此文档 2.023 KB

从桶中读取对象

        有很多方法可以从桶中获取对象。我们可以将对象读入程序中的一个Java对象,我们可以将对象读入当前机器中的一个文件,我们可以将对象作为输入流读取,我们还可以使用多部分读取来读取大对象。这里我们将看一个技术,将对象读入本地文件。如果我们希望操纵对象,我们可以打开文件并使用标准的Java文件处理读取其数据。

        为了将对象读入本地文件系统,我们可以编写以下代码(完整程序)。这里我们打算从本实验室会话中较早创建的桶中读取index.html文件。您的桶名称将不同!

        我们需要导入以下类。

package au.edu.scu.app;

import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
import software.amazon.awssdk.services.s3.model.S3Request;
import software.amazon.awssdk.services.s3.model.NoSuchKeyException;
import software.amazon.awssdk.core.sync.RequestBody;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.FileSystems;

        然后我们编写以下代码,将"index.html"的内容读入名为"temp.txt"的本地文件(在应用内)。

public class App 
{
    public static S3Client s3;
    
    public static void main( String[] args ) throws IOException
    {
        String bucket = "pchakrab14-bucket-1";
        String key = "index.html";
        Region region = Region.US_EAST_1;
        s3 = S3Client.builder().region(region).build();
        
        Path path = FileSystems.getDefault().getPath("temp.txt");
        GetObjectRequest req =
            GetObjectRequest.builder().bucket(bucket).key(key).build();
        GetObjectResponse resp = s3.getObject(req, path);
        
    }
}

        您可能不习惯这样的代码。它使用静态构建方法创建程序内部使用的对象。当标准的new操作无法提供足够的信息来初始化这些对象时,通常需要这样做,因为它们依赖于从互联网或程序外部其他来源加载信息。

        关于这段代码的注解:

  • 代码构建了一个"S3Client"对象来访问s3产品。"S3Client"实际上是一个接口,所以实际的对象将是实现该接口的类。
  • "Path"对象定义了本地文件系统中的实际文件路径。在这个例子中,它是放置对象副本的文件的名称("index.html")。
  • 我们使用构建函数创建了一个"GetObjectRequest"对象,用来包含S3请求的细节。这里我们指定了区域、桶ID和对象名称(键)。
  • 我们通过在"S3Client"对象上调用"getObject()"方法来启动操作。它返回一个"GetObjectResponse"对象。注意这个函数有几个重载版本,这是将对象复制到指定路径的版本。
  • 这段代码中没有错误处理!大多数错误将作为异常被运行时系统捕获。例如,如果桶存在但我们指定了错误的键(对象ID),则会抛出"NoSuchKeyException"。
  • 错误处理很重要,既适用于实际程序中的恢复,也适用于调试代码。不总是容易看出错误将从哪里来。对于异常处理,我们只需要添加熟悉的try-catch语句,例如,要捕获"NoSuchKeyException",我们可以用以下代码替换上面的一些代码:
try {
    Region region = Region.US_EAST_1;
    s3 = S3Client.builder().region(region).build();

    Path path = FileSystems.getDefault().getPath("temp.txt");
    GetObjectRequest req =
        GetObjectRequest.builder().bucket(bucket).key(key).build();
    GetObjectResponse resp = s3.getObject(req, path);
} catch (NoSuchKeyException e) {
    System.err.println("That object does not exist.");
}

        请注意,我们已经在上面的第一个版本中包含了适当的导入语句:

import software.amazon.awssdk.services.s3.model.NoSuchKeyException;

        我们还可以通过捕获"S3Exception"异常来捕获所有S3异常,这是所有S3异常的基类。如下代码在调试时非常有用:

} catch (S3Exception e) { System.err.println(“S3 Exception: “+e.awsErrorDetails().errorMessage()); System.exit(-1); }

        其他问题可以通过查看"getObject()"函数调用返回的"GetObjectResponse"对象来确定。在这种情况下,如果成功将对象复制到文件中,对象将为null,所以只有异常才传递错误。

        我们需要使用上周使用的相同命令来编译和运行应用程序。

mvn package

java -jar target/s3app1.0.jar

创建桶对象

        我们还可以在桶中放置对象。在这一节中,我们将在桶对象中放置数据存储在ByteBuffer对象中。

        首先,我们可以创建一些测试数据在ByteBuffer对象中,如下所示。添加这两个导入:

import java.nio.ByteBuffer;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;

        接下来的字符串将被写入我们将要创建的新对象内。

byte bytes[] = "this will be the contents of the object.".getBytes();
ByteBuffer data = ByteBuffer.wrap(bytes);

        此代码使用静态的"wrap()"函数创建一个新的"ByteBuffer"对象。之所以称之为‘wrap’,是因为它在字节数组周围创建了一个缓冲对象。字节数组仍然是数据的位置,而"ByteBuffer"对象允许使用标准接口访问数据。

        我们可以按照以下方式将对象存储在桶中。这里我们为对象的名称创建了一个新的键,以便它不与前一节中读取的对象发生冲突。

String writeKey = "data.tmp";   // 新对象名称
s3.putObject(PutObjectRequest.builder().bucket(bucket).key(writeKey).build(),
RequestBody.fromByteBuffer(data));

        关于这段代码有几个重要的事项需要注意:

  • 代码接续了前一节中定义的s3和bucket的代码。
  • 您需要将"PutObjectRequest"类导入程序。它以与前一节中使用的"GetObjectRequest"非常相似的方式使用构建器。
  • "RequestBody"类,它也在前一节中的get对象请求中使用过,有将"ByteBuffer"转换为适合S3的格式的方法。您应该注意,还有其他获取对象数据的方法,您可以在"RequestBody"文档中看到。

        另一个重要的问题可能已经出现在您的脑海中。我们如何能够在前一节中将桶设置为公共只读访问时写入桶。答案是您既拥有Cloud9 EC2实例又拥有桶,因此作为所有者,您对所有AWS服务都有写权限。EC2实例的开发环境在您每次启动时自动初始化。如果您使用另一个IDE或计算机,例如在PC上的NetBeans,您将不会被初始化,每次启动会话时都需要输入您的AWS凭据。"main()"内的完整代码看起来如下:

String bucket = "pchakrab14-bucket-1"; //您的桶名称
String readKey = "index.html"; //我们不需要这个键

 写入桶对象
try {
    Region region = Region.US_EAST_1;
    s3 = S3Client.builder().region(region).build();
    
    byte bytes[] = "this will be the contents of the object.".getBytes();
    ByteBuffer data = ByteBuffer.wrap(bytes);
    String writeKey = "data.tmp";   // 新对象名称
    s3.putObject(PutObjectRequest.builder().bucket(bucket).key(writeKey).build(), RequestBody.fromByteBuffer(data));
}
catch(NoSuchKeyException e) {
    System.err.println("That object does not exist.");
}

列出桶对象

        我们可以使用以下代码列出前一节中桶的所有对象。添加这些导入语句。

import software.amazon.awssdk.services.s3.model.ListObjectsV2Request;
import software.amazon.awssdk.services.s3.model.ListObjectsV2Response;
import software.amazon.awssdk.services.s3.model.S3Object;

        以下代码将手动将当前对象列表到终端。

ListObjectsV2Request listObjectsReqManual =
    ListObjectsV2Request.builder()
                        .bucket(bucket)
                        .maxKeys(1)
                        .build();

boolean done = false; 
while (!done) {
    ListObjectsV2Response listObjResponse =
        s3.listObjectsV2(listObjectsReqManual);
    for (S3Object content : listObjResponse.contents()) {
        System.out.println(content.key());
    }

    if (listObjResponse.nextContinuationToken() == null) {
        done = true;
    }

    listObjectsReqManual =
        listObjectsReqManual.toBuilder()
            .continuationToken(listObjResponse.nextContinuationToken())
            .build();
}

        这段代码比前几节中的代码要复杂得多。代码使用‘手动分页’,这意味着我们需要在循环遍历对象列表时添加我们自己的格式化数据。

        对于上述代码,您可以看到两个循环。有一个在done变量上的循环,还有一个处理由"listObjectV2()"方法返回的每个列表的for循环。需要两个循环的原因是"listObjectV2()"方法可能不会返回桶中的所有对象。最大数量由提供给构建器的"maxKeys()"调用控制。在这种情况下,我们将最大值设置为一个,所以for循环将只处理一个对象。while循环将一次迭代一个对象。

        有一个更强大的列表机制使用Iterable接口一次处理对象列表。这隐藏了上面的内部循环在一个迭代器中。以下代码执行此操作。它还为每个对象打印出不同格式的字符串,包括其大小。

import software.amazon.awssdk.services.s3.paginators.ListObjectsV2Iterable;

ListObjectsV2Request listReq =
    ListObjectsV2Request.builder()
                        .bucket(bucket)
                        .maxKeys(1)
                        .build();
                
ListObjectsV2Iterable listRes = s3.listObjectsV2Paginator(listReq);
            
for (S3Object content : listRes.contents()) {
     System.out.println(" Key: " + content.key() + " size = "
                         + content.size());
}

        关于这段代码的一些要点:

  • 必须从"software.amazon.awssdk.services.s3.paginators"包中导入"ListObjectsV2Iterable"类。
  • 这段代码中的第一条语句与上面的手动分页代码中的第一条语句完全相同。第二条语句将列表对象转换为可迭代列表。
  • for循环使用标准的Java for-each处理。

        您将熟悉带有Iterable接口的对象,因为您之前的Java编程经验中有很多用于处理可迭代对象的实用函数,许多这些函数允许将通用函数应用到每个元素上,例如使用lambda表达式。例如,我们可以使用以下方式处理上述可迭代对象列表作为流:

listRes.contents().stream()
       .forEach(content -> System.out.println(" Key: " +
                                              content.key() +
                                              " size = " +
                                              content.size()));

        这里的"stream()"方法返回由可迭代对象列表组成的流。"forEach()"方法然后将lambda表达式映射到流的每个元素。

删除桶对象

        要删除对象,我们使用与之前的请求非常相似的方式使用"DeleteObjectRequest"。以下代码将删除我们之前创建的对象。

import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;

DeleteObjectRequest delRequest = DeleteObjectRequest.builder().bucket(bucket).key(deleteKey).build();
s3.deleteObject(delRequest);

如前所述,请注意以下事项:

  • 代码接续了前一节中定义的s3、deleteKey和bucket的代码。
  • 您需要将"DeleteObjectRequest"类导入程序。它以与之前使用的"GetObjectRequest"非常相似的方式使用构建器。
  • 删除对象时没有确认。如果对象存在,S3将继续删除对象。如果您想在程序中确认删除,您将需要自己实现代码。

故障排除

        在运行S3应用程序后,您可能在终端看到以下错误消息。

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.

        您可以将以下依赖项添加到您的pom.xml中以避免这些错误消息。

<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-simple</artifactId>
  <version>1.7.21</version>
</dependency>
05-29 19:05