根据是否加载可选模块而公开不同方法的类

时间:2017-01-30 03:54:22

标签: java plugins reflection

我有一个由多个模块组成的库:

  • 番石榴

core模块是必需的,而guava是可选的。还有其他可选模块(此问题代表最小的测试用例)。

每个模块都公开了一组用户可以调用的方法:

class CoreVerifier
{
  MapVerifier verify(Map);
}

class GuavaVerifier
{
  MultimapVerifier verify(Multimap);
}

我想要什么

  • 为用户提供一个可以在一个地方导出所有方法的类:

    class UnifiedVerifier
    {
      MapVerifier verify(Map);
      MultimapVerifier verify(Multimap);
    }
    
  • 我希望用户能够使用此类,即使运行时缺少可选模块(例如guava)。这意味着,UnifiedVerifier与类路径上的所有库一起编译,但在第二种方法引用的运行时MultimapVerifier不存在。

  • 即使第二种方法(取决于guava模块)在运行时不可用,用户也应该能够调用第一种方法。
  • 如果用户尝试调用第二种方法(取决于缺少的模块),则应该获得运行时异常。

究竟发生了什么

  • 如果用户从应用程序代码中调用第一个方法,则javac失败并显示:

    Application.java: cannot access MultimapVerifier
      class file for MultimapVerifier not found
    

意思是,即使第一个方法定义良好(核心模块在编译时可用),编译器也拒绝继续,因为第二个方法(它们没有使用)引用了一个缺少的类。类路径。

有没有办法在Java中实现这类功能?

assertj的类似技术

assertj有一个聪明的静态导入机制,它为每个模块(核心,番石榴)声明一个不同的Assertions类,Java的静态导入根据类型选择正确的方法你传入了。我已经在静态方法中使用了类似的机制,但现在我想要一些类似的东西,我不能使用静态方法。

2 个答案:

答案 0 :(得分:0)

是的,你可以这样做:

  1. 定义一个接口(可能是提供这些服务的模块的内部);我将其称为ThingyImplementation

  2. 将使用可选库(Guava等)的代码放在一个您不能在任何其他代码中直接引用的独特类中。该类实现ThingyImplementation

  3. 在运行时,使用Class.forName动态尝试使用可选的librayr加载类,并通过Class#newInstance实例化它,将结果分配给声明为ThingyImplementation的变量。通过接口使用生成的实例作为提供。

  4. 功能的一部分

    如果类路径上没有可选库,则在步骤3中加载或实例化类将引发异常,您可以在自己的异常中传播或包装。

    请注意,外部类(UnifiedVerifier)不能直接引用可选库定义的类型,因此您不能拥有MultimapVerifier verify(Multimap);。如果 提供,则必须接受Object,然后根据需要在可选模块实现中进行转换。

答案 1 :(得分:0)

我想出办法!您可以使用类阴影来实现所需的行为。

  1. 宣布两个模块:
    • 番石榴
  2. 在核心库中,声明每个模块公开的功能的接口。例如:
    • CoreVerifiers
    • GuavaVerifiers
  3. 在番石榴模块中第二次声明GuavaVerifiers
  4. 每个GuavaVerifiers接口都应该声明该模块实现的方法。因此,核心模块中的GuavaVerifiers应为空,而guava模块中的GuavaVerifiers应包含该模块实现的方法。 (意思是,如果用户只链接核心模块,他们就不应该看到任何与番石榴有关的功能)。
  5. 在每个模块中实现接口。例如CoreVerifiersImpl应该实现CoreVerifiers。核心模块中的GuavaVerifiersImpl应为空,因为核心模块中的GuavaVerifiers为空。另一方面,guava模块中的GuavaVerifiersImpl应该实现非空的GuavaVerifiers接口。
  6. 对于步骤2中的每个接口,使用委派给现有验证程序的默认方法在核心模块中声明接口。例如:

    public interface ForwardingCoreVerifiers
    {
      CoreVerifiers coreVerifiers();
    
      default CoreVerifiers method1()
      {
        coreVerifiers().method1();
      }
    }
    
  7. 最后,在核心库中,声明一个扩展所有转发接口的实现:

    public final class Verifiers
      implements ForwardingCoreVerifiers, ForwardingGuavaVerifiers
    {
      public CoreVerifiers coreVerifiers()
      {
        return new CoreVerifiersImpl(...);
      }
      public CoreVerifiers guavaVerifiers()
      {
        return new GuavaVerifiersImpl(...);
      }
    }
    
  8. 现在这里有魔力:

    • 如果用户在其类路径中包含核心模块,则编译器将阻止它们调用任何与番石榴相关的方法。
    • 但是,如果用户在类路径上的核心模块前面包含guava模块,那么突然用户将看到与guava相关的方法(因为guava模块GuavaVerifier接口将影响核心模块中的接口)。

    表现提示:

    • 在Java 8中,默认方法与抽象类不同:https://stackoverflow.com/a/30314501/14731
    • 因此,我建议使用抽象类作为核心功能(CoreVerifiers)并使用默认方法来处理其余模块(GuavaVerifiers

    更新:我为Java 9发布了updated question