­

Spring 动态注入依赖设计

  • 2019 年 12 月 29 日
  • 笔记

前言

最近在开发一个在线网盘的功能, 支持多个存储策略. 启动时, 读取数据库, 获取当前启用的存储类型, 然后项目启动后, 还可以动态切换存储类型.

由于是基于 Spring 开发的, 所以一般是这么写的:

接口:

public interface FileService {        /**       * 接口中的方法, 以此为例.       */      void method1();        /**       * 获取当前的存储类型       * @return  存储类型       */      String getStorageType();    }

腾讯云实现类:

@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 到 ControllerfileService 属性中. 具体看代码吧:

两个 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.