【AWS Java SDK 1 ＆ 2】LocalStackを使ってS3にファイルを保存する

# 目的
投稿時点でAWS for Java SDK 2 の情報がネット上に少ない、探しにくい印象を受けたのでコードを書いてみました。v1で運用してる人が多いのかな。いずれv2が主流になる？既になってる？ので移行の参考になるような記事を今後も追加したいなと考えてます。LocalStackを活用してAWSを無料で遊んでみたい人や、AWSをローカルで動確/テストしたい人向けの記事になりますので、LocalStackなんてケチくさいぞ本番でジャンジャン課金してけばいいんだよって方はブラウザバック安定です（笑）


【追記】
やっぱりサポート切れるじゃん:scream::scream::scream:移行めんどい:innocent:
2025 年 12 月 31 日をもって AWS SDK for Java v1.x のサポートを終了
https://aws.amazon.com/jp/blogs/developer/announcing-end-of-support-for-aws-sdk-for-java-v1-x-on-december-31-2025/


# 環境
OS：Windows11
java：Amazon Corretto v17
DockerDesktop：v4.25.2




# v2の新機能
公式サイトからの引用です。
> 独自の HTTP クライアントを設定できます。

> 非同期クライアントが完全に非ブロックとなり、CompletableFutureオブジェクトを返すようになりました。

> 複数ページを返すオペレーションには、自動ページ分割レスポンスがあります。これにより、後続のページを確認して取得する必要がなくなり、レスポンスをどのように処理するかのコードに集中できます。

> AWS Lambda 関数のSDK起動時間のパフォーマンスが強化されました。

> バージョン 2.x は、リクエストを作成するための、新しい簡易的な方法をサポートします。

