Spring中是否可以通过泛型类型管理bean注入?

问题描述 投票:0回答:1

我有两个具有相同结构的实体,唯一的区别是它们存储在不同的表中。所以我决定将它们的字段移至抽象类:

@Getter
@AllArgsConstructor
@NoArgsConstructor
@MappedSuperclass
public abstract class AbstractRecord {
    @Id
    Long id;

    @Setter
    @Column(name = "status")
    Long status;

}

@Entity(name = "record")
public class Record extends AbstractRecord {
}

@Entity(name = "adjusted_record")
public class AdjustedRecord extends AbstractRecord {
}

我对存储库做了同样的事情,并且我还在接口声明中引入了返回类型作为泛型:

@NoRepositoryBean
public interface AbstractRecordURepository<T extends AbstractRecord> extends JpaRepository<T, Long> {

    @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class)
    default T getNext() {
        // some logic
    }

}

@Repository
public interface RecordURepository extends AbstractRecordURepository<Record> {
}

@Repository
public interface AdjustedRecordURepository extends AbstractRecordURepository<AdjustedRecord> {
}

现在我有一个对两个实体都有共同逻辑的服务,唯一的区别是

MyService
类内部的存储库类型(及其实体类型),所以我决定对存储库类型执行与返回类型为
AbstractRecordURepository
。一个重要的细节是
MyService
中存储库字段的名称 - recordRepository

@Slf4j
@Service
@RequiredArgsConstructor
public class MyService<T extends AbstractRecord, R extends AbstractRecordURepository<T>> {
    private final R recordRepository;

    public void getNextRecord() {
        T record = recordRepository.getNext();
        // some logic
    }
}

所以我尝试以这种方式注入我的

myService
bean,一旦我希望将
AdaptedRecordURepository
作为
R
中的
myService
类型注入:

@Autowired
MyService<AdjustedRecord, AdjustedRecordURepository> myService;

但是我没有得到预期的结果。注入时通用类型会被忽略,RecordRepository 每次都会被注入到

myService
,因为它的字段名称是
recordRepository

所以,我的问题是:是否可以通过这种方式(通过泛型)管理 bean 注入。这不是一个完全坏主意吗?

java spring oop generics dependency-injection
1个回答
0
投票

Spring 中,运行时注入的 bean 的类型由它们在 Spring 上下文中的定义决定,而不是由代码中使用的泛型类型决定。这意味着 Spring 不会直接使用泛型类型(如

T
中的
R
MyService<T, R>
)来解决依赖关系。它依赖于实际的 bean 定义和限定符来连接依赖关系。这是因为 Java 对泛型使用类型擦除,这意味着泛型类型信息在运行时不可用。

您可以尝试使用 qualifiers 或定义特定配置来定义应注入哪个 bean。

首先,定义限定符来区分您的 bean。这将帮助 Spring 准确地知道您想要注入哪个 bean。

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
public @interface RecordType {
    String value();
}

然后使用这些限定符注释您的存储库接口:

@Repository
@RecordType("record")
public interface RecordURepository extends AbstractRecordURepository<Record> {
}

@Repository
@RecordType("adjustedRecord")
public interface AdjustedRecordURepository extends AbstractRecordURepository<AdjustedRecord> {
}

更新您的服务以指定它需要哪个存储库。您可以通过自动装配

ApplicationContext
并根据限定符值动态获取 bean 来实现此目的,使用 Lombok 中的
@RequiredArgsConstructor
:

@Slf4j
@Service
@RequiredArgsConstructor
public class MyService<T extends AbstractRecord> {

    private final ApplicationContext context;
    private AbstractRecordURepository<T> recordRepository;

    @PostConstruct
    public void init() {
        // Example usage: "adjustedRecord" or "record"
        String qualifier = "adjustedRecord"; // Determine this dynamically if needed
        recordRepository = (AbstractRecordURepository<T>) context.getBean(RecordType.class, qualifier);
    }

    public void getNextRecord() {
        T record = recordRepository.getNext();
        // some logic
    }
}

或者:您可以为您需要的每种类型的服务定义特定的配置。这将涉及为您计划使用的每种类型的

MyService
创建显式 bean。

您原来的

MyService
类被设计为通用的,接受任何类型的
AbstractRecord
及其相应的存储库。要使用基于配置的方法,需要修改
MyService
以直接通过其构造函数接受特定类型的
AbstractRecordURepository
,而不是依赖泛型类型
R

@Slf4j
@Service
public class MyService<T extends AbstractRecord> {

    private final AbstractRecordURepository<T> recordRepository;

    public MyService(AbstractRecordURepository<T> recordRepository) {
        this.recordRepository = recordRepository;
    }

    public void getNextRecord() {
        T record = recordRepository.getNext();
        // some logic
    }
}

通过

MyService
现在明确期望具体的
AbstractRecordURepository
,您可以为您需要的每种服务类型定义特定的 bean。这是在
@Configuration
类中完成的。对于每种类型的
MyService
,都会通过构造函数注入特定的存储库。

@Configuration
public class ServiceConfig {

    @Bean
    public MyService<Record> recordService(RecordURepository repository) {
        return new MyService<>(repository);
    }

    @Bean
    public MyService<AdjustedRecord> adjustedRecordService(AdjustedRecordURepository repository) {
        return new MyService<>(repository);
    }
}

通过在配置类中定义 bean,您可以显式地显示每个

MyService
实例的依赖关系。由于每个
MyService
bean 都显式配置了正确类型的存储库,因此您可以保持类型安全。通过特定的配置,Spring 不再需要根据泛型推断正确的存储库类型。这简化了注入过程,因为每个 bean 都明确定义了它的依赖关系。

但这降低了

MyService
自动适应新类型
AbstractRecord
的灵活性。每个新类型都需要在配置类中添加特定的 bean 定义。如果
AbstractRecord
类型集已知并且相对稳定,那么 显式配置会很有趣。

© www.soinside.com 2019 - 2024. All rights reserved.