Spring Boot で Object Storage を使う
今回は Spring Boot で Object Storage を使ってみようと思います.
Object Storage の説明はこちらを参照ください.
(簡単に言うと、S3 と同じイメージです)
Object Storage の Java 用 SDK が用意されているので、こちらを使います.
build.gradle
build.gradle は以下のようになります.
buildscript {
ext {
springBootVersion = '2.0.4.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
group = 'com.ibm.jp.gbs'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile('org.springframework.boot:spring-boot-starter-web:latest.release')
compile('org.projectlombok:lombok:latest.release')
compile('com.ibm.cos:ibm-cos-java-sdk:latest.release')
}
Repository
まずは Interface です.
画像を取得する getImage を宣言しました.
package com.ibm.jp.gbs.ibmwaydayapp.domain.repository;
import java.util.Optional;
public interface IObjectStorageRepository {
public Optional getImage(String bucketName, String imageName);
}
IObjectStorageRepository の実装です.
最終的には S3ObjectInputStream を byte[] に変換して返しています.
画像が取得できなかった場合は、Optional.empty が返されます.
package com.ibm.jp.gbs.ibmwaydayapp.infrastructure;
import com.ibm.cloud.objectstorage.AmazonServiceException;
import com.ibm.cloud.objectstorage.ClientConfiguration;
import com.ibm.cloud.objectstorage.SDKGlobalConfiguration;
import com.ibm.cloud.objectstorage.SdkClientException;
import com.ibm.cloud.objectstorage.auth.AWSCredentials;
import com.ibm.cloud.objectstorage.auth.AWSStaticCredentialsProvider;
import com.ibm.cloud.objectstorage.auth.BasicAWSCredentials;
import com.ibm.cloud.objectstorage.client.builder.AwsClientBuilder;
import com.ibm.cloud.objectstorage.oauth.BasicIBMOAuthCredentials;
import com.ibm.cloud.objectstorage.services.s3.AmazonS3;
import com.ibm.cloud.objectstorage.services.s3.AmazonS3ClientBuilder;
import com.ibm.cloud.objectstorage.services.s3.model.S3ObjectInputStream;
import com.ibm.jp.gbs.ibmwaydayapp.application.config.ConfigReader;
import com.ibm.jp.gbs.ibmwaydayapp.domain.repository.IObjectStorageRepository;
import org.springframework.stereotype.Repository;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Optional;
@Repository
public class ObjectStorageRepository implements IObjectStorageRepository {
private static AmazonS3 client;
public ObjectStorageRepository(){
SDKGlobalConfiguration.IAM_ENDPOINT = "https://iam.bluemix.net/oidc/token";
String apiKey = "XXXAu8zD_hfm66ycnwHxkAb0smjJBcGm7kUPCJmLxE5";
String resourceInstanceId = "XXX:v1:bluemix:public:cloud-object-storage:global:a/XXX::";
String endpointUrl = "https://s3.ap-geo.objectstorage.softlayer.net";
String location = "ap-geo";
client = createClient(apiKey, resourceInstanceId, endpointUrl, location);
}
private static AmazonS3 createClient(String apiKey,
String serviceInstanceId,
String endpointUrl,
String location)
{
AWSCredentials credentials;
if (endpointUrl.contains("objectstorage.softlayer.net")) {
credentials = new BasicIBMOAuthCredentials(apiKey, serviceInstanceId);
} else {
credentials = new BasicAWSCredentials(apiKey, serviceInstanceId);
}
ClientConfiguration clientConfig = new ClientConfiguration().withRequestTimeout(5000);
clientConfig.setUseTcpKeepAlive(true);
AmazonS3 client = AmazonS3ClientBuilder.standard()
.withCredentials(new AWSStaticCredentialsProvider(credentials))
.withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(endpointUrl, location))
.withPathStyleAccessEnabled(true)
.withClientConfiguration(clientConfig).build();
return client;
}
@Override
public Optional getImage(String bucketName, String imageName) {
S3ObjectInputStream content;
try {
content = client.getObject(bucketName, imageName).getObjectContent();
} catch (AmazonServiceException amazonServiceException){
System.out.printf("Error reading file %s:%s:%s\n",
bucketName,
imageName,
amazonServiceException.getMessage());
return Optional.empty();
}
try {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
byte [] buffer = new byte[1024];
while(true) {
int length = content.read(buffer);
if(length < 0) {
break;
}
stream.write(buffer, 0, length);
}
return Optional.of(stream.toByteArray());
} catch (IOException ioException){
System.out.printf("Error reading file %s:%s:%s\n", bucketName, imageName, ioException.getMessage());
return Optional.empty();
}
}
}
Service
Service になります.
Optinal 型の中身が Null だった場合に、no_image.png を設定する処理を記述しています.
package com.ibm.jp.gbs.ibmwaydayapp.domain.service;
import com.ibm.cloud.objectstorage.util.IOUtils;
import com.ibm.jp.gbs.ibmwaydayapp.domain.repository.IObjectStorageRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.io.InputStream;
import java.util.Optional;
@Service
public class ImageService {
@Autowired
private IObjectStorageRepository repository;
@Autowired
private ResourceLoader resourceLoader;
public byte[] getImage(String bucketName, String imageName) throws IOException {
Optional optional = repository.getImage(bucketName, imageName);
Resource resource = resourceLoader.getResource("classpath:" + "static/image/no_image.png");
InputStream stream = resource.getInputStream();
return optional.orElse(IOUtils.toByteArray(stream));
}
}
Controller
最後に Controller です.
画像を返すエンドポイント image/backet_name/image_name を実装しました.
package com.ibm.jp.gbs.ibmwaydayapp.application.controller;
import com.ibm.jp.gbs.ibmwaydayapp.domain.service.ImageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
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.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;
import java.io.IOException;
@Controller
public class ImageController {
@Autowired
private ImageService service;
@ResponseBody
@GetMapping("image/{bucketName}/{imageName}")
public ResponseEntity getImage(@PathVariable("bucketName") String bucketName,
@PathVariable("imageName") String imageName) throws IOException {
byte[] image = service.getImage(bucketName, imageName);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.IMAGE_JPEG);
headers.setContentLength(image.length);
return ResponseEntity.ok()
.headers(headers)
.body(image);
}
}
SDK があるので、とてもシンプルに書けますね.
Object Storage 自体は AWS S3 API と互換なので、使い勝手も良いかと思います.
以上です.