本文旨在讲解如何构建一个企业级的OSS(对象存储服务)Spring Boot Starter,实现开箱即用,为项目快速赋能。该Starter基于Amazon S3协议设计,能够无缝适配市面上主流的对象存储服务,例如阿里云OSS、腾讯云COS、七牛云OSS以及Minio等。
文章核心步骤概览
- 什么是OSS?
- OSS在项目中的使用场景
- 什么是Amazon S3
- 寻找并引入核心依赖
- 编写配置属性类
OssProperties
- 定义操作接口
OssTemplate
- 实现
OssTemplate接口
- 创建自动配置类
OssAutoConfiguration
- 配置
spring.factories文件
- 执行
install打包至本地仓库
- 创建测试工程进行验证
什么是OSS?
OSS(Object Storage Service),即对象存储服务。它是一种通过HTTP API来存储和检索非结构化数据(对象)的云服务。简单来说,就是将系统所需的各类文件(如图片、文档、音视频)上传到云端存储,该服务提供上传、下载、预览、权限控制、版本管理以及数据生命周期管理等一站式功能。
OSS在项目中的使用
对象存储在当今的绝大多数项目中已成为不可或缺的基础组件,其主要应用场景如下图所示。

- 资源集中管理:项目通常使用OSS来集中管理图片、文件、音频等静态资源,提供统一的上传、下载、预览和删除接口,并实施精细的权限控制与生命周期策略。
- 前端项目部署:利用OSS的静态网站托管功能,可以直接部署前端项目。
什么是Amazon S3
Amazon Simple Storage Service(Amazon S3)是AWS最早推出的云服务之一。经过多年发展,其API协议在对象存储领域已成为事实上的行业标准。
其主要特性包括:
- 提供统一的REST/SOAP接口访问数据。
- 采用简单的键值对(对象名-数据)存储模型。
- 存储容量无限,单个对象大小最高可达5TB,支持动态扩容。
- 性能优异,每个存储桶(Bucket)每秒可承受高达3500次写请求或5500次读请求。
- 具备完善的版本控制与权限管理能力。
- 支持灵活的数据生命周期管理策略。
由于S3功能的完备性和普适性,目前市面上主流的OSS服务都提供了对Amazon S3协议的兼容支持。本文的核心,正是讲解如何基于Amazon S3的Java SDK,封装我们自己的、可适配多平台的Spring Boot Starter。
主流云服务对S3的兼容性
- 阿里云OSS兼容S3

- 七牛云对象存储兼容S3

- 腾讯云COS兼容S3

- Minio兼容S3

为什么选择基于Amazon S3实现Starter?
核心原因在于适配性、可迁移性和可扩展性。如果直接对接各云厂商的专属SDK,当需要从阿里云迁移到腾讯云,或同时支持多种存储后端时,业务代码将面临大量修改。而基于统一的S3协议进行封装,后续切换存储提供商时,通常只需修改配置信息(如Endpoint、AccessKey),无需调整业务逻辑代码,极大地提升了组件的通用性和可维护性。
第一步:创建Spring Boot项目
首先,我们需要创建一个基础的Spring Boot项目,项目名定为 oss-spring-boot-starter。

项目创建成功后,结构如下图所示,接下来我们将开始具体的开发工作。

第二步:引入核心依赖
打开Maven中央仓库,搜索 aws-java-sdk-s3。
仓库地址:https://mvnrepository.com/

选择第一个结果进入,这里我们以 1.12.423 版本为例进行演示。

