Créons une bibliothèque d'opérations de stockage de fichiers polyvalente (?) En faisant abstraction du stockage / acquisition de fichiers avec Java

Nanikore

Lors de la création d'une application (* ici, Java côté serveur est supposé), je pense qu'il est souvent nécessaire de sauvegarder et de récupérer des fichiers. Il existe différents candidats pour stocker des fichiers.

ici

--Définissez une interface qui fait abstraction du stockage de fichiers qui ne dépend pas d'une méthode de stockage spécifique,

Je vais vous montrer comment faire cela.

Le formulaire rempli peut être trouvé sur GitHub.

Aperçu de ce qu'il faut faire

Nous implémenterons le stockage de fichiers en tant que «stockage de fichiers». Le "stockage de fichiers" est comme un magasin clé-valeur qui gère les fichiers.

--Key (emplacement du fichier) --Valeur (contenu du fichier)

Supposons que vous gériez des fichiers sous la forme de.

Aperçu de la mise en œuvre

Nous fournirons une interface appelée FileStorageService, à travers laquelle les fichiers peuvent être enregistrés / récupérés.

Il existe trois types d'implémentations FileStorageService.

--LocalFileStorageService (Enregistrer les fichiers dans le système de fichiers de l'hôte local. En supposant qu'ils seront utilisés pendant le développement local) --S3FileStorageService (Enregistrer dans S3. En supposant une utilisation dans un environnement de production) --ʻInMemoryFileStorageService` (Conserve les fichiers en mémoire, en supposant une utilisation pendant le CI et les tests automatisés)

Enfin, configurez Spring Boot pour remplacer ces implémentations en fonction de votre environnement.

Bibliothèques dépendantes

Cette fois, nous utiliserons commons-io et ʻaws-java-sdk-s3. Plongeons dans les «dépendances». J'utilise habituellement Lombok`, mais je ne l'utiliserai pas cette fois car ce sera une implémentation relativement simple.

dependencies {
  implementation 'commons-io:commons-io:2.6'
  implementation 'com.amazonaws:aws-java-sdk-s3:1.11.774'
}

Préparer une API pour faire fonctionner le "stockage de fichiers"

Tout d'abord, nous allons faire le côté "côté". J'en ferai trois.

--FileStorageService ... Celui qui gère le traitement tel que l'enregistrement / la récupération de fichiers. Le rôle principal. --FileLocation ... Un objet de valeur qui représente l'emplacement d'un fichier sur le stockage. --FileStorageObject ... Un objet qui représente le contenu d'un fichier

C'est une image à utiliser comme ça.

FileStorageService fileStorageService = ...;

//Enregistrez le fichier
fileStorageService.putFile(FileLocation.of("hoge/fuga/sample.txt"), file);

//Extraire le fichier enregistré
FileStorageObject fileStorageObject = fileStorageService.getFile(FileLocation.of("hoge/fuga/sample.txt"));
InputStream is = fileStorageObject.getInputStream(); //Le contenu du fichier peut être obtenu avec InputStream

La clé qui représente l'emplacement du fichier peut être «String», mais préparons un objet de valeur appelé «FileLocation» qui encapsule «String».

FileLocation.java

Tout d'abord, créez un objet qui représente l'emplacement (clé) du fichier.

import java.util.Objects;

/**
 *L'emplacement du fichier sur le stockage de fichiers.
 */
public class FileLocation {

  /**
   *chemin."parent/child/file.txt"En supposant une valeur comme.
   */
  private final String value;

  private FileLocation(String value) {
    this.value = value.startsWith("/") ? value.substring(1) : value;
  }

  /**
   *À partir d'une chaîne{@link FileLocation}Créez une instance de.
   *
   * @chemin de la valeur du paramètre
   * @instance de retour
   */
  public static FileLocation of(String value) {
    return new FileLocation(value);
  }

