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.
S3
et Cloud Storage
--RDB (type BLOB)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.
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.
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.
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'
}
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));
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.)
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.
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
)
test
(→ ʻInMemoryFileStorageServiceest utilisé) --Production:
production (→ Utiliser
S3FileStorageService`)Assurez-vous que le profil approprié est activé dans chaque environnement.
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
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
Lorsque vous démarrez l'application Spring Boot en production, démarrez-la avec l'option --spring.profiles.active = production
.
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