对应的Maven依赖如下:
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-s3</artifactId>
<version>1.12.423</version>
</dependency>
本项目完整的POM文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.9</version>
<relativePath/><!-- lookup parent from repository -->
</parent>
<groupId>com.qing</groupId>
<artifactId>oss-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>oss-spring-boot-starter</name>
<description>Demo oss-spring-boot-starter</description>
<properties>
<java.version>1.8</java.version>
<aws.version>1.12.423</aws.version>
<hutool.version>5.8.5</hutool.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-s3</artifactId>
<version>${aws.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
第三步:编写配置属性类 - OssProperties
这个类用于映射配置文件中的OSS相关属性。@ConfigurationProperties 注解报红可以先忽略,后续步骤会解决。@Data 是Lombok注解,用于自动生成getter和setter方法。
@ConfigurationProperties(prefix = "oss"):将应用配置文件中以 oss 为前缀的属性绑定到该类的字段上。
这意味着使用我们最终Starter的项目,只需要在 application.yml 或 application.properties 中配置如下属性即可:
oss.endpoint=xxx
oss.accessKey=xxx
oss.secretKey=xxx
/**
* @Author JiaQIng
* @Description Oss配置类
* @ClassName OssProperties
* @Date 2023/3/18 17:51
**/
@Data
@ConfigurationProperties(prefix = "oss")
public class OssProperties {
/**
* 对象存储服务的URL
*/
private String endpoint;
/**
* 区域
*/
private String region;
/**
* true path-style nginx 反向代理和S3默认支持 pathStyle模式 {http://endpoint/bucketname}
* false supports virtual-hosted-style 阿里云等需要配置为 virtual-hosted-style 模式{http://bucketname.endpoint}
* 只是url的显示不一样
*/
private Boolean pathStyleAccess = true;
/**
* Access key
*/
private String accessKey;
/**
* Secret key
*/
private String secretKey;
/**
* 最大连接数,默认:100
*/
private Integer maxConnections = 100;
}
第四步:定义操作接口 - OssTemplate
OssTemplate 是我们定义的OSS操作模板接口。将其定义为接口是为了遵循“面向接口编程”的原则,提高扩展性。其他开发者使用我们的Starter时,可以通过实现此接口来自定义或增强某些操作。
以下代码定义了一系列基本的OSS操作方法:
/**
* @Author JiaQIng
* @Description oss操作模板
* @ClassName OssTemplate
* @Date 2023/3/18 18:15
**/
public interface OssTemplate {
/**
* 创建bucket
* @param bucketName bucket名称
*/
void createBucket(String bucketName);
/**
* 获取所有的bucket
* @return
*/
List<Bucket> getAllBuckets();
/**
* 通过bucket名称删除bucket
* @param bucketName
*/
void removeBucket(String bucketName);
/**
* 上传文件
* @param bucketName bucket名称
* @param objectName 文件名称
* @param stream 文件流
* @param contextType 文件类型
* @throws Exception
*/
void putObject(String bucketName, String objectName, InputStream stream, String contextType) throws Exception;
/**
* 上传文件
* @param bucketName bucket名称
* @param objectName 文件名称
* @param stream 文件流
* @throws Exception
*/
void putObject(String bucketName, String objectName, InputStream stream) throws Exception;
/**
* 获取文件
* @param bucketName bucket名称
* @param objectName 文件名称
* @return S3Object
*/
S3Object getObject(String bucketName, String objectName);
/**
* 获取对象的url
* @param bucketName
* @param objectName
* @param expires
* @return
*/
String getObjectURL(String bucketName, String objectName, Integer expires);
/**
* 通过bucketName和objectName删除对象
* @param bucketName
* @param objectName
* @throws Exception
*/
void removeObject(String bucketName, String objectName) throws Exception;
/**
* 根据文件前置查询文件
* @param bucketName bucket名称
* @param prefix 前缀
* @param recursive 是否递归查询
* @return S3ObjectSummary 列表
*/
List<S3ObjectSummary> getAllObjectsByPrefix(String bucketName, String prefix, boolean recursive);
}
第五步:实现 OssTemplate 接口
接下来,我们需要实现 OssTemplate 接口,内部通过调用 AmazonS3 SDK 提供的方法来完成具体操作。Amazon S3 提供了非常丰富的API,这里仅实现部分核心方法,实际项目中可按需扩展。
AmazonS3 官方API文档地址:https://docs.aws.amazon.com/AmazonS3/latest/API/API_CreateBucket.html
注解说明:
@RequiredArgsConstructor:Lombok注解,为 final 字段生成构造函数,替代 @Autowired 进行注入。
@SneakyThrows:Lombok注解,自动抛出受检异常,避免代码中显式编写 throws。
/**
* @Author JiaQIng
* @Description OssTemplate的实现类
* @ClassName OssTemplateImpl
* @Date 2023/3/18 19:02
**/
@RequiredArgsConstructor
public class OssTemplateImpl implements OssTemplate {
private final AmazonS3 amazonS3;
/**
* 创建Bucket
* @param bucketName bucket名称
*/
@Override
@SneakyThrows
public void createBucket(String bucketName) {
if ( !amazonS3.doesBucketExistV2(bucketName) ) {
amazonS3.createBucket((bucketName));
}
}
/**
* 获取所有的buckets
* @return
*/
@Override
@SneakyThrows
public List<Bucket> getAllBuckets() {
return amazonS3.listBuckets();
}
/**
* 通过Bucket名称删除Bucket
* @param bucketName
*/
@Override
@SneakyThrows
public void removeBucket(String bucketName) {
amazonS3.deleteBucket(bucketName);
}
/**
* 上传对象
* @param bucketName bucket名称
* @param objectName 文件名称
* @param stream 文件流
* @param contextType 文件类型
*/
@Override
@SneakyThrows
public void putObject(String bucketName, String objectName, InputStream stream, String contextType) {
putObject(bucketName, objectName, stream, stream.available(), contextType);
}
/**
* 上传对象
* @param bucketName bucket名称
* @param objectName 文件名称
* @param stream 文件流
*/
@Override
@SneakyThrows
public void putObject(String bucketName, String objectName, InputStream stream) {
putObject(bucketName, objectName, stream, stream.available(), "application/octet-stream");
}
/**
* 通过bucketName和objectName获取对象
* @param bucketName bucket名称
* @param objectName 文件名称
* @return
*/
@Override
@SneakyThrows
public S3Object getObject(String bucketName, String objectName) {
return amazonS3.getObject(bucketName, objectName);
}
/**
* 获取对象的url
* @param bucketName
* @param objectName
* @param expires
* @return
*/
@Override
@SneakyThrows
public String getObjectURL(String bucketName, String objectName, Integer expires) {
Date date = new Date();
Calendar calendar = new GregorianCalendar();
calendar.setTime(date);
calendar.add(Calendar.DAY_OF_MONTH, expires);
URL url = amazonS3.generatePresignedUrl(bucketName, objectName, calendar.getTime());
return url.toString();
}
/**
* 通过bucketName和objectName删除对象
* @param bucketName
* @param objectName
*/
@Override
@SneakyThrows
public void removeObject(String bucketName, String objectName) {
amazonS3.deleteObject(bucketName, objectName);
}
/**
* 根据bucketName和prefix获取对象集合
* @param bucketName bucket名称
* @param prefix 前缀
* @param recursive 是否递归查询
* @return
*/
@Override
@SneakyThrows
public List<S3ObjectSummary> getAllObjectsByPrefix(String bucketName, String prefix, boolean recursive) {
ObjectListing objectListing = amazonS3.listObjects(bucketName, prefix);
return objectListing.getObjectSummaries();
}
/**
*
* @param bucketName
* @param objectName
* @param stream
* @param size
* @param contextType
* @return
*/
@SneakyThrows
private PutObjectResult putObject(String bucketName, String objectName, InputStream stream, long size,
String contextType) {
byte[] bytes = IOUtils.toByteArray(stream);
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentLength(size);
objectMetadata.setContentType(contextType);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
// 上传
return amazonS3.putObject(bucketName, objectName, byteArrayInputStream, objectMetadata);
}
}
第六步:创建自动配置类 - OssAutoConfiguration
OssAutoConfiguration 是Spring Boot自动装配的核心,它负责创建 AmazonS3 客户端Bean和 OssTemplate Bean。

关键注解解析:
@RequiredArgsConstructor:同上。
@EnableConfigurationProperties(OssProperties.class):启用对 OssProperties 配置类的支持。
@Bean:声明一个由Spring容器管理的Bean。
@ConditionalOnMissingBean:当Spring容器中不存在指定类型的Bean时,才创建当前Bean。这保证了用户可以在自己的项目中自定义同类型Bean来覆盖默认实现。
@ConditionalOnBean(AmazonS3.class):当容器中存在 AmazonS3 类型的Bean时,才创建当前Bean。确保 OssTemplate 依赖于 AmazonS3 客户端。
/**
* @Author JiaQIng
* @Description oss配置bean
* @ClassName OssAConfiguration
* @Date 2023/3/18 18:23
**/
@Configuration
@RequiredArgsConstructor
@EnableConfigurationProperties(OssProperties.class)
public class OssAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public AmazonS3 ossClient(OssProperties ossProperties) {
// 客户端配置,主要是全局的配置信息
ClientConfiguration clientConfiguration = new ClientConfiguration();
clientConfiguration.setMaxConnections(ossProperties.getMaxConnections());
// url以及region配置
AwsClientBuilder.EndpointConfiguration endpointConfiguration = new AwsClientBuilder.EndpointConfiguration(
ossProperties.getEndpoint(), ossProperties.getRegion());
// 凭证配置
AWSCredentials awsCredentials = new BasicAWSCredentials(ossProperties.getAccessKey(),
ossProperties.getSecretKey());
AWSCredentialsProvider awsCredentialsProvider = new AWSStaticCredentialsProvider(awsCredentials);
// build amazonS3Client客户端
return AmazonS3Client.builder().withEndpointConfiguration(endpointConfiguration)
.withClientConfiguration(clientConfiguration).withCredentials(awsCredentialsProvider)
.disableChunkedEncoding().withPathStyleAccessEnabled(ossProperties.getPathStyleAccess()).build();
}
@Bean
@ConditionalOnBean(AmazonS3.class)
public OssTemplate ossTemplate(AmazonS3 amazonS3){
return new OssTemplateImpl(amazonS3);
}
}
关于 ClientConfiguration 对象
ClientConfiguration 是Amazon S3客户端的全局配置对象,包含了连接超时、最大连接数、重试策略等诸多配置项。下图展示了其部分默认配置。