  /**
   *À partir de plusieurs chaînes{@link FileLocation}Créez une instance de.
   *Chaque chaîne de caractères est"/"Il est connecté par.
   *
   * @parties param Plusieurs chaînes qui composent le chemin
   * @instance de retour
   */
  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;
  }

  //Implémenter hashCode et equals
  ...
}

Vous pouvez obtenir l'objet FileLocation comme ceci.

FileLocation fileLocation = FileLocation.of("key/to/file.txt");

C'est pareil. (J'ai fait référence à l'API standard Java Paths.get ())

FileLocation fileLocation = FileLocation.of("key", "to", "file.txt");

FileStorageObject.java

Ensuite, créez un objet qui représente le fichier extrait du stockage. Utilisé dans la valeur de retour de FileStorageService # getFile ().

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;

/**
 *Un objet qui représente le contenu d'un fichier sur le stockage.
 */
public interface FileStorageObject {

  /**
   *Le contenu du fichier{@link InputStream}Entrer.
   *
   * @return {@link InputStream}
   */
  InputStream getInputStream();
}

Créez comme interface. Ici, seule la méthode qui renvoie ʻInputStream` est utilisée, mais il est possible de créer diverses méthodes pratiques.

FileStorageService.java

Enfin, faites une personne qui gère le fichier. C'est le rôle principal.

import java.io.InputStream;
import java.nio.file.Path;

/**
 *Un service de manipulation de fichiers dans le stockage de fichiers.
 */
public interface FileStorageService {

  /**
   *Enregistrez le fichier.
   *
   * @param fileLocation Destination sur le stockage
   * @Contenu du fichier param inputStream
   */
  void putFile(FileLocation fileLocation, InputStream inputStream);

  /**
   *Enregistrez le fichier.
   *
   * @param fileLocation Destination sur le stockage
   * @param localFile Fichier à enregistrer
   */
  void putFile(FileLocation fileLocation, Path localFile);

  /**
   *Supprimez le fichier.
   *Si le fichier n'existe pas, ne faites rien.
   *
   * @param fileLocation Destination sur le stockage
   */
  void deleteFile(FileLocation fileLocation);

  /**
   *Récupérez le fichier.
   *
   * @param fileLocation Emplacement sur le stockage
   * @retourne l'objet File. Null s'il n'existe pas
   */
  FileStorageObject getFile(FileLocation fileLocation);
}

C'est comme une carte avec FileLocation comme clé et le contenu du fichier comme valeur. La méthode putFile () fournit putFile (InputStream) et putFile (Path), Vous pouvez tout sauvegarder tant que vous avez ʻInputStream. Si vous voulez spécifier le contenu du fichier avec byte []`, cela ressemble à ceci.

byte[] bytes = ...;
fileStorageService.putFile(FileLocation.of("hoge"), new ByteArrayInputStream(bytes));

Préparer une implémentation de FileStorageService

