一个接口多个实现类,@Primary帮你智能选择

本文介绍了一种统一文件上传接口的设计方案,通过实现不同存储方式(本地、FastDFS、MinIO)的具体接口,使得业务代码可以不关心底层存储细节,轻松实现文件存储方式的切换。

背景:

        最近做产品的开发,其中涉及到文件上传的功能,不同的场景使用的文件存储方式不一样,比如本地文件存储、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;
}

创建一个完整的Eclipse图书管理系统是一个复杂的任务,涉及到多个组件如用户界面、数据库交互等。由于这是一个简短的回答,我将提供一个基本的框架和步骤概述,你可以在此基础上进一步开发。 首先,你需要安装Eclipse IDE并配置JEE(Java Enterprise Edition)环境,以便支持Web应用开发。然后可以按照以下步骤开始: 1. **设计数据库**: 使用SQL创建一个图书数据库表,包含字段如id(主键)、title(书名)、author(作者)、isbn(ISBN号码)和quantity(库存量)。 ```sql CREATE TABLE Books ( id INT PRIMARY KEY, title VARCHAR(255), author VARCHAR(255), isbn VARCHAR(13) UNIQUE, quantity INT ); ``` 2. **创建Java实体(Entity)**: 为了操作数据库,你需要创建Java POJOs (Plain Old Java Objects),代表数据库表中的实体。 ```java @Entity public class Book { @Id private int id; private String title; private String author; private String isbn; private int quantity; // getters and setters } ``` 3. **设置数据访问层(DAO)**: 使用JPA或Hibernate创建DAO来处理CRUD操作。 4. **设计UI**: 使用Spring MVC或JavaFX创建用户界面,包括添加图书、查看图书列表、编辑和删除功能。例如,`AddBookController.java`: ```java @Controller @RequestMapping("/books") public class AddBookController { @Autowired private BookService bookService; @PostMapping("/add") public RedirectView addBook(@ModelAttribute("book") Book book) { bookService.save(book); return new RedirectView("books"); } } ``` 5. **实现业务逻辑**: 创建业务服务类`BookService.java`,处理业务规则和调用DAO。 6. **部署到服务器**: 将应用打包成war文件,部署到Tomcat或其他Java Web服务器。 这只是一个非常基础的示例,实际项目会更复杂,包括错误处理、分页、权限控制和前端样式等。如果你需要具体的代码片段,可以告诉我你需要哪个部分的指导,比如数据库连接、MVC架构的某个方法,我会你详细说明。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

chenjp111111

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值