【问题标题】:Spring: Generic class with autowired dependenciesSpring:具有自动装配依赖项的通用类
【发布时间】:2022-10-24 15:53:44
【问题描述】:

我有一个标准的 Spring Boot MVC 应用程序,其中包含许多实体以及相应的存储库和服务。组件之间共享了很多基础架构,因此我想将其抽象为泛型类。我目前试图实现的方式是这样的(只显示骨架来传达这个想法):

interface AbstractRepository<T> {
    fun findById(entityId: Long): T 
}

abstract class AbstractEntityService<T>(
  private val entityRepository: AbstractRepository<T>,
) {

  fun getEntity(entityId: Long): T = entityRepository.findById(entityId)
}

@Repository
interface MyRepository : AbstractRepository<MyEntity>

@Service
class MyEntityService(
  myRepository: MyRepository,
  /* some other dependencies */
) : AbstractEntityService<MyEntity>(myRepository) {
  /* some additional methods */
}

这似乎可行,即我可以实例化(或自动装配)MyEntityService。但是请注意,我必须将 MyRepository 显式传递给构造函数,而不是让 Spring 自动装配它。由于运行时类型擦除,这是不可能的。但到目前为止,它并没有给我带来太多困扰。

当我想向 AbstractEntityService 添加一些需要其他 bean 的逻辑时出现问题,即类似这样的东西

@Service
abstract class AbstractEntityService<T>(
  private val entityRepository: AbstractRepository<T>,
) {
  
  @Autowired
  private lateinit var otherService: OtherService

  fun getEntity(entityId: Long): T

  fun commonMethodUsingOtherService(): T
}

但是现在我遇到了一个问题,因为为了自动装配OtherService,我必须使我的抽象服务成为一个 Spring 组件 (@Service),它具有 Spring 试图注入在我的构造函数中声明的 AbstractRepository&lt;T&gt; 的不良副作用。由于上面提到的类型擦除,它找到了很多 AbstractRepository 类型的 bean 并且失败了。

我的问题: 如何说服 Spring 不要将 bean 注入我的 AbstractEntityService 构造函数?

相关问题:对于我的问题(在第一段中提到)是否有技术上不同的解决方案,可以避免可能的框架限制/缺点? IE。不使用继承,以不同的方式构造我的代码等。

【问题讨论】:

    标签: java spring kotlin dependency-injection


    【解决方案1】:

    这可以通过将AbstractEntityService 的字段注入替换为构造函数注入并删除@Service 注释来轻松解决:

    abstract class AbstractEntityService<T>(
        private val entityRepository: AbstractRepository<T>,
        private val otherService: OtherService,
    ) {
        ...
    }
    
    

    现在,如果这个类不是抽象的,它的依赖关系可以由 Spring 注入,至少与 otherService 是一个字段的定义一样好。但是这个定义更好,因为您现在还可以将依赖项从您的子类传递到这个类,如下所示:

    @Service
    class MyEntityService(
        myRepository: MyRepository,
        otherService: OtherService,
        /* some other dependencies */
    ) : AbstractEntityService<MyEntity>(myRepository, otherService) {
        /* some additional methods */
    }
    

    我不太确定您的意思是什么,为什么 MyRepository 由于类型擦除而无法自动装配,我想问题中缺少一些细节。

    如果问题是您的应用程序上下文中有 MyRepository 类型的不同组件,您可以使用 @Primary 注释其中一个组件,这使得 Spring 在必须自动装配单个候选时选择该组件。

    另一种方法是给你的bean一个名字,然后在你的MyEntityService中限定依赖:

    @Repository("myrepo")
    open class MyRepository : AbstractRepository<MyEntity> {
       ...
    }
    
    @Service
    class MyEntityService(
        @Qualifier("myrepo")
        myRepository: AbstractRepository<MyEntity>,
        otherService: OtherService,
        /* some other dependencies */
    ) : AbstractEntityService<MyEntity>(myRepository, otherService) {
        /* some additional methods */
    }
    
    

    【讨论】:

    • 也许我应该从一开始就说明我知道这个解决方案并发现它不是最理想的,因为(正如我所说的)我有“许多实体和相应的存储库和服务”。所以我有MyEntity1MyEntity2等,都有相应的repos MyRepository1MyRepository2,...和服务MyEntityService1MyEntityService2...OtherService只是@的一个实现细节987654338@ 所以它的子类不应该理想地知道这一点,否则我们将打破关注点分离原则。
    • 例如。如果AbstractEntityService 将来需要其他“私人”依赖怎么办?然后我们必须修改所有子类服务以构建该依赖关系并将其传递给超类,即使它们没有直接使用它。但我知道鉴于 Spring 和 JVM 的限制(见下文),可能没有更好的解决方案。
    • 关于“MyRepository 由于类型擦除而无法自动装配”,我的意思是:在一个理想的世界中,泛型类型参数在运行时保留,我可以想象我会用无参数构造函数定义 AbstractEntityService 并自动装配 entityRepository 和 @987654342 @字段。这将导致 IMO 正确分离关注点。不幸的是,这是不可能的,因为 Spring 无法自动装配 AbstractRepository&lt;T&gt;,因为具体的 T 在运行时会被删除。
    【解决方案2】:

    事实证明,这

    @Service
    abstract class AbstractEntityService<T>(
      private val entityRepository: AbstractRepository<T>,
    ) {
      
      @Autowired
      private lateinit var otherService: OtherService
    
      fun getEntity(entityId: Long): T
    
      fun commonMethodUsingOtherService(): T
    }
    

    实际有效,尽管 OP(我)在问题中声称。显然,Spring 支持抽象类中的自动装配。此外,@Service 注释(或一般@Component)可以理解被 Spring 忽略,因此它不会尝试将 AbstractRepository&lt;T&gt; 注入构造函数。另一方面,如果显式声明,Spring 可以注入它,因为它supports generics as bean qualifiers

    abstract class AbstractEntityService<T> {
      
      @Autowired
      private lateinit var otherService: OtherService
    
      @Autowired
      private lateinit var entityRepository: AbstractRepository<T>
    }
    

    当我发布这个问题时,我真的不知道我在想什么 :-) 也许,我被 IDE 发出的错误弄糊涂了,它显然不那么聪明并且无法执行泛型类型 bean 解析,所以它抱怨 Could not autowire. No beans of 'AbstractRepository&lt;T&gt;' type found.

    【讨论】:

      猜你喜欢
      • 2014-12-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-11-11
      • 2012-10-27
      • 1970-01-01
      • 2021-10-19
      • 1970-01-01
      相关资源
      最近更新 更多