Erstellen wir eine vielseitige Dateispeicher (?) - Operationsbibliothek, indem wir die Dateispeicherung / -erfassung mit Java abstrahieren

Nanikore

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.

Überblick über das, was zu tun ist

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.

Implementierungsübersicht

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.

Abhängige Bibliotheken

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'
}

Bereiten Sie eine API für den Betrieb des "Dateispeichers" vor

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 Sie mit Spring Boot die Implementierung für jede Umgebung

Was du machen willst

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.

Realisierungspolitik

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)

Profileinstellungen

Stellen Sie sicher, dass das entsprechende Profil in jeder Umgebung aktiviert ist.

Während der lokalen Entwicklung: "Entwicklung"

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

Während des automatischen Tests: 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

Produktion: "Produktion"

Wenn Sie die Spring Boot-Anwendung in der Produktion starten, starten Sie sie mit der Option "--spring.profiles.active = Production".

Registrieren Sie die Implementierung von FileStorageService gemäß Profil als Bean

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

Erstellen wir eine vielseitige Dateispeicher (?) - Operationsbibliothek, indem wir die Dateispeicherung / -erfassung mit Java abstrahieren
Lassen Sie uns mit Java ein supereinfaches Webframework erstellen
[Java] Erstellen wir einen Minecraft Mod 1.14.4 [0. Basisdatei]
[Java] Erstellen wir einen Minecraft Mod 1.16.1 [Basisdatei]
[Java] Erstellen Sie eine temporäre Datei
Erstellen wir ein Datei-Upload-System mit der Azure Computer Vision-API und dem Java SDK von Azure Storage
Lassen Sie uns eine TODO-App in Java 4 erstellen. Implementierung der Buchungsfunktion
Lassen Sie uns eine TODO-App in Java 6 erstellen. Implementierung der Suchfunktion
Lassen Sie uns eine TODO-App in Java 8 erstellen. Implementierung von Bearbeitungsfunktionen
Erstellen wir eine TODO-Anwendung mit Java 1 Kurze Erläuterung von MVC
Lassen Sie uns eine TODO-App in Java 5 erstellen. Schalten Sie die Anzeige von TODO um
So erstellen Sie eine Zip-Datei beim Gruppieren von Datenbanksuchergebnissen in Java
Lassen Sie uns eine Java-Entwicklungsumgebung erstellen (Aktualisierung)
Erstellen Sie eine TODO-App in Java 7 Create Header
[Java] Erstellen wir eine DB-Zugriffsbibliothek!
Erstellen wir eine TODO-App in Java 9 Erstellen einer TODO-Anzeige Sortieren nach Datum und Uhrzeit + Setzen Sie das Fälligkeitsdatum auf das aktuelle Datum
Android-Laden Sie Bilddateien in den Azure Blob-Speicher in Java hoch
Erstellen wir eine vielseitige Dateispeicher (?) - Operationsbibliothek, indem wir die Dateispeicherung / -erfassung mit Java abstrahieren
[Java] Dateisystembetrieb
Azure funktioniert in Java
Erstellen Sie Azure-Funktionen in Java
Lesen Sie die Java-Eigenschaftendatei in C #
Führen Sie Java-Anwendungen in Azure Batch aus
Entpacken Sie die Zip-Datei in Java
Protokollausgabe in Datei in Java
Informationen zur Dateikopierverarbeitung in Java
Lesen Sie die xlsx-Datei in Java mit Selenium
Beispiel zum Entpacken einer gz-Datei in Java
Elementoperationsmethode in Appium TIPS (Java)
Lesen Sie eine Zeichenfolge in einer PDF-Datei mit Java
Erstellen Sie eine CSR mit erweiterten Informationen in Java
Lassen Sie uns mit Javas Timer einen zeitgesteuerten Prozess erstellen! !!
Versuchen Sie, ein Bulletin Board in Java zu erstellen
[Java] Erstellen wir einen Minecraft Mod 1.14.4 [Einführung]
Eine Bat-Datei, die Java in Windows verwendet
[Java] Erstellen wir einen Minecraft Mod 1.16.1 [Einführung]
[Java] Erstellen wir einen Minecraft Mod 1.14.4 [99. Mod-Ausgabe]
Erstellen wir eine TODO-Anwendung mit Java 11-Ausnahmebehandlung, wenn Sie mit einer nicht vorhandenen ID auf TODO zugreifen
Erstellen Sie ein Java-Servlet und eine JSP-WAR-Datei für die Bereitstellung auf Apache Tomcat 9 mit Gradle
[Java] Erstellen wir einen Minecraft Mod 1.14.4 [4. Tools hinzufügen]
So erstellen Sie eine Java-Umgebung in nur 3 Sekunden
[Java] Lass uns einen Minecraft Mod 1.14.4 erstellen [5. Rüstung hinzufügen]
[CentOS, Eclipse] Lädt Bibliotheksdateien in C-Projekt
[Java] Erstellen wir einen Minecraft Mod 1.14.4 [Extra Edition]
[Java] Erstellen wir einen Minecraft Mod 1.14.4 [7. Fortschritt hinzufügen]
[Java] Erstellen wir einen Minecraft Mod 1.14.4 [6. Rezept hinzufügen]
[Java] Erstellen wir einen Minecraft Mod 1.16.1 [Element hinzufügen]
Ich habe versucht, eine Clova-Fähigkeit in Java zu erstellen
[Java] Erstellen wir einen Minecraft Mod 1.14.4 [1. Element hinzufügen]
So erstellen Sie einen Daten-URI (base64) in Java
So konvertieren Sie eine Datei in ein Byte-Array in Java
[Java] Erstellen wir einen Minecraft Mod 1.14.4 [2. Fügen Sie einen Block hinzu]
Java11: Führen Sie Java-Code unverändert in einer einzelnen Datei aus
[Java] Erstellen wir einen Minecraft Mod 1.16.1 [Block hinzufügen]
Eine Bibliothek, die mehrzeilige Zeichenfolgen in mehrzeiligen Java-Zeichenfolgen realisiert
[Java] Dateisystembetrieb
[Java] Erstellen Sie einen Filter
Erstellen Sie JSON in Java
Aktualisieren Sie Ihre Java-Kenntnisse, indem Sie einen gRPC-Server in Java schreiben (2).
[Java] Erstellen wir einen Minecraft Mod 1.14.4 [3. Registerkarte "Creative hinzufügen"]
Erstellen wir eine Taschenrechner-App mit Java ~ Zeigen Sie das Anwendungsfenster an
Erstellen Sie eine JAR-Datei, die auf Gradle ausgeführt werden kann
Aktualisieren Sie Ihre Java-Kenntnisse, indem Sie einen gRPC-Server in Java schreiben (1)
Erstellen Sie einen SlackBot mit AWS Lambda & API Gateway in Java
Erstellen Sie eine Methode, um den Steuersatz in Java zurückzugeben
Ich möchte eine Parkettdatei auch in Ruby erstellen
Lassen Sie uns auf Deep Java Library (DJL) eingehen, eine von AWS veröffentlichte Bibliothek, die Deep Learning in Java verarbeiten kann.
Erstellen wir eine TODO-App in Java 13 TODO-Formularvalidierung 1: Zeichenbegrenzung · Gradle-Update zur Verwendung von @Validated
Erstellen wir eine TODO-App mit Java 3 Speichern Sie temporäre Daten in MySQL-> Get all-> Display on top