Dans ↑, FileStorageService est créé en tant qu'interface, il n'y a donc pas de contenu. Ici, nous en implémenterons trois, LocalFileStorageService, S3FileStorageService et ʻInMemoryFileStorageService`.

LocalFileStorageService.java

Tout d'abord, créez une implémentation de FileStorageService qui stocke les fichiers dans votre système de fichiers local. Reçoit le répertoire racine de l'emplacement de stockage des fichiers en tant qu'argument de constructeur.

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);
      }
    }
  }
}

Si vous aimez ↓, un fichier sera créé dans / hoge / fuga / abc / efg.txt.

FileStorageService fileStorageService = new LocalFileStorageService(Paths.get("/hoge/fuga"));
fileStorageService.putFile(FileLocation.of("abc/efg.txt"), file);

S3FileStorageService.java

Ensuite, créez une implémentation de FileStorageService qui stocke les fichiers dans AWS S3.

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) {
      //Si vous essayez de télécharger directement avec InputStream, vous devez définir ContentLength, donc écrivez-le une fois dans un fichier
      //PutFile si vous vous souciez des performances(FileLocation, InputStream, int contentLength)Ou peut-être que vous pouvez vous préparer
      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

Enfin, créez un FileStorageService qui contient les fichiers en mémoire.

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);
    }
  }
}

À ce stade, l'implémentation de la bibliothèque qui gère le «stockage de fichiers» est terminée. (Des versions légèrement améliorées sont disponibles sur GitHub.)

Avec Spring Boot, remplacez l'implémentation pour chaque environnement

Chose que tu veux faire

Remplaçons l'implémentation de 3 types de service de stockage de fichiers en fonction de l'environnement. Les utilisateurs de FileStorageService ont une instance de FileStorageService injectée dans Spring afin qu'ils n'aient pas à savoir quelle implémentation est utilisée.

@Service
public class SampleService {

  private final FileStorageService fileStorageService; //Soyez injecté. Vous n'avez pas besoin de savoir quelle implémentation est utilisée

  public SampleService(FileStorageService fileStorageService) { //Injection de constructeur
    this.fileStorageService = fileStorageService;
  }

  public void doSomething() {
    fileStorageService.getFile(...);
  }
}

Le reste est parfait si l'instance de FileStorageService à injecter est commutée en fonction de l'environnement.

Politique de réalisation

Spring a [Profiles](https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-profiles] comme mécanisme pour changer les paramètres pour chaque environnement. ). Utilisons ceci pour remplacer l'implémentation de FileStorageService pour chaque environnement.

Ici, définissez trois profils en fonction de l'environnement.

--Pendant le développement local: development (→ use LocalFileStorageService)

Paramètres de profil

Assurez-vous que le profil approprié est activé dans chaque environnement.

Pendant le développement local: développement

Permet au profil développement d'être activé par défaut. Si vous écrivez ↓ dans src / main / resources / application.yml, ce profil sera activé lorsque vous démarrez normalement l'application Spring Boot.

src/main/resources/application.yml


spring:
  profiles:
    active: development

Pendant le test automatique: test

Ensuite, assurez-vous que le profil test est activé lorsque vous exécutez le test. Écrivez comme ↓ dans src / test / resources / application.yml. (Au moment du test, ceci est prioritaire sur ↑)

src/test/resources/application.yml


spring:
  profiles:
    active: test

Production: «production»

Lorsque vous démarrez l'application Spring Boot en production, démarrez-la avec l'option --spring.profiles.active = production.

Enregistrer l'implémentation de FileStorageService selon Profile en tant que bean

Enregistrez le bean à l'aide de Java Config. Vous pouvez utiliser l'annotation @ Profile pour générer et enregistrer un bean uniquement lorsqu'un profil particulier est valide.

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();
  }
}

Dans la partie de ↑ où c'est @Value (" $ {key} "), si vous écrivez le paramètre correspondant à ʻapplication- {profile} .yml`, la valeur sera injectée automatiquement.

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

