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

时间:2015-11-22 17:08:15

标签: java guice

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

  • 将所有内容组合在一起的Main项目。这包含public static void main(String... args)入口点。
  • 组件A
  • 组件B
  • CommonA引用的第三方B组件。

我使用Guice来处理所有四个部分之间的管道,这是我的问题:
AB主要Guice模块中,我安装了一个模块,该模块扩展了Common中定义的模块。在运行时,此设置失败,并显示以下错误:

  

已在common.SomeClass配置了对common.AbstractCommonModule.configure()的绑定。 [source]

原因是我两次调用common.AbstractCommonModule.configure();一次通过从组件common.AbstractCommonPrivateModule' A安装com.a.MainModule.configure()的子类实例,再从组件B安装com.b.MainModule.configure() common.AbstractCommonPrivateModule }。

Main中只安装{em>一个 {/ 1}}实例不是一个选项,因为AbstractCommonPrivateModule实现了特定的绑定方法bindComplicatedStuff(ComplicatedStuff),我只分别知道AB内的论点。

我尝试通过在PrivateModule s中包装AB各自的主要Guice模块来解决这一问题。但是,这失败了下一个错误:

  

无法为%s创建绑定。它已经在一个或多个子注入器或私有模块上配置%s%n如果它在PrivateModule中,您是否忘记公开绑定? [source]

就我而言,AB各自的主要Guice模块实际上是ServletModule s - 显然我可以< / em>从Main安装两次。

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

修改:我上传了一些示例代码(包含有关详情的说明)to GitHub

2 个答案:

答案 0 :(得分:12)

不要让AB安装Common,而是让他们requireBinding()为他们所需的课程Common。然后,依赖AB的模块也需要安装Common。这可能有点奇怪,但实际上是可取的,因为AB现在与Common紧密耦合。

<强>更新

  

我安装两个ShiroWebModule的原因是因为我希望ui模块中的Jersey资源只能使用一个Shiro配置(一个解除密码保护资源的配置)来保护,而所有应使用完全不同的Shiro配置(仅将承载令牌理解为身份验证机制)来保护api模块中的泽西资源。

从广义上讲,这是棘手的。 Guice Injector为整个应用程序提供了一种做某事的方法(通常是一个接口的实现);每个包装没有不同的机制。您的两个ModuleSwsApiServletModuleSwsUiServletModule提供了许多相同的绑定,SwsModule将它们安装在一起。从本质上说,你会说#Gu; Guice,请提供一个基于承载令牌的认证机制&#34;然后立即说#34; Guice,请提供基于密码的身份验证机制&#34;。它只能做一个或另一个,所以不是任意选择一个,而是快速失败。

当然,有很多解决方案,具体取决于您的需求。最常见的是使用binding annotations并让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的目的,但它并不总是错误的决定。使用child Injectors可以减少痛苦。

顺便说一句,您的&#34;示例代码&#34;是巨大的,可能超过90%与问题无关。将来请花点时间创建一个SSCCE,其中只包含与手头问题相关的代码。任何人都无法筛选100多个Java文件和7,300多行代码来理解您的问题。这不仅会让那些试图帮助您的人更容易,而且只是尝试创建一个能够证明问题的SSCCE,通常足以帮助您自己理解和解决问题。

答案 1 :(得分:0)

要安装相同的模块两次,请覆盖模块中的.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的实施与安装它的BC的实施紧密结合。正如@ dimo414所提到的,OP似乎真的想要使用两个不同的ShiroWebModule,这正是Guice通过在更高级别安装这两个不同模块而擅长的。同样,更高级别的安装允许您在测试时换出。如果你真的想在某个时候交换其中一个模块,Guice将再次破解。

如果覆盖模块(这是另一个有用的测试工具),这也可能会中断。

OP还希望两次安装通用模块。包装另一个库的通用模块会增加额外的风险;原作者可能有很好的理由不自己实施上述技巧,例如安全性。