# Docker Desktopの導入
[Docker Desktop公式サイト](https://www.docker.com/products/docker-desktop/)
Docker用に仮想環境を構築してくれるソフトウェアです。一時期、有料化で話題になりましたね。LocalStackを動作させるために必要になります。QiitaにDocker環境構築の記事は沢山あるため、ここでは割愛します。

# AWS CLIのインストール
[AWS公式サイト](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/getting-started-install.html)
上記サイトからWindows用の資材をインストールすればコマンドプロンプトでAWS CLIが使えるようになります。


# LocalStackについて
[LocalStack公式サイト](https://localstack.cloud/)
AWSサービスをローカルで使うことが出来るエミュレーションサービスです。無料版と有料版がありますが無料でも十分AWSをローカル環境で開発、テスト可能です。とりあえずAWSに触れてみたい方にはオススメのサービスです。serverlessなサービスなら無料でほとんど利用できます。（S3、lamdba、SQS、DynamoDBなど）


## LocalStack導入の準備
公式LocalStackがawsコマンドをラッパーしたawslocalコマンドを提供しています。endpoint設定を省略できるだけかもですが少し楽になります。以下のコマンドでインストール可能です。（Windowsの場合はPythonをインストールしないとpipコマンド叩けないです）
```
pip install awscli-local
```
[LocalStack AWS CLI (awslocal) ](https://docs.localstack.cloud/user-guide/integrations/aws-cli/#localstack-aws-cli-awslocal)

awsコマンドでLocalStackで作成したS3のバケットを確認したい場合は
```
aws s3 ls --endpoint http://localhost:4566
```
のようにエンドポイントを指定する必要があります。

awscli-localの場合は
```
awslocal s3 ls
```
だけになります。

他にもGUIでLocalStackを操作可能な「Localstack Cockpit」なんてツールも提供してますが、まだBeta版なためか私の環境ではエラー吐きまくりでまともに動作しませんでした。。
（LocalStack Cockpit 0.1.1は発行元不明でウイルスチェックに引っ掛かる始末😢😢😢）
## LocalStackをDocker上で起動する
docker-compose.ymlを使います。初期ポートは4566です。AWS_ACCESS_KEY_IDやAWS_SECRET_ACCESS_KEYは認証する必要がないためdummyで構いません。以下、S3動作確認時のサンプルになります。
```yml
version: '3.8'

services:
  localstack:
    container_name: localstack
    image: localstack/localstack
    ports:
      - "127.0.0.1:4566:4566"            
      - "127.0.0.1:4510-4559:4510-4559"  
    environment:
      - SERVICES=dynamodb,sqs,sns,ses,s3,lamdba
      - AWS_ACCESS_KEY_ID=dummy
      - AWS_SECRET_ACCESS_KEY=dummy
      - AWS_DEFAULT_REGION=ap-northeast-1
      - DATA_DIR=/tmp/localstack/data
    volumes:
      - ./docker/localstack:/docker-entrypoint-initaws.d
      -  /var/run/docker.sock:/var/run/docker.sock
```




最後に以下のコマンドでコンテナを立ち上げて準備完了です！
```
docker-compose up -d
```
最終行に```Container localstack                 Started```と表示されればOKです。


## AWS CLIでバケットを作成する
javaでバケットを作成することも出来ますがコマンドで作成しちゃいます。
```
aws s3 mb s3://bucket-name --endpoint http://localhost:4566
```
または
```
awslocal s3 mb s3://bucket-name
```
```make_bucket: bucket-name```と表示されたら作成完了です。

# ローカルファイルをS3にアップロードする
![uploadToS3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121906/9974a08e-e1fd-cfd2-f7d8-26377a097b41.png)
v1とv2のjavaでの実装の違いを確認してみます。
## AWS for Java SDK 1.x のコード
認証情報などは引数から渡せるようにしてます。ローカル環境なのでアクセスキーとシークレットキーは"dummy"とかで大丈夫です。公式のsampleソースをもとに作成しましたが、必要な個所をコピペするだけです。
```java
public void UploadObjectToS3(String region,String endpoint,String accessKey,String secretKey,String bucketName,String filePath,String fileName) throws Exception {

    try {
            // S3 クライアント
            AmazonS3 s3Client = AmazonS3ClientBuilder.standard()
                    .withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(endpoint, region))
                    .withPathStyleAccessEnabled(true)
                    .withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials(accessKey, secretAccessKey)))
                    .build();

            // ファイルアップロード
            PutObjectResult result = s3Client.putObject(bucketName, fileName, new File(filePath));

        } catch (S3Exception e) {
            // 例外処理
        } catch (SdkClientException e) {
            // 例外処理
        } catch (SdkServiceException e) {
            // 例外処理
        }
}
```



## AWS for Java SDK 2.x のコード
v2からクライアントをcloseしてリソース開放可能になりました。try-with-resources文で実装した方が良いかもです。あとはメソッドチェーンにwithが無くなっているくらいでほとんど差はないですね。v1ではputObjectメソッドの引数にFileクラスを指定出来ましたが、v2ではバイト配列に変換する必要があります。v1とv2でnamespaceが異なります。併用環境は是非v2への移行を。
```java
public void UploadObjectToS3(String accessKey,String secretKey,String endpoint,String bucketName,String filePath,String fileName) throws Exception {

    S3Client s3Client = null;

    try {
            // S3クライアント
            s3Client = S3Client.builder()
        		            .credentialsProvider(() -> (AwsBasicCredentials.create(accessKey,secretAccessKey)))
        		            .region(Region.AP_NORTHEAST_1)
        		            .forcePathStyle(true)
        		            .endpointOverride(URI.create(endpoint))
        		            .build();
        	
            PutObjectRequest putObjectRequest = PutObjectRequest.builder()
                .bucket(bucketName)
                .key(fileName)
                .build();
        
            File file = new File(filePath);
            byte[] data = Files.readAllBytes(file.toPath());
        
            // ファイルアップロード
            PutObjectResponse response = s3Client.putObject(putObjectRequest, RequestBody.fromBytes(data));

        } catch (S3Exception e) {
            // 例外処理
        } catch (SdkClientException e) {
            // 例外処理
        } catch (SdkServiceException e) {
            // 例外処理
        } finally {
            s3Client.close();
        }
    
}
```

AWSのサーバーレスサービスは運用コストも安く、コーティングも殆ど発生しないからとっても便利ですね。冪等性とか癖の強い面もあるので扱うのは結構大変ですが。。。