如何在Spring Boot中在运行时动态选择一个TreeRepository bean,而无需编写庞大的switch?
我正在构建一个Java Spring Boot应用,用来管理一个二叉树结构。
目前,数据存储(MongoDB、PostgreSQL和内存)与树结构(自定义实现和集合)处理方式不同。为了测试,我通过直接修改 pom.xml 来切换策略,但我需要一种在系统运行时能够从UI动态处理的方法。
目前,我有这样一个通用接口:
public interface TreeRepository {
FolderNode save(FolderNode node);
List<FolderNode> findAll();
}
我目前使用 @ConditionalOnProperty 在应用启动时选择数据库。比如,这是我对内存数据库的实现:
@Repository
@ConditionalOnProperty(name = "app.storage", havingValue = "memory", matchIfMissing = true)
public class MemoryTreeRepository implements TreeRepository {
private final Map<String, FolderNode> nodes = new LinkedHashMap<>();
@Override
public FolderNode save(FolderNode node) {
nodes.put(node.id(), node);
return node;
}
// ... findAll implementation
}
通过这个构造函数注入:
@Service
public class TreeServiceImpl implements TreeService {
private final TreeRepository repository;
public TreeServiceImpl(TreeRepository repository) {
this.repository = repository;
}
@Override
public FolderNode createRoot(CreateNodeRequest request) {
FolderNode root = new FolderNode(UUID.randomUUID().toString(), request.name(), null);
return repository.save(root);
}
}
我的问题 我需要去掉 @ConditionalOnProperty,因为所有数据库都必须始终处于可用状态。数据库必须在应用运行时根据用户输入动态选择(例如,在HTTP请求中发送一个 "dbType": "mongo" 头)。如果我移除条件注解,Spring会抛出一个 NoUniqueBeanDefinitionException,因为有多个实现 TreeRepository 的Bean。
我的问题 在Spring Boot中,标准做法是什么,能够在我的 TreeServiceImpl 中根据请求数据注入实现并选择正确的 TreeRepository Bean,而不需要写一大段的 switch 语句?
解决方案
如果有多个Bean实现了同一个接口,它们应该能够顺利注册到你的应用上下文。你可能需要用 @Qualifier 对它们进行限定,或给它们赋予不同的名称。
你也可以将一个 Collection<Interface>(例如 List<Interface>)或 Map<String, Interface> 注入到你的组件中:
- 使用一组Bean时,只需遍历该集合并根据某个条件选择Bean(例如对
instanceof进行检查): - 对于Map,键将是Bean的名称,因此你可以按名称选择你的Bean。
你可能想构建一个带有专门实现的 Composite(Strategy?)来选择合适实现的结构。或者你可能希望通过某种工厂引入另一层间接性:
@Bean
class TreeRepositoryProvider {
private final Map<String, TreeRepository> repos; // or use a collection
TreeRepositoryProvider(final Map<String, TreeRepository> repos) { this.repos = repos; }
TreeRepository repoForType(String dbType) {
return Objects.requireNonNull(repos.get(dbType)); // replace with more robust logic
}
}
然后在你的服务中:
@Service
public class TreeServiceImpl implements TreeService {
private final TreeRepositoryProvider repositoryProvider;
public TreeServiceImpl(TreeRepositoryProvider repositoryProvider) {
this.repositoryProvider = repositoryProvider;
}
@Override
public FolderNode createRoot(CreateNodeRequest request) {
FolderNode root = new FolderNode(UUID.randomUUID().toString(), request.name(), null);
final TreeRepository repository = repositoryProvider.repoForType(request.getDbType());
return repository.save(root);
}
}
请参阅 Spring文档关于自动装配 和 Can I inject Map of all impl of a bean in Spring。