SpringBoot 集成阿里云OSS文件存储服务
作者:禅与计算机程序设计艺术
1.简介
在分布式系统中,云计算是构建大型应用的基石之一。云文件存储(Cloud Object Storage Service,OSS)是一个基于对象存储、面向文件存储的海量、安全、低成本、高可靠、弹性扩展的网络存储服务。它具备安全、高可用、低成本、全覆盖多地部署等特点。随着云计算技术的迅速普及,越来越多的人开始关注并使用云文件存储服务。
Spring Boot 是目前最流行的 Java 框架之一,它提供了简单易用、支持自动配置的特性,可以轻松实现微服务架构中的各种功能。为了让 Spring Boot 更好地与云文件存储服务集成,阿里巴巴开源了 spring-boot-starter-alicloud-oss ,通过该组件可以很方便地对接阿里云 OSS 服务,将 OSS 配置到 Spring Boot 项目中,使得 Spring Boot 项目能够快速地进行文件存储的管理。本文主要介绍如何集成 Spring Boot 项目和阿里云 OSS 文件存储服务。
2.基本概念
在开始介绍 Spring Boot 和阿里云 OSS 之前,需要先了解一些基本的概念。
2.1 Spring Boot
Spring Boot 是由 Pivotal 团队提供的全新框架,其旨在促进开发人员的快速、敏捷开发。Spring Boot 的设计目标之一就是“约定优于配置”,通过少量的配置项就能创建一个独立运行的应用。Spring Boot 提供了一系列默认值,减少了开发人员的配置负担,也降低了开发难度。
2.2 Alibaba Cloud
Alibaba Cloud 是阿里巴巴集团推出的一站式数字化商务平台,其中包括阿里云计算、云通信、云存储、云数据库等产品线。云服务是阿里巴巴一直以来坚持的品牌理念,也是阿里巴巴发展的基石。作为一个用户群体聚焦平台的云服务商,阿里云的全球化战略、强大的技术能力、丰富的产品和服务使得客户可以在任何时间、任何地点访问到阿里云的核心能力。同时阿里云还积极参与国际标准组织,推动云计算领域的技术创新与合作交流,为客户提供广阔的机遇和挑战。
阿里云提供的产品包括云计算、云通信、云存储、云数据库等多个服务,涵盖了 IaaS、PaaS、SaaS、服务器软件、智能终端设备、存储设备等多个领域,这些产品可以通过一站式服务的方式满足客户的业务需求。除此之外,阿里云还提供其他工具和服务,如云安全、云监控、云效率、云审计等。
除了云服务,阿里云还推出了“云栖”计划,帮助企业搭建基于阿里云云资源的私有云、混合云或公有云。通过云栖计划,企业可以在一个地方创建、管理和部署自己的云环境,实现数据中心内外业务的连续性。
2.3 OSS
Object Storage Service(OSS),即对象存储服务,是一种云上对象存储服务。OSS 是阿里云提供的海量、安全、低成本、高可靠、弹性扩展的网络存储服务。OSS 可用于存放任意类型的文件,包括图片、视频、音频、日志、归档文件、桌面应用程序、安装包等。OSS 提供了 RESTful API 以满足不同场景下的开发需求。OSS 在设计时充分考虑安全性、可用性、可伸缩性和性能,是云存储领域中的佼佼者。
3.核心算法原理和具体操作步骤以及数学公式讲解
本文所述的内容主要分为以下三个部分:
- 第一部分,介绍 Spring Boot 项目架构;
- 第二部分,介绍 Spring Boot Starter for Aliyun OSS 服务组件,并以阿里云官方文档的示例说明其使用方法;
- 第三部分,结合具体的代码实例,阐述 OSS 客户端编程模型的相关知识。
3.1 Spring Boot 项目架构
Spring Boot 使用了非常灵活的自动配置机制,根据用户引入的依赖,自动加载对应的配置类。所以一般情况下,不需要手动配置,只需引入相应的依赖即可。但是如果需要自定义一些配置信息,也可以通过配置文件或者环境变量来设置。如下图所示:
从图中可以看出,Spring Boot 项目中共分为四层:
- Framework —— Spring Framework 中的一些基础类库,如 IOC 和 AOP;
- Container —— Spring Boot 容器,用于加载配置和Bean;
- Application —— 用户编写的具体应用逻辑;
- Bootstrap —— Spring Boot 启动过程的控制,包括自动配置和其他初始化工作。
当项目启动时,会依次调用各个层级的初始化方法,最终完成 Spring Bean 的加载。
3.2 Spring Boot Starter for Aliyun OSS 服务组件
Spring Boot Starter for Aliyun OSS 服务组件(spring-boot-starter-alicloud-oss)是阿里巴巴针对 Spring Boot 项目的适配器,可以方便地将阿里云 OSS 服务集成到 Spring Boot 中。
安装方式
可以通过 Maven 或 Gradle 来安装 Spring Boot Starter for Aliyun OSS 服务组件,如下所示:
<dependency><groupId>com.alibaba.cloudgroupId><artifactId>spring-boot-starter-alicloud-ossartifactId>
dependency>
implementation("com.alibaba.cloud:spring-boot-starter-alicloud-oss")
基本配置
Spring Boot Starter for Aliyun OSS 服务组件提供了两种配置方式,可以通过 application.properties 文件进行配置,也可以通过 @ConfigurationProperties 注解注入到 Spring Bean 中。
通过 properties 文件配置
默认情况下,Spring Boot Starter for Aliyun OSS 服务组件会从 classpath 下面的 oss.config 配置文件读取配置信息。该配置文件包含两项必填属性,accessKey、endpoint。
spring:cloud:alicloud:access-key: your-access-keyendpoint: your-endpointoss:bucket-name: your-bucket-name
如果不想使用默认的配置文件名,也可以通过 spring.cloud.alicloud.oss.config-location 指定配置文件路径。
通过 @ConfigurationProperties 注解配置
Spring Boot Starter for Aliyun OSS 服务组件提供了 @EnableAliyunOssProperties 和 @AliyunOssBucketProperties 两个注解,可以通过该注解在启动类上进行配置。
import com.alibaba.cloud.spring.boot.context.config.EnableAliyunOss;
import com.alibaba.cloud.spring.boot.context.config.EnableAliyunOssProperties;
import com.alibaba.cloud.spring.boot.context.config.AliyunOssBucketProperties;
@SpringBootApplication
@EnableAliyunOss //开启 OSS 模块
public class MyApp {public static void main(String[] args) {ConfigurableApplicationContext context =SpringApplication.run(MyApp.class, args);EnableAliyunOssProperties enableAliyunOssProperties =context.getBean(EnableAliyunOssProperties.class);System.out.println(enableAliyunOssProperties.getAccessKey());//your-access-keyAliyunOssBucketProperties aliyunOssBucketProperties =context.getBean(AliyunOssBucketProperties.class);System.out.println(aliyunOssBucketProperties.getEndpoint());//your-endpoint}}
上述代码通过注解 @EnableAliyunOss 将 Spring Boot Starter for Aliyun OSS 服务组件组件激活,然后再通过 ConfigurableApplicationContext#getBean() 方法获取 @EnableAliyunOssProperties 和 @AliyunOssBucketProperties 对象,分别获取 accessKey、endpoint 属性的值。
创建客户端
创建 OSS 客户端的方式有两种:
- 使用 Spring Bean
- 非 Spring Bean
使用 Spring Bean
Spring Boot Starter for Aliyun OSS 服务组件提供了 OSSClient Bean,可以通过在启动类上添加 @EnableAutoConfiguration 和 @Import 注解来激活自动配置。并在配置类上增加注解 @EnableAliyunOss,指定要连接的 OSS 服务。
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.*;
import org.springframework.core.env.Environment;
import com.alibaba.cloud.spring.boot.context.config.EnableAliyunOss;
import com.aliyuncs.auth.DefaultCredentialProvider;
import com.aliyuncs.auth.ICredentialProvider;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.exceptions.ServerException;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
import com.aliyuncs.regions.ProductDomain;
import com.aliyuncs.regions.EndpointProductFilterBuilder;
import com.aliyuncs.regions.EndpointRegionalInfo;
import com.aliyuncs.regions.IEndpointRegionalHandler;
import com.aliyuncs.regional_endpoints.model.DescribeEndpointsResponse;
import com.aliyuncs.regional_endpoints.EndpointLocator;
import java.util.ArrayList;
import java.util.List;
@SpringBootApplication(exclude= DataSourceAutoConfiguration.class) //排除默认的数据源配置
@EnableAliyunOss(clients = "default-client", multiTenantMode = true)
public class MyApp {public static void main(String[] args) throws ClientException, ServerException{Environment environment = SpringApplication.run(MyApp.class, args).getEnvironment();String clientName = environment.getProperty("spring.cloud.alicloud.oss.clients");OSSClient ossClient = (OSSClient) environment.getBean("aliyun-oss-" + clientName);List<DescribeEndpointsResponse.Service> services = new ArrayList<>();ProductDomain productDomain = EndpointProductFilterBuilder.findProductDomainForRegionIdAndApiName(environment.getProperty("spring.cloud.alicloud.oss.endpoint"));DescribeEndpointsRequest request = new DescribeEndpointsRequest();request.setSysRegionId(productDomain.getRegionId());request.setType(productDomain.getProductName()!= null? productDomain.getProductName().toLowerCase() : "");DescribeEndpointsResponse response = ossClient.describeEndpoints(request);if (response.getServiceNames() == null || response.getServiceNames().isEmpty()) {throw new RuntimeException("No available endpoints found in the specified region.");}for (String serviceName : response.getServiceNames()) {DescribeEndpointsResponse.Service service = new DescribeEndpointsResponse.Service();service.setCode(serviceName);service.setRegions(new ArrayList<>());services.add(service);}List<EndpointRegionalInfo> list = EndpointRegionalInfo.from(services);ICredentialProvider provider = DefaultCredentialProvider.getProfileCredentialsProvider();IClientProfile profile = DefaultProfile.getProfile(productDomain.getRegionId(), provider);RegionEndpointResolver resolver = new RegionEndpointResolver(list, profile, EndpointLocator.DEFAULT_ENDPOINT_LOCATOR);IEndpointRegionalHandler handler = resolver.resolve();OSS oss = new OSSClientBuilder().build(handler);oss.putObject("your-bucket-name","your-object-name","input content".getBytes());}private static class RegionEndpointResolver implements IEndpointRegionalHandler {private final List<EndpointRegionalInfo> regionInfos;private final IClientProfile profile;private final EndpointLocator locator;public RegionEndpointResolver(List<EndpointRegionalInfo> regionInfos,IClientProfile profile,EndpointLocator locator) {this.regionInfos = regionInfos;this.profile = profile;this.locator = locator;}/*** Resolve an endpoint with specific region and client name. If no such endpoint found, it will try to use other regions one by one.*/@Overridepublic String handle(String regionId, String clientName) throws Exception {String ep = resolveFromLocal(regionId, clientName);if (!ep.equals("")) {return ep;}//Try all available regionsboolean success = false;for (int i = 0; i < regionInfos.size(); i++) {for (EndpointRegionalInfo info : regionInfos.get(i).getEndpoints()) {ep = info.getEndpoint();if (!ep.equals("")) {logger.info("Resolved endpoint '" + ep + "' from region Id '"+ info.getRegionId() + "'");return ep;} else {continue;}}}if (success) {//If get here, all regions are tried but still not find any valid endpointthrow new IllegalArgumentException("Cannot resolve a valid endpoint for the given parameters."+ " Please check your configuration.");}throw new IllegalStateException("Should never reach here!");}protected String resolveFromLocal(String regionId, String clientName) {String fileSeparator = System.getProperty("file.separator");String userHomeDir = System.getProperty("user.home") + fileSeparator + ".oss" + fileSeparator;String fileName = userHomeDir + regionId + "-" + clientName + "-endpoint";try {File f = new File(fileName);if (f.exists()) {BufferedReader reader = new BufferedReader(new FileReader(f));try {return reader.readLine();} finally {reader.close();}}} catch (IOException e) {logger.debug("Failed to read endpoint config file " + fileName, e);}return "";}}}
上述代码首先通过 @EnableAutoConfiguration 和 @Import 注解激活自动配置。并通过 @EnableAliyunOss 注解指定要连接的 OSS 服务为 default-client。然后在主函数中通过 ConfigurableApplicationContext#getBean() 方法获取 OSSClient Bean。
这里还通过扩展 IEndpointRegionalHandler 接口实现了从本地配置文件读取 endpoint 的能力。这样,就可以在每次启动的时候都优先尝试从本地配置文件中读取 endpoint,避免频繁访问阿里云服务器获取最新的 endpoint 列表。
非 Spring Bean
此外,Spring Boot Starter for Aliyun OSS 服务组件还提供了一些便利的方法来创建 OSS 客户端。例如,可以通过 OSSFactory 类的静态方法 createOSSClient() 创建 OSS 客户端。
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.auth.DefaultCredentialProvider;
import com.aliyuncs.auth.ICredentialProvider;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.exceptions.ServerException;
import com.aliyuncs.profile.IClientProfile;
import com.aliyuncs.utils.ParameterHelper;
import org.apache.commons.lang3.StringUtils;
public class Main {public static void main(String[] args) throws ClientException, ServerException {String endpoint = "https://oss-cn-beijing.aliyuncs.com";String accessKeyId = "yourAccessKeyId";String accessKeySecret = "yourAccessKeySecret";String bucketName = "bucketName";// create OSS client using factory methodIClientProfile profile = DefaultProfile.getProfile(ParameterHelper.getRegionIDByEndpoint(endpoint), DefaultCredentialProvider.getProfileCredentialsProvider(accessKeyId, accessKeySecret));OSS oss = OSSFactory.createOSSClient(profile, endpoint, bucketName);// or you can also build it manually like thisIAcsClient client = DefaultAcsClientBuilder.newBuilder().withProfile(profile).withEndpoint(endpoint).build();oss = new OSSClientBuilder().build(client);oss.putObject(bucketName,"objectName", "input content".getBytes());}
}
通过 OSSFactory#createOSSClient() 方法创建 OSS 客户端。该方法根据指定的凭证和区域创建 OSS 客户端。而通过 DefaultAcsClientBuilder 直接创建客户端则不需要传入凭证信息。不过,建议采用前一种方式创建客户端,因为后者更加简单灵活。
3.3 OSS 客户端编程模型
OSS 客户端的编程模型包括一下几个方面:
- Bucket 操作
- Object 操作
- Multipart Upload 操作
- Download/Get Object 操作
- Presigned URL 操作
Bucket 操作
获取某个 Bucket 的信息,或创建、删除 Bucket。
import com.aliyun.oss.model.Bucket;
// Get bucket information
OSSClient ossClient =... ; // create OSS client
String bucketName = "test-bucket";
Bucket bucket = ossClient.getBucket(bucketName);
if (bucket!= null) {System.out.println("Bucket Name:" + bucket.getName());System.out.println("Creation Date:" + bucket.getCreationDate());
}
// Create bucket
ossClient.createBucket(bucketName);
// Delete bucket
ossClient.deleteBucket(bucketName);
Object 操作
上传、下载、删除、查询 Object。
import com.aliyun.oss.model.PutObjectResult;
// Upload object
String localFile = "/data/local-file.txt";
String remoteFile = "remote-file.txt";
PutObjectResult result = ossClient.putObject(bucketName, remoteFile, new File(localFile));
System.out.println("ETag: " + result.getETag());
System.out.println("RequestId: " + result.getRequestId());
// Download object
OSSObject ossObject = ossClient.getObject(bucketName, remoteFile);
InputStream inputStream = ossObject.getObjectContent();
FileOutputStream outputStream = new FileOutputStream("/data/local-download-file.txt");
byte[] buffer = new byte[1024];
int len = -1;
while ((len = inputStream.read(buffer))!= -1) {outputStream.write(buffer, 0, len);
}
inputStream.close();
outputStream.flush();
outputStream.close();
// Delete object
ossClient.deleteObject(bucketName, remoteFile);
// List objects
ObjectListing listing = ossClient.listObjects(bucketName);
for (OSSObjectSummary objectSummary : listing.getObjectSummaries()) {System.out.println("ObjectName: " + objectSummary.getKey());System.out.println("LastModified: " + objectSummary.getLastModified());System.out.println("Size: " + objectSummary.getSize());System.out.println("StorageClass: " + objectSummary.getStorageClass());
}
Multipart Upload 操作
分片上传文件到 OSS。
import com.aliyun.oss.model.InitiateMultipartUploadRequest;
import com.aliyun.oss.model.InitiateMultipartUploadResult;
import com.aliyun.oss.model.UploadPartRequest;
import com.aliyun.oss.model.UploadPartResult;
import com.aliyun.oss.model.CompleteMultipartUploadRequest;
import com.aliyun.oss.model.PartETag;
// Init multipart upload
InitiateMultipartUploadRequest initiateMultipartUploadRequest =new InitiateMultipartUploadRequest(bucketName, key);
InitiateMultipartUploadResult initResult = ossClient.initiateMultipartUpload(initiateMultipartUploadRequest);
uploadId = initResult.getUploadId();
// Upload parts
List<PartETag> partETags = new ArrayList<>();
long partSize = 1024 * 1024; // 1MB
long fileLength = new File(uploadFile).length();
int partCount = (int) (fileLength / partSize);
if (fileLength % partSize!= 0) {partCount++;
}
for (int i = 0; i < partCount; i++) {long startPos = i * partSize;long curPartSize = Math.min(partSize, fileLength - startPos);// Upload partString uploadFileName = prefix + "/" + pathPrefix + getRandomUUID() + "-" + i + "." + extName;UploadPartRequest uploadPartRequest = new UploadPartRequest();uploadPartRequest.setBucketName(bucketName);uploadPartRequest.setKey(key);uploadPartRequest.setUploadId(uploadId);uploadPartRequest.setInputStream(new FileInputStream(uploadFile));uploadPartRequest.setPartNumber(i + 1);uploadPartRequest.setPartSize(curPartSize);uploadPartRequest.setContentMd5("");UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);PartETag partETag = new PartETag(i + 1, uploadPartResult.getETag());partETags.add(partETag);
}
// Complete multipart upload
CompleteMultipartUploadRequest completeMultipartUploadRequest = new CompleteMultipartUploadRequest(bucketName, key, uploadId, partETags);
ossClient.completeMultipartUpload(completeMultipartUploadRequest);
Download/Get Object 操作
下载/获取已上传至 OSS 的 Object。
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.common.utils.IOUtils;
// Download object to file system
OSSClient ossClient =... // create OSS client
String objectName = "my-object-name";
ossClient.getObjectToFile(bucketName, objectName, new File("/tmp/" + objectName));
// Read object content as string
String str = IOUtils.toString(ossClient.getObject(bucketName, objectName).getObjectContent());
// Download object to stream
GetObjectRequest getObjectRequest = new GetObjectRequest(bucketName, objectName);
OSSObject ossObject = ossClient.getObject(getObjectRequest);
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(ossObject.getObjectContent()));
StringBuffer sb = new StringBuffer();
String line;
while ((line = bufferedReader.readLine())!= null) {sb.append(line);
}
System.out.println(sb.toString());
ossClient.shutdown();
Presigned URL 操作
为某些操作生成临时的签名 URL。
import java.net.URL;
import java.util.Date;
import com.aliyun.oss.ServiceException;
import com.aliyun.oss.model.GetObjectRequest;
import com.aliyun.oss.model.GeneratePresignedUrlRequest;
import com.aliyun.oss.model.HeadObjectRequest;
// Generate presigned url for download object
URL signedUrl = ossClient.generatePresignedUrl(bucketName, objectName, ExpirationPeriod.valueOfDays(1), HttpMethod.GET);
System.out.println(signedUrl.toString());
// Generate presigned url for head object
URL signedUrlOfHead = ossClient.generatePresignedUrl(headObjectRequest, expiration);
System.out.println(signedUrlOfHead.toString());
// Generate presigned url for put object
URL signedUrlForUpload = ossClient.generatePresignedUrl(generatePresignedUrlRequest, expiration);
System.out.println(signedUrlForUpload.toString());
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
