背景:
最近做产品的开发,其中涉及到文件上传的功能,不同的场景使用的文件存储方式不一样,比如本地文件存储、FastDfs存储、minio存储,如何做到不改动业务代码,快速切换呢。
实现方式:
首先Service层需要一个接口,包含一个上传方法。
public interface ISysFileService
{
/**
* 文件上传接口
*
* @param file 上传的文件
* @return 访问地址
* @throws Exception
*/
public String uploadFile(MultipartFile file) throws Exception;
}
其次分别实现三种不同的文件上传方法。
1、本地存储
@Service
public class LocalSysFileServiceImpl implements ISysFileService
{
/**
* 资源映射路径 前缀
*/
@Value("${file.prefix}")
public String localFilePrefix;
/**
* 域名或本机访问地址
*/
@Value("${file.domain}")
public String domain;
/**
* 上传文件存储在本地的根路径
*/
@Value("${file.path}")
private String localFilePath;
/**
* 本地文件上传接口
*
* @param file 上传的文件
* @return 访问地址
* @throws Exception
*/
@Override
public String uploadFile(MultipartFile file) throws Exception
{
String name = FileUploadUtils.upload(localFilePath, file);
String url = domain + localFilePrefix + name;
return url;
}
}
2、FastDfs存储。
@Service
public class FastDfsSysFileServiceImpl implements ISysFileService
{
/**
* 域名或本机访问地址
*/
@Value("${fdfs.domain}")
public String domain;
@Autowired
private FastFileStorageClient storageClient;
/**
* FastDfs文件上传接口
*
* @param file 上传的文件
* @return 访问地址
* @throws Exception
*/
@Override
public String uploadFile(MultipartFile file) throws Exception
{
StorePath storePath = storageClient.uploadFile(file.getInputStream(), file.getSize(),
FilenameUtils.getExtension(file.getOriginalFilename()), null);
return domain + "/" + storePath.getFullPath();
}
}
3、minio存储
@Service
public class MinioSysFileServiceImpl implements ISysFileService
{
@Autowired
private MinioConfig minioConfig;
@Autowired
private MinioClient client;
/**
* 本地文件上传接口
*
* @param file 上传的文件
* @return 访问地址
* @throws Exception
*/
@Override
public String uploadFile(MultipartFile file) throws Exception
{
String fileName = FileUploadUtils.extractFilename(file);
PutObjectArgs args = PutObjectArgs.builder()
.bucket(minioConfig.getBucketName())
.object(fileName)
.stream(file.getInputStream(), file.getSize(), -1)
.contentType(file.getContentType())
.build();
client.putObject(args);
return minioConfig.getUrl() + "/" + minioConfig.getBucketName() + "/" + fileName;
}
}
以上三个具体实现都完成了,如果想选择其中某一种存储怎么办呢,直接在实现类上添加
@Primary就可以自由切换了。
那么原理是什么呢?在我们创建bean时,会先获取bean的依赖bean,当匹配的bean的个数大于1时,
//如果匹配的bean⼤于1,执⾏determineAutowireCandidate⽅法
if (matchingBeans.size() > 1) {
autowiredBeanName = this.determineAutowireCandidate(matchingBeans, descriptor);
在determineAutowireCandidate方法中会进行判断
String primaryCandidate = this.determinePrimaryCandidate(candidates, requiredType);
if (primaryCandidate != null) {
return primaryCandidate;
} else {
String priorityCandidate = this.determineHighestPriorityCandidate(candidates, requiredType);
if (priorityCandidate != null) {
return priorityCandidate;
} else {
Iterator var6 = candidates.entrySet().iterator();
String candidateName;
Object beanInstance;
do {
if (!var6.hasNext()) {
return null;
}
在determinePrimaryCandidate中,会遍历所有同类型的bean,并找到带有Primary注解的bean进行返回,如果有多个就会报错。
Iterator var4 = candidates.entrySet().iterator();
while(var4.hasNext()) {
Entry<String, Object> entry = (Entry)var4.next();
String candidateBeanName = (String)entry.getKey();
Object beanInstance = entry.getValue();
if (this.isPrimary(candidateBeanName, beanInstance)) {
if (primaryBeanName != null) {
boolean candidateLocal = this.containsBeanDefinition(candidateBeanName);
boolean primaryLocal = this.containsBeanDefinition(primaryBeanName);
//isPrimary⽅法是判断是否有@Primary注解,如果有多个满⾜条件的bean有@Primary注解就会抛出异常:
if (candidateLocal && primaryLocal) {
throw new NoUniqueBeanDefinitionException(requiredType, candidates.size(), "more than one 'primary' bean found among candidates: " + candid }
if (candidateLocal) {
primaryBeanName = candidateBeanName;
}
} else {
primaryBeanName = candidateBeanName;
}
}
}
return primaryBeanName;
}