理解这些配置的作用对于避免生产环境下的潜在问题至关重要,建议有需要的同学深入阅读官方文档进行配置调优。
第七步:创建 spring.factories 文件
在 resources 目录下创建 META-INF 文件夹,并在其中新建 spring.factories 文件。这是Spring Boot“约定大于配置”理念的体现,用于声明自动配置类,使其能够被Spring Boot应用在启动时自动加载。
文件内容如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.qing.oss.OssAutoConfiguration
第八步:打包并安装到本地Maven仓库
在打包之前,我们需要对项目做一些清理和调整:
- 删除Spring Boot工程的启动类、配置文件。
- 删除测试包(
src/test)。
- 最关键的一步:从
pom.xml 中移除 spring-boot-maven-plugin 插件,否则执行 install 时会报错。

需要移除的插件配置部分:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
清理调整后,项目的最终结构应如下图所示:

现在,可以执行 mvn clean install 命令,将我们的 oss-spring-boot-starter 打包并安装到本地Maven仓库。

安装成功后,可以在本地仓库的对应目录下找到生成的JAR文件。

第九步:创建测试工程进行验证
1. 创建测试Spring Boot工程
新建一个普通的Spring Boot工程作为测试项目。

2. 引入我们自制的Starter依赖
在测试项目的 pom.xml 中,首先定义版本属性:
<properties>
<oss.version>0.0.1-SNAPSHOT</oss.version>
</properties>
然后添加依赖:
<dependency>
<groupId>com.qing</groupId>
<artifactId>oss-spring-boot-starter</artifactId>
<version>${oss.version}</version>
</dependency>
刷新Maven后,可以在依赖列表中看到我们引入的starter。

