Java的ServiceLoader和测试资源

Posted

技术标签:

【中文标题】Java的ServiceLoader和测试资源【英文标题】:Java's ServiceLoader and test resources 【发布时间】:2013-04-19 12:38:02 【问题描述】:

我有一个 Web 应用程序,它将 Hibernate Integrator 定义为 Java ServiceLoader 规范的一部分,如下所示:

src/main/resources/META-INF/services/org.hibernate.integrator.spi.Integrator

  # Define integrators that should be instantiated by the ServiceLoader
  org.emmerich.MyIntegrator

这是根据 Hibernate 指南 here 完成的。

我的问题是,当我尝试执行单元测试时,主 Integrator 描述符仍然被解析和实例化。这意味着,因为我在单元测试中模拟了大部分应用程序,所以当集成器尝试运行它时会遇到导致我的测试失败的错误。

我在测试资源中定义了相同的文件:

src/test/resources/META-INF/services/org.hibernate.integrator.spi.Integrator

  # Empty file to try and overwrite the main deployment description.

但是我发现测试和主集成器文件都被解析了。

我预计测试资源会覆盖主资源,从而使主资源过时,但事实并非如此。因为这两个文件都在类路径上(我通过 Maven 使用 surefire-plugin 运行测试,它将 test-classesclasses 放在类路径上)。 persistence.xml 也会发生类似的情况。

在我的单元测试环境中,我不希望实例化任何集成器,因为我想尽可能手动地控制这些 bean 的构建。假设我正在测试执行单元,我不希望出现其他可能影响测试运行的 bean,例如 Integrator。我认为这是单元测试期间完全合法的要求。但是,虽然主要资源仍由 ServiceLoader 解析,但这是不可能的。

我来做的解决方案是基于这里发布的persistence.xml 解决方案:

How to configure JPA for testing in Maven

我的问题是,在单元测试期间是否有比强制重命名更好的方法来排除主要资源的处理,尤其是在 ServiceLoader 文件的上下文中?

尝试更好地总结一下:

当您在类路径上有两个文件都以相同的服务接口命名时会发生什么?对我来说,似乎两个文件中的所有服务都被实例化了。似乎没有覆盖。

【问题讨论】:

【参考方案1】:

对于那些感兴趣的人,多一点调查会发现这并不是 Hibernate 的问题,而是ServiceLoader 加载文件的方式。来自API:

如果一个特定的具体提供者类被命名为多个 配置文件,或者在同一个配置文件中命名更多 不止一次,则忽略重复项。

不幸的是,这只适用于具体的类。因此,如果我在表单中指定同一文件的两个副本:

org.emmerich.MyServiceInterface

  org.emmerich.MyServiceImpl

那么MyServiceImpl 只被实例化一次。但是,如果我用两个不同的实现类指定两个文件,那么这两个实现都会被实例化。

这对于您希望更好地控制服务实例化位置的单元测试来说并不是很好。我对它背后的设计决策知之甚少,但是 Hibernate 对它的使用使得单元测试变得更加困难。

无论如何,我想出的解决方案是将主要资源的覆盖委托给我可以控制的文件,例如properties 文件。在服务内部,我现在检查该属性文件中的标志是真还是假。在我的主要资源中,这是真的。在我的测试中,这是错误的。因为查找属性会命中类路径上的第一个文件,所以我知道它将获得测试资源。我的结构如下所示:

src
  main
    resources
      META-INF
        services
          org.hibernate.integrator.spi.Integrator # has line MyIntegrator
    application.properties     # shouldIntegrate=true 
  test
    resources
      application.properties   # shouldIntegrate=false

MyIntegrator:

public class MyIntegrator implements Integrator 

  @Override
  public void integrate(Configuration configuration, SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) 
    if(shouldIntegrate()) 
      // do integration
    
  

  private boolean shouldIntegrate() 
    // read Properties file from classpath
    // getClass().getClassLoader().getResourceAsStream("application.properties")
    // return value of "shouldIntegrate"
  

当我在测试环境中运行时,属性文件查找指向测试资源。在测试环境之外,它指向主资源。

【讨论】:

以上是关于Java的ServiceLoader和测试资源的主要内容,如果未能解决你的问题,请参考以下文章

Java之ServiceLoader

java浅析JDK中ServiceLoader的源码

ServiceLoader的使用

何时在 OSGi 之类的东西上使用 ServiceLoader

java SPI 03-ServiceLoader jdk 源码解析

java SPI 03-ServiceLoader jdk 源码解析