본문 바로가기
infra/aws

[AWS SDK for JAVA] S3 버전 1.x에서 2.x로 마이그레이션

by moonsiri 2024. 6. 4.
728x90
반응형

AWS SDK for Java에서 S3(Simple Storage Service)를 사용할 때, 버전 1.x에서 2.x로 마이그레이션하는 과정에 대해 설명하겠습니다. 버전 2.x는 많은 변경 사항과 개선된 기능을 제공하므로, 이를 통해 더 나은 성능과 유지보수성을 확보할 수 있습니다.

 

Overview

`TransferManager`를 사용한 multipart 업로드 시 이슈가 발생하였습니다. 문제의 원인을 조사한 결과, 해당 문제가 GitHub 이슈로 등록되어 있었습니다. AWS 측에서는 현재 Java SDK 버전 1에 대한 지원을 중단하고 버전 2를 지원하고 있는 상황이어서, AWS SDK를 버전 2로 변경하기로 결정했습니다.

AWS SDK 버전 1.x는 `groupId`로 `com.amazonaws`를 사용하고, 버전 2.x는 `software.amazon.awssdk`를 사용합니다.

 

Migration

Maven dependency

먼저, 프로젝트의 의존성을 변경해야 합니다. 다음은 Maven을 사용하는 경우의 예시입니다.

V1)

<dependency>
    <groupId>com.amazonaws</groupId>
    <artifactId>aws-java-sdk-s3</artifactId>
    <version>1.12.372</version>
</dependency>

V2)

<dependency>
    <groupId>software.amazon.awssdk</groupId>
    <artifactId>s3</artifactId>
    <version>2.25.64</version>
</dependency>

<!-- transfer manager 사용 시 추가 -->
<dependency>
    <groupId>software.amazon.awssdk</groupId>
    <artifactId>s3-transfer-manager</artifactId>
    <version>2.25.64</version>
</dependency>
<dependency>
    <groupId>software.amazon.awssdk</groupId>
    <artifactId>auth-crt</artifactId>
    <version>2.25.64</version>
</dependency>

 

Configuration

버전 2.x에서는 새로운 인증 및 클라이언트 빌더를 사용합니다.

V1)

import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;

@Configuration
public class AwsS3Configuration {

	@Bean
	public AmazonS3 amazonS3Client() {
		BasicAWSCredentials creds = new BasicAWSCredentials(accessKey, secretKey);
		final Regions region = Regions.AP_NORTHEAST_2;
		return AmazonS3ClientBuilder.standard().withCredentials(new AWSStaticCredentialsProvider(creds)).withRegion(region).build();

	}
}

V2)

import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3AsyncClient;
import software.amazon.awssdk.services.s3.internal.crt.S3CrtAsyncClient;

@Configuration
public class AwsS3Configuration {

	@Bean
	public S3AsyncClient amazonS3Client() {
		AwsBasicCredentials creds = AwsBasicCredentials.create(accessKey, secretKey);
		final Region region = Region.of(region);
		return S3CrtAsyncClient.builder()
				.credentialsProvider(StaticCredentialsProvider.create(creds))
				.region(region)
			.build();
	}

    /** Pre-signed 사용 시 추가 */
    @Bean
    public S3Presigner amazonS3Presigner() {
		AwsBasicCredentials creds = AwsBasicCredentials.create(accessKey, secretKey);
		final Region region = Region.of(region);
		return S3Presigner.builder()
				.credentialsProvider(StaticCredentialsProvider.create(creds))
				.region(region)
			.build();
    }
}

V2부터 비동기 방식의 클라이언트를 사용하는 이유는 다음과 같습니다:

  • 성능 향상: 비동기 방식은 I/O 작업을 비동기로 처리하여 높은 동시성을 제공하며, 대규모 데이터 처리 시 성능을 향상시킵니다.
  • 자원 효율성: 비동기 클라이언트는 CPU와 메모리 사용을 최적화하여 리소스 효율성을 높입니다.
  • 유연성: 비동기 프로그래밍은 다양한 작업을 병렬로 수행할 수 있어 복잡한 워크플로우를 간단하게 처리할 수 있습니다.

 

