Dateien aus Amazon S3 in Java importieren
Um ein Objekt aus Amazon S3 in eine lokale Datei zu importieren, verwenden Sie
getObject aus dem AWS SDK for Java v2 mit einem Datei-Antworttransformer.
Der folgende Importer lädt das Objekt an einen temporären Speicherort herunter, prüft eine
vertrauenswürdige SHA-256-Prüfsumme und stellt die Zieldatei erst bereit, wenn der vollständige
Download die Prüfung bestanden hat. Er unterstützt auch den standardmäßigen asynchronen Client,
den AWS CRT-Client und S3 Transfer Manager.
Einführung in Amazon S3 und Java
Dieses Beispiel ruft einen bekannten Schlüssel aus einem S3-Bucket für allgemeine Zwecke ab. Sie benötigen die Region des Buckets, die Berechtigung zum Lesen dieses Objekts und eine SHA-256-Prüfsumme, die vom Herausgeber stammt oder vor dem Upload aus der Originaldatei berechnet wurde. Lassen Sie das Objekt während des Downloads unverändert. Die Prüfsumme identifiziert die Bytes, die Sie importieren möchten, auch bei einem leeren Objekt oder einer Binärdatei.
AWS SDK v2 einrichten
Die Befehle verwenden Bash unter Linux, die Locale C.UTF-8 sowie ein separat
installiertes JDK und Maven. Die UTF-8-Locale erhält Nicht-ASCII-Zeichen in Argumenten für Schlüssel
und Dateinamen. Getestet wurden OpenJDK 21.0.12.1, Maven 3.10.0,
AWS SDK v2 2.55.13 und AWS CRT 0.48.5. Die folgenden Abhängigkeiten sind für reproduzierbare Tests
auf feste Versionen gesetzt. Prüfen Sie beim Aktualisieren die
AWS SDK-Veröffentlichungen.
Erstellen Sie ein neues Projekt außerhalb eines Maven-Checkouts mit einem Verzeichnis namens
.mvn. Maven kann die JVM-Optionen, Erweiterungen und Befehlsoptionen dieses
Verzeichnisses übernehmen. Der folgende Befehlsblock prüft die Werkzeuge und lehnt diese übernommene
Konfiguration ab, bevor er Dateien erstellt. Ihre Shell bleibt dabei im aktuellen Verzeichnis.
Falls s3-import bereits existiert, verwenden Sie ein anderes neues Verzeichnis,
statt dessen Dateien zu löschen.
(
for tool in java javac mvn; do
command -v "$tool" >/dev/null || {
printf 'Missing prerequisite: %s\n' "$tool" >&2
exit 1
}
done
parent="$PWD"
while :; do
[ ! -e "$parent/.mvn" ] || {
printf 'Use a directory outside the enclosing Maven configuration.\n' >&2
exit 1
}
[ "$parent" != / ] || break
parent="${parent%/*}"
[ -n "$parent" ] || parent=/
done
mkdir s3-import && mkdir -p s3-import/src/main/java
)
Speichern Sie diese vollständige Datei pom.xml in
s3-import/. Die BOM hält die SDK-Module auf derselben Version; CRT hat eine eigene
Version. Das Abhängigkeits-Plugin kopiert die Laufzeit-JARs, die der Java-Launcher benötigt.
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>s3-import</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.release>21</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>bom</artifactId>
<version>2.55.13</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
</dependency>
<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.48.5</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>1.7.36</version>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.14.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.8.1</version>
<executions>
<execution>
<phase>package</phase>
<goals><goal>copy-dependencies</goal></goals>
<configuration><includeScope>runtime</includeScope></configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
AWS-Zugangsdaten konfigurieren
Konfigurieren Sie Zugangsdaten über die bestehende AWS-Anmeldung oder Laufzeitrolle Ihrer
Organisation. Dieses Programm verwendet die
Standardkette der Zugangsdatenanbieter des SDKs. Sie prüft
Java-Systemeigenschaften, Umgebungsvariablen, gemeinsam genutzte Profile und Laufzeitrollen.
Temporäre Zugangsdaten aus der Umgebung benötigen alle drei Werte: AWS_ACCESS_KEY_ID,
AWS_SECRET_ACCESS_KEY und AWS_SESSION_TOKEN. Halten Sie diese aus dem
Java-Quellcode und den Maven-Dateien heraus.
Die Abhängigkeiten des Beispiels unterstützen Zugangsdaten aus der Umgebung und einfache gemeinsam
genutzte Profile. Ein Profil, das Zugangsdaten über SSO oder AssumeRole bezieht, benötigt zusätzlich
die passenden SDK-Module wie sso, ssooidc oder
sts in der BOM-Version. Schließen Sie diese Anmeldung ab, bevor Sie den
Importer ausführen.
AWS-Region konfigurieren
Übergeben Sie die Region des Buckets als zweites Programmargument, beispielsweise
us-east-1. Jeder Client verwendet Region.of(regionName) und ist daher
nicht auf eine Regionsangabe in Ihrer Shell oder einem gemeinsam genutzten Profil angewiesen.
Das Schlüsselargument ist der unveränderte S3-Schlüssel, keine URL mit
s3://. Codieren Sie Leerzeichen oder Prozentzeichen nicht selbst für die URL.
Dateien aus S3 importieren
Speichern Sie Folgendes als s3-import/src/main/java/S3FileImporter.java:
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.security.MessageDigest;
import java.util.HexFormat;
import java.util.Set;
import java.util.concurrent.CompletionException;
import software.amazon.awssdk.core.async.AsyncResponseTransformer;
import software.amazon.awssdk.core.sync.ResponseTransformer;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3AsyncClient;
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.transfer.s3.S3TransferManager;
import software.amazon.awssdk.transfer.s3.model.DownloadFileRequest;
public class S3FileImporter {
public static void main(String[] args) {
try {
run(args);
} catch (Exception error) {
Throwable cause = error;
while (cause instanceof CompletionException && cause.getCause() != null) {
cause = cause.getCause();
}
if (cause instanceof S3Exception s3Error) {
System.err.println("S3 download failed (HTTP " + s3Error.statusCode() + ").");
} else if (cause instanceof IOException || cause instanceof IllegalArgumentException) {
System.err.println(cause.getMessage());
} else {
System.err.println("Download failed; check credentials, connectivity, and local disk space.");
}
System.exit(1);
}
}
private static void run(String[] args) throws Exception {
if (args.length != 6 || !Set.of("sync", "async", "crt", "transfer").contains(args[0])) {
throw new IllegalArgumentException(
"Usage: S3FileImporter <sync|async|crt|transfer> <region> <bucket> <key> <destination> <sha256>");
}
String expectedHash = args[5];
if (!expectedHash.matches("[0-9a-fA-F]{64}")) {
throw new IllegalArgumentException("Supply the original object's 64-digit SHA-256.");
}
Path destination = Path.of(args[4]).toAbsolutePath();
if (Files.exists(destination, LinkOption.NOFOLLOW_LINKS)) {
throw new FileAlreadyExistsException("Destination already exists: " + destination);
}
GetObjectRequest request = GetObjectRequest.builder().bucket(args[2]).key(args[3]).build();
Region region = Region.of(args[1]);
Path stagingDirectory = Files.createTempDirectory(destination.getParent(), ".s3-import-");
Path stagedFile = stagingDirectory.resolve("object.bin");
try {
download(args[0], region, request, stagedFile);
if (!sha256(stagedFile).equalsIgnoreCase(expectedHash)) {
throw new IOException("SHA-256 mismatch; destination was not published.");
}
Files.move(stagedFile, destination);
System.out.println("Downloaded " + Files.size(destination) + " bytes to " + destination);
} finally {
Files.deleteIfExists(stagedFile);
Files.delete(stagingDirectory);
}
}
private static void download(String mode, Region region, GetObjectRequest request, Path file) {
if (mode.equals("sync")) {
try (S3Client client = S3Client.builder().region(region)
.overrideConfiguration(c -> c.retryStrategy(r -> r.maxAttempts(1))).build()) {
client.getObject(request, ResponseTransformer.toFile(file));
}
return;
}
if (mode.equals("async")) {
try (S3AsyncClient client = S3AsyncClient.builder().region(region)
.overrideConfiguration(c -> c.retryStrategy(r -> r.maxAttempts(1))).build()) {
client.getObject(request, AsyncResponseTransformer.toFile(file)).join();
}
return;
}
try (S3AsyncClient client = S3AsyncClient.crtBuilder().region(region)
.minimumPartSizeInBytes(8L * 1024 * 1024).maxConcurrency(2)
.retryConfiguration(r -> r.numRetries(0)).build()) {
if (mode.equals("crt")) {
client.getObject(request, AsyncResponseTransformer.toFile(file)).join();
} else {
try (S3TransferManager manager = S3TransferManager.builder().s3Client(client).build()) {
manager.downloadFile(DownloadFileRequest.builder()
.getObjectRequest(request).destination(file).build()).completionFuture().join();
}
}
}
}
private static String sha256(Path file) throws Exception {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
try (InputStream input = Files.newInputStream(file)) {
byte[] buffer = new byte[8192];
int count;
while ((count = input.read(buffer)) != -1) {
digest.update(buffer, 0, count);
}
}
return HexFormat.of().formatHex(digest.digest());
}
}
Bauen Sie im Verzeichnis s3-import/ das Programm und führen Sie den synchronen
Ablauf aus. Ersetzen Sie Bucket, Schlüssel, Region und Prüfsumme durch die Werte Ihres Objekts.
Das übergeordnete Verzeichnis des Ziels muss existieren, die Zieldatei selbst darf noch nicht
vorhanden sein. Der Klassenpfad in Anführungszeichen erhält den Platzhalter, damit Java ihn auflöst.
mvn -B package &&
LC_ALL=C.UTF-8 java -cp 'target/classes:target/dependency/*' S3FileImporter \
sync us-east-1 your-bucket-name 'path/to/your/file.bin' \
'./downloaded-file.bin' 'REPLACE_WITH_ORIGINAL_SHA256'
Ein erfolgreicher Befehl gibt die Anzahl der heruntergeladenen Bytes und das Ziel aus und endet mit
Exit-Status null. Die Prüfsummenprüfung erfolgt, nachdem das SDK die Datei fertiggestellt hat und
bevor Files.move sie bereitstellt. Sie liest die gesamte Datei; deren bloße
Existenz, ein nicht leerer Anfang oder ein abgeschlossenes Future reichen daher nicht aus.
Ermitteln Sie die erwartete Prüfsumme nicht durch Hashen der heruntergeladenen Datei selbst.
Ausnahmen behandeln
Der Befehl endet mit Exit-Status eins, wenn das Ziel abgelehnt wird, die Prüfsumme falsch ist, das SDK fehlschlägt oder ein lokaler E/A-Fehler auftritt. Ein abgefangener Download- oder Prüfsummenfehler entfernt die temporäre Datei und hinterlässt keine neue endgültige Zieldatei. Eine vorhandene Zieldatei bleibt erhalten. Führen Sie Downloads für dasselbe Ziel nacheinander aus; dieses Beispiel koordiniert keine gleichzeitige Bereitstellung durch mehrere Prozesse.
Bei einem fehlenden Schlüssel gibt S3 den Status 404 zurück, wenn der Aufrufer auch
s3:ListBucket hat. Ohne diese Berechtigung kann der Status 403 zurückgegeben werden.
Ein Status 403 unterscheidet daher nicht zwischen einem nicht vorhandenen Schlüssel und verweigertem
Zugriff. Prüfen Sie den exakten Schlüssel und die Berechtigungen anhand der
GetObject-Berechtigungsregeln.
Bei einer abweichenden Prüfsumme bestätigen Sie vor einem erneuten Versuch mit einem neuen Ziel den
Hash des Herausgebers und stellen Sie sicher, dass sich das Objekt nicht geändert hat. Bei einem
Fehler mit Zugangsdaten oder Netzwerk beheben Sie die betreffende Voraussetzung und wiederholen Sie
den Befehl. Werten Sie eine ausgegebene Ausnahme nicht als erfolgreichen Import.
S3-Clienttypen
Wählen Sie den Modus, indem Sie nur das erste Programmargument ändern und ein neues Ziel verwenden.
Synchroner Client (S3Client)
sync blockiert innerhalb von getObject, bis der
Datei-Antworttransformer fertig ist. Das ist ein guter Ausgangspunkt für einen Befehl, der ein
Objekt importiert.
Asynchroner Client (S3AsyncClient)
async verwendet den standardmäßigen asynchronen Java-Client. In einer
Anwendung ermöglicht sein Future, andere Arbeiten während des Downloads fortzusetzen. Dieser
Befehl ruft join() auf, bevor er die Datei prüft, den Client schließt oder
endet. Andernfalls könnte der Prozess vor Abschluss des Imports enden.
Client mit gesteigerter Leistung (AWS CRT)
crt verwendet den
AWS CRT-basierten S3-Client, eine Implementierung von
S3AsyncClient, die große Objekte in Byte-Bereichen abrufen kann. Seine native
Abhängigkeit ist im Maven-Build enthalten. Er bietet weniger Konfigurationsoptionen als der
Standardclient. Messen Sie Ihre Arbeitslast, bevor Sie ihn wegen seiner Leistung wählen; diese
Befehle versprechen keine bestimmte Übertragungsrate.
Leistung optimieren
Auf die Festplatte streamen
Alle vier Modi verwenden Zieldateien, statt das gesamte Objekt in ein Java-Byte-Array zu laden.
Auch der SHA-256-Prüflauf liest mit Puffern fester Größe. Planen Sie Speicherplatz für das gesamte
Objekt ein und verwenden Sie ein beschreibbares Zielverzeichnis. Falls der Prozess zwangsweise
beendet wird, prüfen und entfernen Sie vor einem erneuten Versuch sein zurückgebliebenes Verzeichnis
.s3-import-*. Der Java-Block finally kann nach einer solchen
Prozessbeendigung nicht aufräumen.
Multipart-Downloads
transfer verwendet S3 Transfer Manager
mit dem explizit angegebenen CRT-Client. Große Downloads können parallele Anfragen für Byte-Bereiche
nutzen. Das Beispiel legt eine minimale Teilgröße von 8 MiB und maximal zwei Verbindungen fest.
Dabei handelt es sich um Download-Bereiche, nicht um S3-Multipart-Uploads. Vor der Prüfsummenprüfung
wird auf das Abschluss-Future des Managers gewartet, und der Manager wird vor seinem Client geschlossen.
In dieser Demonstration sind Retries deaktiviert, damit eine fehlgeschlagene Anfrage dem Aufrufer gemeldet wird. Wählen Sie in einer Anwendung eine begrenzte Retry-Richtlinie und Zeitlimits für den gewählten Client. Gehen Sie nicht davon aus, dass die Builder für den Standardclient und CRT identische Einstellungen akzeptieren. Ein Download kann sonst auf eine langsame Verbindung warten; dieses Programm setzt kein Zeitlimit für die gesamte Übertragung.
IAM-Berechtigungen
Gewähren Sie für die aktuelle Version eines bekannten Schlüssels in einem Bucket für allgemeine
Zwecke die Berechtigung s3:GetObject auf den Objekt-ARN. Dieser Importer muss den
Bucket nicht auflisten. Lassen Sie beispielsweise Ihre AWS-Administration diese Richtlinie an das
benötigte Objekt anpassen:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::your-bucket-name/path/to/your/file.bin"
}
]
}
Diese Anleitung setzt ein sofort abrufbares Objekt voraus, das mit S3-verwalteten Schlüsseln verschlüsselt ist. Archivierte Objekte müssen möglicherweise wiederhergestellt werden, und eine KMS-Verschlüsselung erfordert zusätzliche Schlüsselberechtigungen. Beachten Sie für diese Fälle die Zuordnung der Berechtigungen im S3-Benutzerhandbuch. Dieses Programm fordert keine bestimmte Versions-ID an und verarbeitet keine SSE-C-Schlüssel.
Bewährte Verfahren
Bewahren Sie für jedes Objekt, das Sie importieren möchten, eine vertrauenswürdige Prüfsumme auf. Wenn Sie diesen Befehl später in einen Job integrieren, geben Sie einen Exit-Status ungleich null an den Scheduler weiter. Lassen Sie nachgelagerte Verarbeitungsschritte nur die verifizierte endgültige Zieldatei lesen. Wählen Sie unveränderliche Schlüssel oder ergänzen Sie einen versionsbewussten Abruf, wenn Ihr Herausgeber Objekte unter demselben Schlüssel ersetzt.
Für den Import von S3-Objekten in einen verwalteten Workflow zur Dateiverarbeitung finden Sie weitere Informationen beim S3 Import Robot und beim Java SDK von Transloadit.
