最近在开发一个在线网盘的功能, 支持多个存储策略. 启动时, 读取数据库, 获取当前启用的存储类型, 然后项目启动后, 还可以动态切换存储类型.
由于是基于 Spring 开发的, 所以一般是这么写的:
接口:
public interface FileService { /** * 接口中的方法, 以此为例. */ void method1(); /** * 获取当前的存储类型 * @return 存储类型 */ String getStorageType(); }
阿里云实现类:
@Service public class AliyunFileService implements FileService { @Override public void method1() { // do something... } @Override public String getStorageType() { return "阿里云"; } }
腾讯云实现类:
@Service public class TencentFileService implements FileService { @Override public void method1() { // do something... } @Override public String getStorageType() { return "腾讯云"; } }
然后在 Controller 层注入:
@Controller public class FileController { @Resource private FileService fileService; @GetMapping("xxx") public void method1() { fileService.method1(); } }
但, 这样肯定会出错的, 因为 FileService
接口, 有两个实现类, 都标注了 @Service
, 注入时, Spring 不知道到底注入哪个.
这办法不可行, 即使指定了注入哪个, 也没办法实现动态切换注入的类.
那么换个思路, 不使用 @Resource
注入, 而是在项目启动完后, 获取 FileService
类型的所有类, 然后从数据库获取当前启用的存储类型, set 到 Controller
的 fileService
属性中. 具体看代码吧:
两个 Service
类的代码不变, 新增获取存储类型的工厂类:
@Component public class StorageTypeFactory implements ApplicationContextAware { private static Map<String, FileService> storageTypeEnumFileServiceMap; private static ApplicationContext applicationContext; /** * 项目启动时执行 */ @Override public void setApplicationContext(ApplicationContext act) throws BeansException { applicationContext = act; // 获取 Spring 容器中所有 FileService 类型的类 storageTypeEnumFileServiceMap = act.getBeansOfType(FileService.class); } /** * 获取指定存储类型 Service */ public static FileService getStorageTypeService(String type) { FileService result = null; for (FileService fileService : storageTypeEnumFileServiceMap.values()) { if (fileService.getStorageType() == type) { result = fileService; break; } } if (result == null) { // 未知的存储类型 throw new UnknownStorageTypeException(type.getDescription()); } return result; } public static ApplicationContext getApplicationContext() { return applicationContext; } }
@Controller public class FileController { private FileService fileService; @GetMapping("xxx") public void method1() { fileService.method1(); } /** * PostConstruct 注解, 表示该类初始化的时候, 自动调用该方法. */ @PostConstruct @GetMapping("/updateStorageType") public void initStorageType(String storageType) { // 如果 storageType 为空, 则表示是启动时初始化, 有值则说明是 Web 接口动态更改的 if (storageType == null) { // 伪代码, 读取数据库获取当前存储类型 storageType = xxxService.getCurrentStorage(); } // 设置 fileService 类为当前存储类型对应的 Service fileService = StorageTypeFactory.getStorageTypeService(storageType); } }
大概就是这样, 主要就是不直接使用 @Resouce
注入, 而是在启动时, 先获取所有的 Service
, 存储到 Map 中, 提供静态方法, 然后利用 @PostConstruct
启动时自动调用初始化方法, 动态注入 fileService
.