`S3CrtAsyncClient`는 고성능 비동기 클라이언트로, multipart upload와 같은 기능을 제공하지만 path style access 설정은 지원하지 않습니다. path style access가 필요한 경우 `S3AsyncClient`를  사용해야합니다.

import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3AsyncClient;
import software.amazon.awssdk.services.s3.S3Configuration;

@Configuration
public class AwsS3Configuration {

	@Bean
	public S3AsyncClient amazonS3Client() {
		AwsBasicCredentials creds = AwsBasicCredentials.create(accessKey, secretKey);
		final Region region = Region.AP_NORTHEAST_2;
		return S3AsyncClient.builder()
				.credentialsProvider(StaticCredentialsProvider.create(creds))
				.region(region)
				.serviceConfiguration(S3Configuration.builder().pathStyleAccessEnabled(true).build())
			.build();
	}
}

 

 

[JAVA] AWS S3 Https 연결이 비공개로 설정되어 있지 않습니다. (Your connection is not private) 해결방안

Https 연결 사용 시 서버 인증서가 필요합니다. 서버 인증서는 CA에서 서명한 x.509 v3 데이터 구조입니다.서버 인증서에는 서버 이름, 유효 기간, 퍼블릭 키 및 기타 데이터가 포함됩니다.브라우저

moonsiri.tistory.com

 

 

 

 

Upload 1

다음은 S3에 직접 업로드하는 방식입니다. 간단한 업로드 작업에 적합하며, 빠른 설정과 사용이 가능합니다.

V1)

import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectRequest;

	public void fileUpload(MultipartFile mfFile) {

		try {
			Mimetypes mimetypes = Mimetypes.getInstance();
			ObjectMetadata objMeta = new ObjectMetadata();

			byte[] bytes = IOUtils.toByteArray(targetIS);
			objMeta.setContentLength(bytes.length);
			objMeta.setContentType(mimetypes.getMimetype(saveFileNm)); //중요 : ContentType 넣어야 웹 등에서 정상 파일타입으로 처리 가능

			ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);

			PutObjectRequest putObjReq = new PutObjectRequest(bucketName, objectKey, byteArrayInputStream, objMeta);
			amazonS3Client.putObject(putObjReq);
		} catch (IOException e) {
			log.warn("파일 업로드 실패");
			throw new RuntimeException();
		}
	}

V2)

import software.amazon.awssdk.core.async.AsyncRequestBody;
import software.amazon.awssdk.services.s3.S3AsyncClient;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;

	public void fileUpload(MultipartFile mfFile) {

		try {
			byte[] bytes = IOUtils.toByteArray(targetIS);
			String contentType =  Files.probeContentType(Paths.get(saveFileNm));

			PutObjectRequest putObjReq = PutObjectRequest.builder()
				.bucket(bucketName)
				.key(objectKey)
				.contentType(contentType)
				.build();

			amazonS3Client.putObject(putObjReq, AsyncRequestBody.fromBytes(bytes));
		} catch (IOException e) {
			log.warn("파일 업로드 실패");
			throw new RuntimeException();
		}
	}

 

Upload 2

다음은 TransferManager를 사용하여 업로드하는 방식입니다. 대용량 파일이나 멀티파트 업로드와 같은 복잡한 업로드 작업에 적합하며, 성능 최적화와 편리한 업로드 기능을 제공합니다.

V1)

import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectRequest;
import com.amazonaws.services.s3.transfer.TransferManager;
import com.amazonaws.services.s3.transfer.TransferManagerBuilder;


	public void fileUpload(List<MultipartFile> fileList) {

		TransferManager tm = TransferManagerBuilder.standard().withS3Client(amazonS3Client).build();

		for (MultipartFile mfFile : fileList) {
			if (mfFile.isEmpty()) {
				continue;
			}

			final String oriFileNm = mfFile.getOriginalFilename();

			try {
				ObjectMetadata objMeta = new ObjectMetadata();
				MimetypesFileTypeMap mimeTypesMap = new MimetypesFileTypeMap();
				String contentType = mimeTypesMap.getContentType(oriFileNm);
				objMeta.setContentType(contentType);
				objMeta.setContentLength(mfFile.getSize());

				//Upload
				//주의 : defaultFolder앞에 / 가 붙으면 안됨
				PutObjectRequest putObjReq = new PutObjectRequest(bucketName, path, mfFile.getInputStream(), objMeta);
				tm.upload(putObjReq);
			} catch (IOException e) {
				log.warn("파일 업로드 실패");
				throw new RuntimeException();
			}
		}
	}

