如何动态获取Spring CrudRepository实现?避免Switch冗余
我们业务项目中有多达50组Entity与CrudRepository,需要实现一个供用户调用的管理器来处理CSV数据导入。CSV每行格式示例如下:
trigram_1;col_1;col_2
trigram_2;col_1;col_2;col_3
(不同trigram对应的列数存在差异)
目前已通过反射与内省完成Entity的实例化及数据填充,核心实现代码如下:
public Map<String,Class> findAllClassesUsingReflectionsLibrary(String packageName) { Reflections reflections = new Reflections(packageName, new SubTypesScanner(false)); return reflections.getSubTypesOf(MySuperEntity.class) .stream() .collect(Collectors.toMap(Class::SimpleName, obj -> obj)); } public foo(String trigram, String data){ // this is only an example of usage Map<String,Class> map = findAllClassesUsingReflectionsLibrary("path.to.package.entity"); String key = "prefix"+trigram+"Db"; if(map.contains(key)){ MySuperEntity entity = map.get(key).newInstance().instantiate(data); // instantiate是MySuperEntity父类定义的方法,用于创建实例并填充属性 } //.... }
当前需要为Repository实现类似的动态获取逻辑:Repository为CrudRepository接口,无法通过反射直接实例化;原方案通过Switch分支匹配trigram返回对应Repository,但50个分支过于冗余。
请问能否在不手动实现Repository接口的前提下,动态获取其由Spring生成的实现类?
当然可以,Spring本身提供了多种方式帮你动态获取它生成的CrudRepository实现类,无需手动编写分支或实现接口,以下是几种可行方案:
方案1:通过Spring ApplicationContext批量获取Repository实例
Spring会自动为每个CrudRepository接口生成代理实现并纳入容器管理,你可以借助ApplicationContext获取所有Repository实例,建立trigram与Repository的映射关系:
- 定义一个Repository父接口,让所有业务Repository继承它,方便批量获取:
public interface BaseRepository<T extends MySuperEntity, ID> extends CrudRepository<T, ID> { }
所有业务Repository修改为继承BaseRepository,例如:
public interface Trigram1DbRepository extends BaseRepository<Trigram1Db, Long> { }
- 在导入管理器中注入
ApplicationContext,初始化trigram到Repository的映射:
@Component public class CsvImportManager { private final Map<String, BaseRepository<? extends MySuperEntity, ?>> repoMap = new HashMap<>(); // 构造注入ApplicationContext public CsvImportManager(ApplicationContext ctx) { // 获取所有BaseRepository类型的Bean Map<String, BaseRepository> repoBeans = ctx.getBeansOfType(BaseRepository.class); // 遍历建立映射,需根据你的Repository命名规则实现trigram提取逻辑 repoBeans.forEach((beanName, repo) -> { String trigram = extractTrigramFromRepoName(beanName); repoMap.put(trigram, repo); }); } // 根据Repository命名规则提取trigram(示例逻辑,需根据实际调整) private String extractTrigramFromRepoName(String beanName) { // 示例:从"trigram1DbRepository"提取出"trigram_1" String repoNameWithoutSuffix = beanName.replace("Repository", ""); String entityName = repoNameWithoutSuffix.replace("prefix", "").replace("Db", ""); // 驼峰转下划线(需引入Guava的CaseFormat,或自行实现转换) return CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, entityName); } // 业务处理方法 public void processCsv(String trigram, String data) { BaseRepository<? extends MySuperEntity, ?> repo = repoMap.get(trigram); if (repo != null) { Map<String,Class> entityMap = findAllClassesUsingReflectionsLibrary("path.to.package.entity"); String entityKey = "prefix"+trigram+"Db"; if(entityMap.containsKey(entityKey)){ try { MySuperEntity entity = entityMap.get(entityKey).newInstance().instantiate(data); repo.save(entity); } catch (InstantiationException | IllegalAccessException e) { // 处理实例化异常 e.printStackTrace(); } } } } // 原有反射获取Entity类的方法 public Map<String,Class> findAllClassesUsingReflectionsLibrary(String packageName) { Reflections reflections = new Reflections(packageName, new SubTypesScanner(false)); return reflections.getSubTypesOf(MySuperEntity.class) .stream() .collect(Collectors.toMap(Class::SimpleName, obj -> obj)); } }
方案2:通过Spring Data工厂类动态生成Repository代理
如果不想依赖容器中的Bean,可直接使用Spring Data的RepositoryFactorySupport动态生成Repository实现:
@Autowired private EntityManager entityManager; public <T extends MySuperEntity> CrudRepository<T, ?> getRepositoryForEntity(Class<T> entityClass) { RepositoryFactorySupport factory = new JpaRepositoryFactory(entityManager); // 根据Entity类获取对应的Repository接口(需保证命名规则一致) Class<? extends CrudRepository<T, ?>> repoClass = getRepositoryClass(entityClass); return factory.getRepository(repoClass); } // 根据Entity类名获取Repository接口类 private <T extends MySuperEntity> Class<? extends CrudRepository<T, ?>> getRepositoryClass(Class<T> entityClass) { try { // 假设Repository命名为Entity类名+Repository,例如Trigram1Db -> Trigram1DbRepository String repoClassName = entityClass.getName() + "Repository"; return (Class<? extends CrudRepository<T, ?>>) Class.forName(repoClassName); } catch (ClassNotFoundException e) { throw new RuntimeException("未找到对应Entity的Repository接口", e); } }
在业务方法中,拿到Entity类后直接调用该方法即可获取对应的Repository实例。
关键注意点
- 核心前提是命名规则一致:trigram、Entity类、Repository接口的命名需有可关联的规律,这样才能通过字符串处理建立映射,避免硬编码。
- 方案1适合大多数常规场景,依赖Spring容器管理,实现简单;方案2更灵活,适合需要动态创建Repository的特殊场景。
内容的提问来源于stack exchange,提问作者Geoffrey BISCH