3. 解决源码(Sources)包问题
你可能会发现引入的依赖没有附加源码(Sources),这会影响IDE中的代码提示和跳转。为了解决这个问题,我们需要在 starter项目的pom文件 中加入 maven-source-plugin 插件,然后重新执行 install。
<build>
<plugins>
<!-- 在打好的jar包中保留javadoc注释,实际会另外生成一个xxxxx-sources.jar -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
重新安装后,在测试项目中刷新依赖,就可以看到附带源码的JAR了。

4. 添加配置文件
在测试项目的 application.properties 或 application.yml 中,配置OSS连接信息。以下以Minio为例:
oss.endpoint=http://your-minio-server:9000
oss.accessKey=your-access-key
oss.secretKey=your-secret-key
# region非必填,根据实际情况配置
# oss.region=us-east-1
5. 编写并执行测试
创建一个简单的测试类,注入 OssTemplate 并调用其方法。
@SpringBootTest
class TestOssSpringBpptStarterApplicationTests {
@Autowired
private OssTemplate ossTemplate;
@Test
void contextLoads() {
ossTemplate.createBucket("oss02");
}
}
执行测试方法,如果控制台输出成功且无报错,则表明starter工作正常。

最后,可以登录到Minio的管理控制台进行确认,发现名为 oss02 的存储桶已被成功创建。

总结与后记
本文详细演示了如何从零开始构建一个基于Amazon S3协议的企业级OSS对象存储 Spring Boot Starter。该组件实现了开箱即用,并能适配阿里云OSS、腾讯云COS、七牛云OSS、Minio等多种兼容S3协议的服务,有效提升了项目在集成对象存储功能时的开发效率和可维护性。
通过这个实战案例,我们不仅完成了一个实用的工具,也深入实践了Spring Boot自动配置、Conditional条件化Bean装配等核心机制。希望这个案例能为你设计和封装自己的 开源实战 组件提供清晰的思路和参考。
如果你想深入探讨 Java 后端开发、微服务架构或更多技术实践,欢迎在 云栈社区 与其他开发者交流分享你的经验和见解。