Créons une bibliothèque d'opérations de stockage de fichiers polyvalente (?) En faisant abstraction du stockage / acquisition de fichiers avec Java
Créons un framework Web ultra-simple avec Java
[Java] Créons un Minecraft Mod 1.14.4 [0. Fichier de base]
[Java] Créons un Minecraft Mod 1.16.1 [Fichier de base]
[Java] Créer un fichier temporaire
Créons un système de téléchargement de fichiers à l'aide de l'API Azure Computer Vision et du SDK Java d'Azure Storage
Créons une application TODO en Java 4 Implémentation de la fonction de publication
Créons une application TODO en Java 6 Implémentation de la fonction de recherche
Créons une application TODO en Java 8 Implémentation des fonctions d'édition
Créons une application TODO avec Java 1 Brève explication de MVC
Créons une application TODO en Java 5 Changer l'affichage de TODO
Pour créer un fichier Zip lors du regroupement des résultats de recherche de base de données en Java
Créons un environnement de développement Java (mise à jour)
Créer une application TODO dans Java 7 Créer un en-tête
[Java] Créons une bibliothèque d'accès à la base de données!
Créons une application TODO en Java 9 Créer un affichage TODO Trier par date et heure + Définir la date d'échéance sur la date actuelle
Android-Télécharger des fichiers image vers Azure Blob Storage en Java
Créons une bibliothèque d'opérations de stockage de fichiers polyvalente (?) En faisant abstraction du stockage / acquisition de fichiers avec Java
[Java] Fonctionnement du système de fichiers
Fonctions Azure en Java
Créer des fonctions Azure en Java
Lire le fichier de propriétés Java en C #
Exécuter des applications Java dans Azure Batch
Décompressez le fichier zip en Java
Enregistrer la sortie dans un fichier en Java
A propos du traitement de la copie de fichiers en Java
Lire le fichier xlsx en Java avec Selenium
Exemple pour décompresser le fichier gz en Java
Méthode d'opération d'élément dans Appium TIPS (Java)
Lire une chaîne dans un fichier PDF avec Java
Créer un CSR avec des informations étendues en Java
Créons un processus chronométré avec la minuterie de Java! !!
Essayez de créer un babillard en Java
[Java] Créons un Minecraft Mod 1.14.4 [Introduction]
Un fichier bat qui utilise Java dans Windows
[Java] Créons un Minecraft Mod 1.16.1 [Introduction]
[Java] Créons un Minecraft Mod 1.14.4 [99. Mod output]
Créons une application TODO en Java 11 Gestion des exceptions lors de l'accès à un TODO avec un ID inexistant
Créer un servlet Java et un fichier WAR JSP à déployer sur Apache Tomcat 9 avec Gradle
[Java] Créons un Minecraft Mod 1.14.4 [4. Ajouter des outils]
Comment créer un environnement Java en seulement 3 secondes
[Java] Créons un Minecraft Mod 1.14.4 [5. Ajouter une armure]
[CentOS, Eclipse] Charger les fichiers de bibliothèque dans le projet C
[Java] Créons un Minecraft Mod 1.14.4 [édition supplémentaire]
[Java] Créons un Minecraft Mod 1.14.4 [7. Add progress]
[Java] Créons un Minecraft Mod 1.14.4 [6. Ajouter une recette]
[Java] Créons un Minecraft Mod 1.16.1 [Ajouter un élément]
J'ai essayé de créer une compétence Clova en Java
[Java] Créons un Minecraft Mod 1.14.4 [1. Ajouter un élément]
Comment créer un URI de données (base64) en Java
Comment convertir un fichier en tableau d'octets en Java
[Java] Créons un Minecraft Mod 1.14.4 [2. Ajouter un bloc]
Java11: exécuter le code Java dans un seul fichier tel quel
[Java] Créons un Minecraft Mod 1.16.1 [Ajouter un bloc]
Une bibliothèque qui réalise des chaînes multilignes en chaîne multiligne Java
[Java] Fonctionnement du système de fichiers
[Java] Créer un filtre
Créer JSON en Java
Mettez à jour vos connaissances Java en écrivant un serveur gRPC en Java (2)
[Java] Créons un Minecraft Mod 1.14.4 [3. Ajouter un onglet de création]
Faisons une application de calcul avec Java ~ Afficher la fenêtre de l'application
Créez un fichier jar qui peut être exécuté sur Gradle
Mettez à jour vos connaissances Java en écrivant un serveur gRPC en Java (1)
Créer un SlackBot avec AWS lambda et API Gateway en Java
Créer une méthode pour renvoyer le taux de taxe en Java
Je veux créer un fichier Parquet même en Ruby
Voyons la Deep Java Library (DJL), une bibliothèque capable de gérer Deep Learning en Java, publiée par AWS.
Créons une application TODO dans Java 13 TODO form validation 1: Character limit-Update Gradle to use @Validated
Créons une application TODO avec Java 3 Enregistrez les données temporaires dans MySQL-> Tout obtenir-> Afficher en haut