Beim Erstellen einer Anwendung (* hier wird serverseitiges Java angenommen) besteht meines Erachtens häufig die Notwendigkeit, Dateien zu speichern und abzurufen. Es gibt verschiedene Kandidaten zum Speichern von Dateien.
Hier
Ich werde Ihnen zeigen, wie das geht.
Das ausgefüllte Formular finden Sie auf GitHub.
Wir werden den Dateispeicher als "Dateispeicher" implementieren. "Dateispeicher" ist wie ein Schlüsselwertspeicher, der Dateien verwaltet.
--Key (Dateispeicherort) --Wert (Inhalt der Datei)
Angenommen, Sie verwalten Dateien in Form von.
Wir werden eine Schnittstelle namens "FileStorageService" bereitstellen, über die Dateien gespeichert / abgerufen werden können.
Es gibt drei Arten von "FileStorageService" -Implementierungen.
--LocalFileStorageService
(Dateien im Dateisystem des lokalen Hosts speichern. Wird während der lokalen Entwicklung verwendet.)
--S3FileStorageService
(In S3 speichern. Verwendung in der Produktionsumgebung voraussetzen)
--InMemoryFileStorageService
(Hält Dateien im Speicher. Wird voraussichtlich für CI- und automatisierte Tests verwendet.)
Konfigurieren Sie schließlich Spring Boot so, dass diese Implementierungen abhängig von Ihrer Umgebung ersetzt werden.
Dieses Mal werden wir commons-io
und aws-java-sdk-s3
verwenden. Lassen Sie uns in "Abhängigkeiten" eintauchen.
Normalerweise benutze ich "Lombok", aber dieses Mal werde ich es nicht verwenden, da es eine relativ einfache Implementierung sein wird.
dependencies {
implementation 'commons-io:commons-io:2.6'
implementation 'com.amazonaws:aws-java-sdk-s3:1.11.774'
}
Zuerst werden wir die "Seite" Seite machen. Ich werde drei machen.
--FileStorageService
... Derjenige, der die Verarbeitung wie das Speichern / Abrufen von Dateien übernimmt. Die Hauptrolle.
--FileLocation
... Ein Wertobjekt, das den Speicherort einer Datei im Speicher darstellt.
--FileStorageObject
... Ein Objekt, das den Inhalt einer Datei darstellt
Es ist ein Bild, das so verwendet werden kann.
FileStorageService fileStorageService = ...;
//Speicher die Datei
fileStorageService.putFile(FileLocation.of("hoge/fuga/sample.txt"), file);
//Extrahieren Sie die gespeicherte Datei
FileStorageObject fileStorageObject = fileStorageService.getFile(FileLocation.of("hoge/fuga/sample.txt"));
InputStream is = fileStorageObject.getInputStream(); //Der Inhalt der Datei kann mit InputStream abgerufen werden
Der Schlüssel, der den Speicherort der Datei darstellt, kann "String" sein. Bereiten wir jedoch ein Wertobjekt mit dem Namen "FileLocation" vor, das "String" umschließt.
FileLocation.java
Erstellen Sie zunächst ein Objekt, das den Speicherort (Schlüssel) der Datei darstellt.
import java.util.Objects;
/**
*Der Speicherort der Datei im Dateispeicher.
*/
public class FileLocation {
/**
*Pfad."parent/child/file.txt"Angenommen, ein Wert wie.
*/
private final String value;
private FileLocation(String value) {
this.value = value.startsWith("/") ? value.substring(1) : value;
}
/**
*Aus einer Schnur{@link FileLocation}Erstellen Sie eine Instanz von.
*
* @Parameterwertpfad
* @Instanz zurückgeben
*/
public static FileLocation of(String value) {
return new FileLocation(value);
}
/**
*Aus mehreren Saiten{@link FileLocation}Erstellen Sie eine Instanz von.
*Jede Zeichenkette ist"/"Ist verbunden durch.
*
* @param parts Mehrere Zeichenfolgen, aus denen der Pfad besteht
* @Instanz zurückgeben
*/
public static FileLocation of(String... parts) {
if (parts.length == 1) {
return new FileLocation(parts[0]);
}
return new FileLocation(String.join("/", parts));
}
@Override
public String toString() {
return value;
}
//Implementieren Sie hashCode und equals
...
}
Sie können das FileLocation
-Objekt wie folgt erhalten.
FileLocation fileLocation = FileLocation.of("key/to/file.txt");
Das ist das gleiche. (Ich habe auf die Java-Standard-API "Paths.get ()" verwiesen.)
FileLocation fileLocation = FileLocation.of("key", "to", "file.txt");
FileStorageObject.java
Erstellen Sie als Nächstes ein Objekt, das die aus dem Speicher abgerufene Datei darstellt. Wird im Rückgabewert von "FileStorageService # getFile ()" verwendet.
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import org.apache.commons.io.IOUtils;
/**
*Ein Objekt, das den Inhalt einer Datei im Speicher darstellt.
*/
public interface FileStorageObject {
/**
*Der Inhalt der Datei{@link InputStream}Reinkommen.
*
* @return {@link InputStream}
*/
InputStream getInputStream();
}
Als Schnittstelle erstellen. Hier wird nur die Methode verwendet, die "InputStream" zurückgibt. Es ist jedoch in Ordnung, verschiedene praktische Methoden zu verwenden.
FileStorageService.java
Machen Sie schließlich eine Person, die die Datei betreibt. Es ist die Hauptrolle.
import java.io.InputStream;
import java.nio.file.Path;
/**
*Ein Dienst zum Bearbeiten von Dateien im Dateispeicher.
*/
public interface FileStorageService {
/**
*Speicher die Datei.
*
* @param fileLocation Ziel im Speicher
* @Inhalt der param inputStream-Datei
*/
void putFile(FileLocation fileLocation, InputStream inputStream);
/**
*Speicher die Datei.
*
* @param fileLocation Ziel im Speicher
* @param localFile Datei zum Speichern
*/
void putFile(FileLocation fileLocation, Path localFile);
/**
*Löschen Sie die Datei.
*Wenn die Datei nicht vorhanden ist, tun Sie nichts.
*
* @param fileLocation Ziel im Speicher
*/
void deleteFile(FileLocation fileLocation);
/**
*Holen Sie sich die Datei.
*
* @param fileLocation Speicherort im Speicher
* @Dateiobjekt zurückgeben. Null, wenn es nicht existiert
*/
FileStorageObject getFile(FileLocation fileLocation);
}
Es ist wie eine Map mit "FileLocation" als Schlüssel und dem Inhalt der Datei als Wert.
Die Methode putFile ()
liefert putFile (InputStream)
und putFile (Path)
,
Sie können alles speichern, solange Sie "InputStream" haben.
byte[] bytes = ...;
fileStorageService.putFile(FileLocation.of("hoge"), new ByteArrayInputStream(bytes));
LocalFileStorageService.java
public class LocalFileStorageService implements FileStorageService {
private final Path rootDirPath;
public LocalFileStorageService(Path rootDirPath) {
this.rootDirPath = Objects.requireNonNull(rootDirPath);
}
@Override
public void putFile(FileLocation targetLocation, InputStream inputStream) {
Path target = rootDirPath.resolve(targetLocation.toString());
ensureDirectoryExists(target.getParent());
try (InputStream is = inputStream) {
Files.write(target, IOUtils.toByteArray(inputStream));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
@Override
public void putFile(FileLocation targetLocation, Path localFile) {
Path target = rootDirPath.resolve(targetLocation.toString());
ensureDirectoryExists(target.getParent());
try {
Files.copy(localFile, target, StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
@Override
public void deleteFile(FileLocation targetLocation) {
Path path = rootDirPath.resolve(targetLocation.toString());
if (!Files.exists(path)) {
return;
}
try {
Files.delete(path);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
@Override
public FileStorageObject getFile(FileLocation fileLocation) {
Path path = rootDirPath.resolve(fileLocation.toString());
if (!Files.exists(path)) {
return null;
}
return new LocalFileStorageObject(path);
}
private void ensureDirectoryExists(Path directory) {
if (!Files.exists(directory)) {
try {
Files.createDirectories(directory);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}
private static class LocalFileStorageObject implements FileStorageObject {
private final Path path;
private LocalFileStorageObject(Path path) {
this.path = path;
}
@Override
public InputStream getInputStream() {
try {
return Files.newInputStream(path);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}
}
Vorbereiten der Implementierung von FileStorageService In ↑ wird "FileStorageService" als Schnittstelle erstellt, sodass kein Inhalt vorhanden ist. Hier implementieren wir drei, "LocalFileStorageService", "S3FileStorageService" und "InMemoryFileStorageService". Erstellen Sie zunächst eine Implementierung von FileStorageService, in der Dateien in Ihrem lokalen Dateisystem gespeichert werden. Empfängt das Stammverzeichnis des Dateispeicherorts als Konstruktorargument. Wenn Sie ↓ mögen, wird eine Datei in / hoge / fuga / abc / efg.txt
erstellt.
FileStorageService fileStorageService = new LocalFileStorageService(Paths.get("/hoge/fuga"));
fileStorageService.putFile(FileLocation.of("abc/efg.txt"), file);
S3FileStorageService.java
Erstellen Sie als Nächstes eine Implementierung von FileStorageService, in der Dateien in AWS S3 gespeichert werden.
public class S3FileStorageService implements FileStorageService {
private final AmazonS3 s3Client;
private final String bucketName;
public S3FileStorageService(AmazonS3 s3Client, String bucketName) {
this.s3Client = Objects.requireNonNull(s3Client);
this.bucketName = Objects.requireNonNull(bucketName);
}
@Override
public void putFile(FileLocation targetLocation, InputStream inputStream) {
Path scratchFile = null;
try (InputStream is = inputStream) {
//Wenn Sie versuchen, direkt mit InputStream hochzuladen, müssen Sie ContentLength festlegen. Schreiben Sie es also einmal in eine Datei
//PutFile, wenn Sie Wert auf Leistung legen(FileLocation, InputStream, int contentLength)Oder vielleicht können Sie sich vorbereiten
scratchFile = Files.createTempFile("s3put", ".tmp");
Files.copy(inputStream, scratchFile);
putFile(targetLocation, scratchFile);
} catch (IOException e) {
throw new UncheckedIOException(e);
} finally {
if (scratchFile != null) {
FileUtils.deleteQuietly(scratchFile.toFile());
}
}
}
@Override
public void putFile(FileLocation targetLocation, Path localFile) {
if (!Files.exists(localFile)) {
throw new IllegalArgumentException(localFile + " does not exists.");
}
s3Client.putObject(new PutObjectRequest(bucketName, targetLocation.toString(), localFile.toFile()));
}
@Override
public void deleteFile(FileLocation targetLocation) {
s3Client.deleteObject(bucketName, targetLocation.toString());
}
@Override
public FileStorageObject getFile(FileLocation fileLocation) {
S3Object s3Object = s3Client.getObject(new GetObjectRequest(bucketName, fileLocation.toString()));
if (s3Object == null) {
return null;
}
return new S3FileStorageObject(s3Object);
}
private static class S3FileStorageObject implements FileStorageObject {
private final S3Object s3Object;
private S3FileStorageObject(S3Object s3Object) {
this.s3Object = s3Object;
}
@Override
public InputStream getInputStream() {
return s3Object.getObjectContent();
}
}
}
InMemoryFileStorageService.java
Erstellen Sie abschließend einen FileStorageService, der die Dateien im Speicher enthält.
public class InMemoryFileStorageService implements FileStorageService {
private final Map<FileLocation, byte[]> files = new ConcurrentHashMap<>();
@Override
public void putFile(FileLocation targetLocation, InputStream inputStream) {
try (InputStream is = inputStream) {
files.put(targetLocation, IOUtils.toByteArray(inputStream));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
@Override
public void putFile(FileLocation targetLocation, Path localFile) {
try {
byte[] bytes = Files.readAllBytes(localFile);
files.put(targetLocation, bytes);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
@Override
public void deleteFile(FileLocation targetLocation) {
files.remove(targetLocation);
}
@Override
public FileStorageObject getFile(FileLocation fileLocation) {
byte[] bytes = files.get(fileLocation);
if (bytes == null) {
return null;
}
return new InMemoryFileStorageObject(bytes);
}
private static class InMemoryFileStorageObject implements FileStorageObject {
private final byte[] bytes;
private InMemoryFileStorageObject(byte[] bytes) {
this.bytes = bytes;
}
@Override
public InputStream getInputStream() {
return new ByteArrayInputStream(bytes);
}
}
}
Zu diesem Zeitpunkt ist die Implementierung der Bibliothek, die den "Dateispeicher" verwaltet, abgeschlossen. (Leicht verbesserte sind auf [GitHub] verfügbar (https://github.com/kohii/filestorage-java).)
Ersetzen wir die Implementierung von drei Arten von Dateispeicherdiensten je nach Umgebung. Benutzern von FileStorageService wird eine Instanz von FileStorageService in Spring injiziert, sodass sie nicht wissen müssen, welche Implementierung verwendet wird.
@Service
public class SampleService {
private final FileStorageService fileStorageService; //Spritzen lassen. Sie müssen nicht wissen, welche Implementierung verwendet wird
public SampleService(FileStorageService fileStorageService) { //Konstruktorinjektion
this.fileStorageService = fileStorageService;
}
public void doSomething() {
fileStorageService.getFile(...);
}
}
Der Rest ist perfekt, wenn die zu injizierende Instanz von FileStorageService je nach Umgebung umgeschaltet wird.
Spring verfügt über [Profile](https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-profiles] als Mechanismus zum Ändern der Einstellungen für jede Umgebung. ). Verwenden Sie dies, um die Implementierung von FileStorageService für jede Umgebung zu ersetzen.
Legen Sie hier drei Profile entsprechend der Umgebung fest.
--Während der lokalen Entwicklung: Entwicklung
(→ Verwenden Sie LocalFileStorageService
)
--Während des automatischen Tests: test
(→ Verwenden Sie InMemoryFileStorageService
)
--Produktion: Produktion
(→ Verwenden Sie S3FileStorageService
)
Stellen Sie sicher, dass das entsprechende Profil in jeder Umgebung aktiviert ist.
Ermöglicht die standardmäßige Aktivierung des Profils "Entwicklung".
Wenn Sie ↓ in src / main / resources / application.yml
schreiben, wird dieses Profil aktiviert, wenn Sie die Spring Boot-Anwendung normal starten.
src/main/resources/application.yml
spring:
profiles:
active: development
test
Stellen Sie als Nächstes sicher, dass das Profil "Test" aktiviert ist, wenn Sie den Test ausführen.
Schreiben Sie als ↓ in src / test / resources / application.yml
. (Zum Zeitpunkt des Tests hat dies Vorrang vor ↑)
src/test/resources/application.yml
spring:
profiles:
active: test
Wenn Sie die Spring Boot-Anwendung in der Produktion starten, starten Sie sie mit der Option "--spring.profiles.active = Production".
Registrieren Sie die Bean mit Java Config. Sie können die Annotation "@ Profile" verwenden, um eine Bean nur dann zu generieren und zu registrieren, wenn ein bestimmtes Profil gültig ist.
FileStorageConfiguration.java
@Configuration
public class FileStorageConfiguration {
@Bean
@Profile("development")
FileStorageService localFileStorageService(
@Value("${app.fileStorage.local.rootDir}") String rootDir) {
return new LocalFileStorageServiceFactory(Paths.get(rootDir));
}
@Bean
@Profile("test")
FileStorageService inMemoryFileStorageService() {
return new InMemoryFileStorageService();
}
@Bean
@Profile("production")
FileStorageService s3FileStorageService(AmazonS3 amazonS3,
@Value("${app.fileStorage.s3.bucketName}") String bucketName) {
return new S3FileStorageService(amazonS3, bucketName);
}
@Bean
AmazonS3 amazonS3() {
return AmazonS3ClientBuilder.defaultClient();
}
}
Wenn Sie in dem Teil von ↑, in dem es sich um "@Value (" $ {key} ")" handelt, die Einstellung für "application- {profile} .yml" schreiben, wird der Wert automatisch eingefügt.
src/main/resources/application-development.yml
app:
fileStorage:
local:
rootDir: "/opt/app/file_storage"
src/main/resources/application-production.yml
app:
fileStorage:
s3:
bucketName: smaple_bucket
Recommended Posts