クラウド上のストレージとして、AWSで利用可能なAmazon S3。今回以降はSpring Cloud AWSを用いてAmazon S3へアクセスするSpringアプリケーションの実装方法について解説します。
前回、 Spring Cloud AWSを使ったS3アクセスアプリケーション(1) に引き続き、 Spring Cloud AWSでS3へアクセスするアプリケーションを実装しています。
アプリケーションコンポーネントの実装に移ります。Controllerでは、以下3種類の処理を実装します。
※今回は比較的小さいファイルサイズの画像を扱うことを想定して、Controllerから取得する例を実装しています。 なお、リクエストマッピング実装の要領については TERASOLUNAガイドライン リクエストとハンドラメソッドのマッピング方法 も適宜参考にしてください。
package org.debugroom.mynavi.sample.aws.s3.app.web;
import java.awt.image.BufferedImage;
// omit
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class SampleController {
@Autowired
S3DownloadHelper s3DownloadHelper;
@Autowired
S3UploadHelper s3UploadHelper;
// omit
@GetMapping(value = "/image",
headers = "Accept=image/jpeg, image/jpg, image/png, image/gif",
produces = {MediaType.IMAGE_JPEG_VALUE, MediaType.IMAGE_PNG_VALUE, MediaType.IMAGE_GIF_VALUE})
@ResponseBody
public ResponseEntity<BufferedImage> getImage(){
return ResponseEntity.ok().body(
s3DownloadHelper.getImage("sample.jpg"));
}
@GetMapping("getTextFileBody")
@ResponseBody
public ResponseEntity<String> getTextFileBody(){
return ResponseEntity.ok().body(
s3DownloadHelper.getTextFileBody("test.txt"));
}
@PostMapping("upload")
public String upload(FileUploadForm fileUploadModel){
s3UploadHelper.saveFile(fileUploadModel.getUploadFile());
return "redirect:/uploadResult.html";
}
// omit
}
Controllerから呼び出すS3でダウンロード、アップロードを行う処理をHelperとして実装します。 ダウンロード処理では、org.springframework.core.io.ResourceLoaderで、 S3のバケットプレフィックスを指定してオブジェクトキーを指定し、InputStreamとして読み込みを行います。 なお、画像ファイルの場合はデータ型としてjava.awt.image.BufferedImageを使用し、テキストデータなどの場合は、org.apache.commons.io.IOUtilsなどのユーティリティライブラリを使ってストリームデータをString型へ変換します。
package org.debugroom.mynavi.sample.aws.s3.app.web.helper;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.stereotype.Component;
@Component
public class S3DownloadHelper{
private static final String S3_BUCKET_PREFIX = "s3://";
private static final String DIRECTORY_DELIMITER = "/";
@Value("${bucket.name}")
private String bucketName;
@Autowired
ResourceLoader resourceLoader;
public BufferedImage getImage(String imageFilePath){
Resource resource = resourceLoader.getResource(
new StringBuilder()
.append(S3_BUCKET_PREFIX)
.append(bucketName)
.append(DIRECTORY_DELIMITER)
.append(imageFilePath)
.toString());
BufferedImage image = null;
try(InputStream inputStream = resource.getInputStream()){
image = ImageIO.read(inputStream);
}catch (IOException e){
e.printStackTrace();
}
return image;
}
public String getTextFileBody(String textFilePath){
Resource resource = resourceLoader.getResource(
new StringBuilder()
.append(S3_BUCKET_PREFIX)
.append(bucketName)
.append(DIRECTORY_DELIMITER)
.append(textFilePath)
.toString());
String textBody = null;
try(InputStream inputStream = resource.getInputStream()){
textBody = IOUtils.toString(inputStream, "UTF-8");
}catch (IOException e){
e.printStackTrace();
}
return textBody;
}
}
アップロード処理は同じくResourceLoaderを経由して、S3のバケットプレフィックスを保存したいオブジェクトキーと組み合わせ、 WritableResourceとして取得し、OutputStreamにデータを保存します。また、バケット上のディレクトリを含めた、 オブジェクキーのデータが存在するかどうかResourcePatternResolverを使って検索ができますが、 ディレクトリの作成やデータの削除などの処理はSDKのライブラリとして提供されているcom.amazonaws.services.s3.AmazonS3を使って直接操作を行う必要があります。
package org.debugroom.mynavi.sample.aws.s3.app.web.helper;
// omit
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectRequest;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.WritableResource;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
@Component
public class S3UploadHelper{
private static final String S3_BUCKET_PREFIX = "s3://";
private static final String DIRECTORY_DELIMITER = "/";
@Value("${bucket.name}")
private String bucketName;
@Autowired
ResourceLoader resourceLoader;
@Autowired
ResourcePatternResolver resourcePatternResolver;
@Autowired
AmazonS3 amazonS3;
public String saveFile(MultipartFile multipartFile){
String objectKey = new StringBuilder()
.append(S3_BUCKET_PREFIX)
.append(bucketName)
.append(DIRECTORY_DELIMITER)
.append(multipartFile.getOriginalFilename())
.toString();
WritableResource writableResource = (WritableResource)resourceLoader.getResource(objectKey);
try(InputStream inputStream = multipartFile.getInputStream();
OutputStream outputStream = writableResource.getOutputStream()){
IOUtils.copy(inputStream, outputStream);
}catch (IOException e){
e.printStackTrace();
}
return objectKey;
}
public boolean existsDirectory(String directoryPath){
try{
List<Resource> resourceList = Arrays.asList(
resourcePatternResolver.getResources(directoryPath + "/**"));
if (resourceList.size() == 0){
return false;
}
}catch (IOException e){
e.printStackTrace();
}
return true;
}
public void createDirectory(String directoryPath){
ObjectMetadata objectMetadata = new ObjectMetadata();
try(InputStream emptyContent = new ByteArrayInputStream(new byte[0]);){
PutObjectRequest putObjectRequest = new PutObjectRequest(
bucketName, directoryPath, emptyContent, objectMetadata);
amazonS3.putObject(putObjectRequest);
}catch (IOException e){
e.printStackTrace();;
}
}
実装が完了したら、画面を作成し、実際に画像がダウンロードされるかを確認し、アップロード処理を実行してみましょう。 今回アップロードしていた「sample.jpg」は当連載のバナー画像であり、「test.txt」をアップロードして、 「Get TextFile Body」ボタンを押して、その内容を取得してみます。
ファイルがアップロードされていることが確認できます。
アップロードしたファイルの中身を取得し、表示します。
このように、S3にアクセスしてダウンロード・アップロードするアプリケーションをSpring Cloud AWSを用いて簡単に実装することができます。 AWS上に構築するクラウドネイティブなアプリケーションは、データ保存にS3を利用することで、可用性・信頼性が高い構成が可能です。 なお、署名つきURLや、一時認証情報を使って、クライアントからS3に直接ファイルをダウンロード・アップロードする方法については、 今回GitHub上にサンプル実装していますが、AWS上のIAMアクセスロール設定やサーバ側のアプリケーション実装が複雑で基本の範疇を越えるため、 詳細な解説は発展編へ譲りたいと思います。
次回は、基本編の最終になりますが、AmazonSQSを使ったSpringアプリケーション(オンライン・バッチ)の実装方法を解説します。
川畑 光平(KAWABATA Kohei)
某システムインテグレータにて、金融機関システム業務アプリケーション開発・システム基盤担当を経て、現在はソフトウェア開発自動化関連の研究開発・推進に従事。
Red Hat Certified Engineer、Pivotal Certified Spring Professional、AWS Certified Solutions Architect Professional等の資格を持ち、アプリケーション基盤・クラウドなど様々な開発プロジェクト支援にも携わる。
2019 APN AWS Top Engineers & Ambassadors 選出。
本連載記事の内容に対するご意見・ご質問は Facebook まで。