0.简介
本教程是基于实战开发的,自定义一个存储相关的starter,主要是集成cos、oss等。教程里面不但详细讲解了starter开发的步骤和细节,同时对springBoot的一些注解做了详细的解释
废话不多说,马上开撸~
1.新建一个项目
新建一个springboot多模块的项目,目录结构如图:
![](https://i-blog.csdnimg.cn/blog_migrate/d2edc76289f14bf98c265b422b26716b.png)
1.yfway-object-storage-core:实现业务逻辑的核心代码
2.yfway-object-storage-starter: 提供必要的依赖项来使用starter , 指定Starter的自动装配类
注:如果不知道怎么建多模块,可自行百度,或留言
2.准备工作
2.1统一返回值类
@Data
public class StorageResult implements Serializable {
private Boolean isSuccess;
private String message;
private Object data;
}
简单定义,仅供参考
2.2pom文件依赖
yfway-object-storage-core模块的pom文件
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--COS相关-->
<dependency>
<groupId>com.qcloud</groupId>
<artifactId>cos_api</artifactId>
<version>5.6.89</version>
</dependency>
<!--配置相关-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
yfway-object-storage-starter模块的pom文件
<dependencies>
<dependency>
<groupId>com.storage</groupId>
<artifactId>yfway-object-storage-core</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
</dependencies>
引入yfway-object-storage-core这个模块
3.新建配置类
相信大家在使用springboot都接触过以下配置:
spring:
datasource:
#MySQL配置
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/demo?useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: root
password: root
这个是数据库连接信息的配置,这个信息基本都是固定不变的,类似的,当我们使用cos(腾讯存储对象)时,也会要求填写配置信息,例如bucket,secretId,secretKey等,例如下面是我们实现的一个例子:
yf-storage:
enable: true
cos:
bucket-name: 你的bucket
secret-id: 你的secretId
secret-key: 你的secretKey
region: ap-guangzhou
接下来就是看看我们自定义的starter如何读取到这个配置
在yfway-object-storage-core模块中新建一个config目录,新建StorageProperties.java,内容如下:
package com.yfway.storage.core.config;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Getter
@Setter
@ConfigurationProperties(prefix = StorageProperties.STOREAGE_PREFIX)
public class StorageProperties {
static final String STOREAGE_PREFIX = "yf-storage";
private Boolean enable = true;
private Cos cos;
@Data
public static class Cos{
private String region;
private String secretId;
private String secretKey;
private String bucketName;
}
}
这段代码有个非常重要的注解
@ConfigurationProperties 是一个用于绑定配置属性的注解,它可以将外部的配置文件或配置中心中的属性值,绑定到一个 Java 类的属性上。 我们就这可以这样获取cos的配置
StorageProperties.getCos()
4.开发上传接口
这里简单来说就是开发一个接口,大家根据实际情况去写自己的代码,其功能就是实现文件上传。下面的代码仅供参考
4.1新建一个YfCosService接口类
内容如下:
public interface YfCosService {
/**
* 上传文件, 根据文件大小自动选择简单上传或者分块上传。
* @param objectName 填写Object完整路径,例如exampledir/exampleobject.txt。Object完整路径中不能包含Bucket名称。
* @param file 文件对象
* @param yfCosConfig 额外配置
* @param objectMetadata 若需要设置对象的自定义 Headers 可参照下列代码,若不需要可省略下面这几行,对象自定义 Headers 的详细信息可参考 https://cloud.tencent.com/document/product/436/13361
* @return com.yfway.storage.core.common.StorageResult
*/
StorageResult uploadFile(String objectName, File file, YfCosConfig yfCosConfig, ObjectMetadata objectMetadata);
}
这里只列出一个上传的接口来讲解,其它接口可自行扩展
4.2新建一个AbstractYfCosService抽象类
内容如下:
public abstract class AbstractYfCosService implements YfCosService{
private final boolean enabled;
public AbstractYfCosService(boolean enabled) {
this.enabled = enabled;
}
@Override
public StorageResult uploadFile(String objectName, File file, YfCosConfig yfCosConfig, ObjectMetadata objectMetadata) {
StorageResult baseResult = new StorageResult();
baseResult.setIsSuccess(true);
if (this.enabled){
baseResult = this.doUploadFile(objectName,file,yfCosConfig,objectMetadata);
}else{
baseResult.setIsSuccess(false);
baseResult.setMessage("service is disabled");
}
return baseResult;
}
protected abstract StorageResult doUploadFile(String objectName, File file, YfCosConfig yfCosConfig,ObjectMetadata objectMetadata);
}
该抽象类继承了YfCosService,主要是实现了uploadFile
这里我们定义了一个抽象方法doUploadFile,整个上传的核心代码要在doUploadFile这个方法里面实现
4.3新建一个YfCosStorage类,继承AbstractYfCosService
内容如下:
@Slf4j
public class YfCosStorage extends AbstractYfCosService {
private final String region;
private final String secretId;
private final String secretKey;
private final String bucketName;
public YfCosStorage(boolean enabled, String region, String secretId, String secretKey, String bucketName) {
super(enabled);
this.region = region;
this.secretId = secretId;
this.secretKey = secretKey;
this.bucketName = bucketName;
}
private COSClient createCosClient(){
try {
COSCredentials cred = new BasicCOSCredentials(this.secretId, this.secretKey);
ClientConfig clientConfig = new ClientConfig(new Region(this.region));
//clientConfig.setHttpProtocol(HttpProtocol.https);
return new COSClient(cred, clientConfig);
}catch (Exception e){
log.info("Create COS client is error:" + e.getMessage());
return null;
}
}
/**
* 创建 TransferManager 实例,这个实例用来后续调用高级接口
*/
private TransferManager createTransferManager(YfCosConfig yfCosConfig) {
// 创建一个 COSClient 实例,这是访问 COS 服务的基础实例。
// 详细代码参见本页: 简单操作 -> 创建 COSClient
COSClient cosClient = createCosClient();
YfCosConfig.TransferManager config = yfCosConfig.getTransferManager();
// 自定义线程池大小,建议在客户端与 COS 网络充足(例如使用腾讯云的 CVM,同地域上传 COS)的情况下,设置成16或32即可,可较充分的利用网络资源
// 对于使用公网传输且网络带宽质量不高的情况,建议减小该值,避免因网速过慢,造成请求超时。
ExecutorService threadPool = Executors.newFixedThreadPool(config.getThreadPoolSize());
// 传入一个 threadpool, 若不传入线程池,默认 TransferManager 中会生成一个单线程的线程池。
TransferManager transferManager = new TransferManager(cosClient, threadPool);
// 设置高级接口的配置项
// 分块上传阈值和分块大小
TransferManagerConfiguration transferManagerConfiguration = new TransferManagerConfiguration();
transferManagerConfiguration.setMultipartUploadThreshold(config.getMultipartUploadThreshold());
transferManagerConfiguration.setMinimumUploadPartSize(config.getMinimumUploadPartSize());
transferManager.setConfiguration(transferManagerConfiguration);
return transferManager;
}
@Override
protected StorageResult doUploadFile(String objectName, File file, YfCosConfig yfCosConfig, ObjectMetadata objectMetadata) {
StorageResult storageResult = new StorageResult();
storageResult.setIsSuccess(true);
if (Objects.isNull(yfCosConfig)){
//读取默认配置
yfCosConfig = YfCosConfig.defaultConfig();
}
TransferManager transferManager = createTransferManager(yfCosConfig);
PutObjectRequest putObjectRequest = new PutObjectRequest(this.bucketName, objectName, file);
// 设置存储类型(如有需要,不需要请忽略此行代码), 默认是标准(Standard), 低频(standard_ia)
// 更多存储类型请参见 https://cloud.tencent.com/document/product/436/33417
if (!Objects.isNull(yfCosConfig.getStorageClass())){
putObjectRequest.setStorageClass(yfCosConfig.getStorageClass());
}
//若需要设置对象的自定义 Headers 可参照下列代码,若不需要可省略下面这几行,对象自定义 Headers 的详细信息可参考 https://cloud.tencent.com/document/product/436/13361
if (!Objects.isNull(objectMetadata)){
putObjectRequest.withMetadata(objectMetadata);
}
try {
// 高级接口会返回一个异步结果Upload
// 可同步地调用 waitForUploadResult 方法等待上传完成,成功返回 UploadResult, 失败抛出异常
Upload upload = transferManager.upload(putObjectRequest);
UploadResult uploadResult = upload.waitForUploadResult();
Map<String,Object> result = new HashMap<>();
result.put("requestId",uploadResult.getRequestId());
result.put("etag",uploadResult.getETag());
result.put("crc64Ecma",uploadResult.getCrc64Ecma());
result.put("key",uploadResult.getKey());
storageResult.setData(result);
} catch (CosClientException | InterruptedException e) {
e.printStackTrace();
storageResult.setIsSuccess(false);
storageResult.setMessage(e.getMessage());
}finally {
// 确定本进程不再使用 transferManager 实例之后,关闭之
transferManager.shutdownNow(true);
}
return storageResult;
}
}
核心代码:doUploadFile方法里面实现了上传逻辑,这里的上传代码可以直接抄cos官方文档给出来的代码
5.新建自动装配类
上面我们虽然实现了接口,如何把它变成bean注入到应用程序的bean容器中,就是我们接下来的工作了
新建一个cos的配置类,内容如下:
@Configuration
@ConditionalOnProperty(value = "yf-storage.enable",havingValue = "true",matchIfMissing = true)
@EnableConfigurationProperties(StorageProperties.class)
public class YfCosStorageConfiguration {
@Bean
@ConditionalOnMissingBean
public YfCosService yfCosStorage(StorageProperties properties){
StorageProperties.Cos cos = properties.getCos();
return new YfCosStorage(properties.getEnable(),cos.getRegion(),cos.getSecretId(),cos.getSecretKey(),cos.getBucketName());
}
}
下面来详细解释一下例子中用到的注解
@Configuration 是一个用于定义配置类的注解,它通常与其他注解一起使用以声明应用程序中的 bean。
上面的例子将YfCosStorageConfiguration类声明为一个配置类,其中的方法yfCosStorage()使用了@Bean注解,表示会创建一个bean,并添加到应用程序的bean容器中
@ConditionalOnProperty 是一个条件注解,它可以根据指定的属性值判断是否要创建一个 bean。
如果应用程序的配置文件中存在 yf-storage.enable=true的属性,那么YfCosServicebean 将被创建。如果不存在该属性,且matchIfMissing属性为true,则 YfCosServicebean 也会被创建。如果matchIfMissing属性为false,则 YfCosService bean 不会被创建。
@EnableConfigurationProperties 是一个注解,它可以将使用 @ConfigurationProperties 注解的类注入为 Spring 的 bean,以便在应用程序中使用。例如上面的例子,如果没有使用该注解,你就不能这样获取信息
public YfCosService yfCosStorage(StorageProperties properties){
properties.getCos();
}
@ConditionalOnMissingBean 是一个条件注解,它可以根据指定的 bean 是否存在来判断是否创建一个新的 bean
接下来我们新建自动装配类YfStorageAutoConfiguration,内容如下:
@Configuration
@EnableConfigurationProperties(StorageProperties.class)
@Import({YfCosStorageConfiguration.class})
public class YfStorageAutoConfiguration {
}
它的作用就算导入上面的cos配置类YfCosStorageConfiguration,之所以这样写主要是为了扩展,如果新增oss的配置类,我们可以这样写
@Configuration
@EnableConfigurationProperties(StorageProperties.class)
@Import({YfCosStorageConfiguration.class,YfOssStorageConfiguration.class})
public class YfStorageAutoConfiguration {
}
6.定义一个注解
内容如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(YfStorageAutoConfiguration.class)
public @interface EnableYfStorage {
}
这个注解的作用就是导入自动装配类,主要是用于yfway-object-storage-starter模块
7.新建spring.factories文件
spring.factories 是 Spring Boot 中的一个特殊的配置文件,它用于自动化配置(auto-configuration)和自动装配(auto-wiring)。
在 Spring Boot 应用程序中,spring.factories 文件通常位于 META-INF/ 目录下,用于定义自动化配置和自动装配的类 。
我们来到yfway-object-storage-starter这个模块,新建一个自动装配类,内容如下:
@Configuration
@EnableYfStorage
public class YfStorageAutoConfiguration {
}
看到@EnableYfStorage这个注解没,我们引入了这个注解,就相当于了yfway-object-storage-core这个模块的自动装配类
最后一步新建spring.factories文件,添加以下内容:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.yfway.storage.config.YfStorageAutoConfiguration
自此一个自定义starter就此完成
接下来就是打成jar包,供其它项目使用
下面是一个调用例子:
首先引入我们打包好的依赖
<dependency>
<groupId>com.storage</groupId>
<artifactId>yfway-object-storage-starter</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
groupId artifactId version都是怎么来的?
其实就是对应yfway-object-storage-starter的pom文件里面的相关配置
<parent>
<artifactId>yfway-object-storage</artifactId>
<groupId>com.storage</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>yfway-object-storage-starter</artifactId>
最后就是测试用例了:
@Autowired
YfCosService yfCosService;
@Test
public void upload()throws Exception {
String objectName = "test/test12.txt";
String path = "E:\\a.txt";
yfCosService.uploadFile(objectName,new File(path),null,null);
}
8.优化
上面的调用例子并不是很简洁,首先你要使用@Autowired注解来装配YfCosService,然后调用uploadFile方法,并且方法里面必须传四个参数(即使可以为空),因此我们可以进一步优化这个starter,希望得到以下结果:
@Test
public void upload()throws Exception {
String objectName = "test/test12.txt";
String path = "E:\\a.txt";
YfCosStorageUtil.uploadFile(objectName,path);
}
封装一个YfCosStorageUtil工具类,根据参数的可选性,定义一系列上传方法,
public class YfCosStorageUtil {
@Autowired
private YfCosService yfCosService;
@Getter
private static YfCosService storage;
@PostConstruct
public void init(){
storage = this.yfCosService;
}
public static StorageResult uploadFile(String objectName, String path){
return storage.uploadFile(objectName, new File(path), null, null);
}
public static StorageResult uploadFile(String objectName, String path, YfCosConfig yfCosConfig){
return storage.uploadFile(objectName, new File(path), yfCosConfig, null);
}
public static StorageResult uploadFile(String objectName, File file){
return storage.uploadFile(objectName, file, null, null);
}
public static StorageResult uploadFile(String objectName, File file, YfCosConfig yfCosConfig){
return storage.uploadFile(objectName, file, yfCosConfig, null);
}
public static StorageResult uploadFile(String objectName, File file, YfCosConfig yfCosConfig, ObjectMetadata objectMetadata){
return storage.uploadFile(objectName, file, yfCosConfig, objectMetadata);
}
}
简单来说就是使用YfCosStorageUtil对uploadFile接口进一步封装。
这里需要注意的是因为静态方法无法直接使用@Autowired注入进行的bean,因此这里使用@PostConstruct注解对其进行初始化(类似于类的构造函数)
接下来还要改两个地方:
1.打开自动装配类YfCosStorageConfiguration,新加如下代码:
@Bean
@ConditionalOnMissingBean
public YfCosStorageUtil yfStorageUtil(){
return new YfCosStorageUtil();
}
其实它的作用就是使这段代码生效
@PostConstruct
public void init(){
storage = this.yfCosService;
}
2.打开YfStorageAutoConfiguration,要加一个@Order注解,代码如下:
@Order(1)
@Configuration
@EnableConfigurationProperties(StorageProperties.class)
@Import({YfCosStorageConfiguration.class})
public class YfStorageAutoConfiguration {
}
@Order 用于指定 Bean 的加载顺序, @Order 中的值越小,优先级越高 ,如果不加上这个注解,YfCosStorageUtil中可能无法直接使用YfCosService