我有两个具有相同结构的实体,唯一的区别是它们存储在不同的表中。所以我决定将它们的字段移至抽象类:
@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 注入。这不是一个完全坏主意吗?
在 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
类型集已知并且相对稳定,那么 显式配置会很有趣。