如何使用安装共同依赖模块的两个Guice模块

27

我正在开发一个由四个部分组成的项目:

  • Main 项目将所有内容汇集在一起。其中包含 public static void main(String... args) 入口点。
  • 组件 A
  • 组件 B
  • 第三方 Common 组件,AB 都引用它。

我使用 Guice 在这四个部分之间进行连接,而我的问题是:
AB 的主要 Guice 模块中,我安装了一个扩展了 Common 中定义的模块的模块。在运行时,此设置会失败,并显示以下错误:

绑定到 common.SomeClass 已在 common.AbstractCommonModule.configure() 中配置。 [source]

这是因为我两次调用了common.AbstractCommonModule.configure();一次是通过从组件Acom.a.MainModule.configure()安装common.AbstractCommonPrivateModule的子类实例,另一次是通过从组件Bcom.b.MainModule.configure()安装。

仅在Main中安装一个common.AbstractCommonPrivateModule不是一个选项,因为AbstractCommonPrivateModule实现了一个特定的绑定器方法bindComplicatedStuff(ComplicatedStuff),我只知道在AB内分别使用哪个参数。

我尝试通过将AB各自的主Guice模块包装在PrivateModule中来解决这整个问题。然而,这导致了下一个错误:

无法为%s创建绑定。它已经配置在一个或多个子注入器或私有模块%s上了。如果它在PrivateModule中,请确认是否忘记暴露该绑定?[source]

在我的情况下,AB各自的主要Guice模块实际上是ServletModule - 看起来我可以从Main安装两次。

如何解决这些错误并安装AbstractCommonPrivateModule模块两次?

编辑:我上传了一些示例代码(包括一些详细说明)到GitHub


