Spring Data仓库实际上是如何实现的?

163

我已经使用Spring Data JPA仓库在我的项目中工作了一段时间,我知道以下几点:

  • 在仓库接口中,我们可以添加像findByCustomerNameAndPhone()这样的方法(假设customerNamephone是域对象中的字段)。
  • 然后,Spring通过在运行时(应用程序运行期间)实现上述仓库接口方法来提供实现。

我对如何编写代码感兴趣,我看了Spring JPA源代码和API文档,但找不到以下问题的答案:

  1. 仓库实现类如何在运行时生成以及如何实现和注入方法?
  2. Spring Data JPA是否使用CGlib或任何字节码操作库动态实现方法并进行注入?

请帮我解决以上问题,并提供相关支持文档,谢谢!

1个回答

202
首先,没有代码生成,这意味着:没有CGLib,根本没有字节码生成。基本的方法是使用Spring的ProxyFactory API在程序中创建JDK代理实例来支持接口,MethodInterceptor拦截所有对该实例的调用,并将方法路由到适当的位置:
  1. 如果存储库已使用自定义实现部分进行初始化(有关详细信息,请参见参考文档),并且调用的方法在该类中实现,则调用将路由到那里。
  2. 如果该方法是一个查询方法(请参见DefaultRepositoryInformation了解如何确定),则会启动存储特定的查询执行机制,并执行为该方法确定要执行的查询。为此,存在一种解析机制,试图在各种位置中识别显式声明的查询(使用方法上的@Query、JPA命名查询),最终回退到从方法名称派生查询。有关查询机制检测,请参见JpaQueryLookupStrategy。查询推导的解析逻辑可以在PartTree中找到。将存储特定的翻译转换为实际查询可以在JpaQueryCreator中看到。
  3. 如果上述情况都不适用,则执行的方法必须是由存储库特定的基类实现的(在JPA的情况下是SimpleJpaRepository),并且调用被路由到该实例。

实现路由逻辑的方法拦截器是QueryExecutorMethodInterceptor,高级路由逻辑可以在这里找到。

这些代理的创建被封装在一个标准的基于Java的工厂模式实现中。高级代理创建可以在RepositoryFactorySupport中找到。然后,存储特定的实现添加必要的基础设施组件,以便对于JPA,您可以继续编写如下代码:

EntityManager em = … // obtain an EntityManager
JpaRepositoryFactory factory = new JpaRepositoryFactory(em);
UserRepository repository = factory.getRepository(UserRepository.class);

我特别提到这一点是因为很明显,该代码的核心根本不需要Spring容器首先运行。它需要Spring作为类路径上的库(因为我们喜欢不重复造轮子),但一般来说与容器无关。
为了方便与DI容器集成,我们当然建立了与Spring Java配置、XML名称空间以及CDI扩展的集成,以便在普通CDI场景中可以使用Spring Data。

3
Oliver,你能详细说明一下Spring是如何发现首先标注@Repository的接口吗?查看RepositoryFactorySupport#getRepository()可以看到它需要将接口类作为参数,因此必须在其他地方发现接口。我尤其想知道如何找到带注释的接口并自动生成实现该接口的JDK代理Bean,这非常类似于Spring Data,但不是与存储库相关的应用特定目的。 - Chris Rice
2
你可能想看一下RepositoryComponentProvider。没有自动化的事情发生,只有对某些类型(无论是带注释还是携带注释)进行组件扫描,并为每个类型配置了一个FactoryBean - Oliver Drotbohm
2
抱歉评论了一篇旧帖子,但是我很好奇...仓库代理是单例对象吗?我们遇到了一个问题,我们的代码尝试调用repo方法,但它似乎永远都无法在代理上进行调用。它只是挂起了。我想知道它是否在等待一个繁忙的单例。 - iu.david

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接