V2)

import software.amazon.awssdk.core.async.AsyncRequestBody;
import software.amazon.awssdk.services.s3.S3AsyncClient;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import software.amazon.awssdk.transfer.s3.S3TransferManager;
import software.amazon.awssdk.transfer.s3.model.UploadRequest;

	public void fileUpload(List<MultipartFile> fileList) {

		try (S3TransferManager tm = S3TransferManager.builder().s3Client(amazonS3Client).build()) {

			for (MultipartFile mfFile : fileList) {
				if (mfFile.isEmpty()) {
					continue;
				}

				final String oriFileNm = mfFile.getOriginalFilename();
				MimetypesFileTypeMap mimeTypesMap = new MimetypesFileTypeMap();
				String contentType = mimeTypesMap.getContentType(oriFileNm);

				PutObjectRequest putObjReq = PutObjectRequest.builder().bucket(bucketName).key(path).contentType(contentType).contentLength(mfFile.getSize()).build();
				UploadRequest uploadRequest = UploadRequest.builder().putObjectRequest(putObjReq).requestBody(AsyncRequestBody.fromInputStream(mfFile.getInputStream(), mfFile.getSize(), Executors.newSingleThreadExecutor())).build();
				tm.upload(uploadRequest).completionFuture().join();
			}
		} catch (IOException e) {
			log.warn("파일 업로드 실패");
			throw new RuntimeException(e);
		}
	}

 

Pre-signed URL

다음은 클라이언트가 특정 시간 동안만 접근할 수 있는 임시 URL 생성 방법입니다.

V1)

import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.GeneratePresignedUrlRequest;

	public URL getFileUrl(String path) {
		GeneratePresignedUrlRequest generatePresignedUrlRequest = new GeneratePresignedUrlRequest(bucketName, path)
			.withMethod(HttpMethod.GET)
			.withExpiration(expiredDate);
		return amazonS3Client.generatePresignedUrl(generatePresignedUrlRequest);
	}

V2)

import software.amazon.awssdk.services.s3.model.GetObjectRequest;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest;
import software.amazon.awssdk.services.s3.presigner.model.PresignedGetObjectRequest;

	public URL getFileUrl(String path) {
		GetObjectRequest objectRequest = GetObjectRequest.builder().bucket(bucketName).key(path).build();
		GetObjectPresignRequest presignRequest = GetObjectPresignRequest.builder()
			.getObjectRequest(objectRequest)
			.signatureDuration(duration)
			.build();
		PresignedGetObjectRequest presignedRequest = amazonS3Presigner.presignGetObject(presignRequest);
		return presignedRequest.url();
	}

 

Delete

V1)

import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.DeleteObjectRequest;

    public void deleteFile(String path) {
        amazonS3Client.deleteObject(new DeleteObjectRequest(BUCKET_NAME, path));
    }

V2)

import software.amazon.awssdk.services.s3.S3AsyncClient;
import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;

    public void deleteFile(String path) {
        amazonS3Client.deleteObject(DeleteObjectRequest.builder().bucket(BUCKET_NAME).key(path).build());
    }

 

 


 

 

이 가이드는 AWS SDK 버전 1.x에서 2.x로 마이그레이션하는 과정에서 발생할 수 있는 주요 변경 사항을 다루고 있습니다. 이 외에도 코드의 다른 부분에서 추가적인 변경이 필요할 수 있으므로, AWS 공식 문서를 참고하여 모든 변경 사항을 반영하시기 바랍니다.

728x90
반응형

댓글