4
这个问题听起来很有趣,但我在没有代码示例的情况下很难完全理解它。你能否发布(或链接到一个gist)你目前尝试过的内容? - Jan Galinski
2
common.SomeClass 绑定是什么样子的?你可以尝试以一种方式编写它,让 Guice 自动帮你去重。 - Tavian Barnes
我已经在GitHub上添加了一些代码(https://github.com/derabbink/simple_web_stack)。对于延迟感到抱歉。 - derabbink
2个回答

18

不要让AB安装Common,而是让它们从CommonrequireBinding()所需的类。然后依赖于AB的模块也需要安装Common。这可能会有点奇怪,但实际上很理想,因为AB现在与Common的耦合度更低。


更新

我安装两个ShiroWebModule的原因是,我希望ui模块中的Jersey资源仅使用一个Shiro配置进行安全保护(可以理解为密码保护资源),而api模块中的所有Jersey资源应该使用完全不同的Shiro配置进行安全保护(只能理解为承载令牌作为认证机制)。

总体来说,这是棘手的。Guice Injector提供了一种方法(通常是接口的一种实现)来处理整个应用程序;而不是每个包使用不同的机制。您的两个ModuleSwsApiServletModuleSwsUiServletModule提供了许多相同的绑定,而SwsModule将它们一起安装。本质上,你正在说“Guice,请提供基于承载令牌的身份验证机制”,然后立即说“Guice,请提供基于密码的身份验证机制”。它只能执行其中之一,因此不是选择一个任意的机制,而是快速失败。

当然,根据您的具体需求,有许多解决方案。最常见的方法是使用绑定注释,并要求UI和API代码请求不同的注释。这样,您可以安装相同接口或类的两个不同实现(具有不同的注释)。
以下是一个示例:
package api;

public class ApiResources {
  @Inject
  public ApiResources(@ApiAuthMechanism AuthMechanism auth) {
    this.auth = auth;
  }
}

---

package api;

public class ApiModule implements Module {
  public void configure() {
    bind(AuthMechanism.class).annotatedWith(ApiAuthMechanism.class)
        .to(BearerTokenAuthMechanism.class);
  }
}

---

package ui;

public class UiResources {
  @Inject
  public UiResources(@UiAuthMechanism AuthMechanism auth) {
    this.auth = auth;
  }
}

---

package ui;

public class UiModule implements Module {
  public void configure() {
    bind(AuthMechanism.class).annotatedWith(UiAuthMechanism.class)
        .to(PasswordAuthMechanism.class);
  }
}

---

package webap;

public class WebappModule implements Module {
  public void configure() {
    // These modules can be installed together,
    // because they don't install overlapping bindings 
    install(new ApiModule());
    install(new UiModule());
  }
}

您在评论中提到,您无法控制安装重叠绑定的情况,因为它们来自第三方模块。如果是这种情况(我没有看到代码中发生这种情况),那么第三方可能出于安全原因不希望您尝试做您正在尝试做的事情。例如,只是绑定基于密码的机制可能会在整个应用程序中引入漏洞。值得尝试更好地了解第三方打算如何使用他们的模块。
另一个选项是使用两个完全独立的Injector实例,每个绑定一个。然后您可以直接将所需的实例手动传递给UI和API代码。这有点违背了Guice的目的,但并不总是错误的决定。使用Injector可以使此过程更加轻松。
作为附带说明,你的“示例代码”非常庞大,可能超过90%与问题无关。请在未来花时间创建一个 SSCCE,其中只包含与手头问题相关的代码。没有人会浏览100多个Java文件和7,300多行代码来理解你的问题。这不仅使试图帮助你的人更容易,而且仅尝试创建演示问题的SSCCE通常足以帮助你理解和解决问题。

我喜欢这个建议,通常来说这就是我要做的事情。但在我的情况下,common.SomeClass 绑定是由第三方库的 Module 构建的。我不能简单地从我的 Main 模块中只安装一次该模块,因为它依赖于其他绑定,在 AB 的范围内是不同的。我在 GitHub 上添加了示例代码。 - derabbink
感谢提供 SSCCE。我会尝试在这个周末进行调试。 - dimo414
感谢您扩展您的答案。关于您的侧面说明:我意识到“SSCCE”中有很多辅助代码,但我想提供一个非人为制造的示例,使用实际库(Shiro)演示问题,并且需要一些辅助代码(可能比我的少)。然而,这也是我包含自述文件的原因。 - derabbink
我很欣赏你的用意,但是非人为制造的例子存在一个问题,那就是仍然有几个移动部分,其中一个或多个可能会涉及。一个人为制造的但又代表性的例子可以让我们理解整个问题,而我无法花时间理解你发布的代码示例中正在发生的事情。 - dimo414

1
要安装相同的模块两次,请重写您的模块中的.equals方法以引用类而不是对象相等性。Guice 不会安装已经安装过的模块。大多数情况下,这并没有什么帮助,因为您会打出以下内容:
install new AbstractCommonPrivateModule();

因此,每个对象都是不同的实例,与上一个实例不相等。重写equals方法可以解决这个问题:

@Override
public boolean equals(Object obj) {
    return obj != null && this.getClass().equals(obj.getClass());
}

// Override hashCode as well.
@Override
public int hashCode() {
    return this.getClass().hashCode();
}

然而,需要注意的是,这种方法经常是不正确的。 为什么不要做上述操作? 此时,您并没有真正利用Guice或依赖注入。相反,您已经将AbstractCommonPrivateModule的实现与安装它的B和C的实现紧密耦合在一起。正如@dimo414所提到的那样,似乎OP真的想使用两个不同的ShiroWebModule,这正是Guice通过在更高层次安装这两个不同的模块来做到的。同样,更高级别的安装允许您在测试时进行交换。如果您实际上想在某个时候交换其中一个模块,Guice将再次中断。 如果您覆盖了一个模块(这是另一个有用的测试工具),这也可能会破坏它。 OP还想两次安装通用模块。包装另一个库的通用模块会增加额外的风险;原始作者可能有很好的理由没有实现上述技巧,例如安全性。

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