简体   繁体   English

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

[英]How to use two Guice modules that install a common dependency module

I'm working on a project that consists of four parts: 我正在开展一个由四部分组成的项目:

  • The Main project that brings everything together. 将所有内容整合在一起的Main项目。 This contains the public static void main(String... args) entry point. 它包含public static void main(String... args)入口点。
  • Component A 组件A
  • Component B 组件B
  • A 3rd party Common component that both A and B refer to. 第三方Common组成,这两个AB参考。

I'm using Guice for the plumbing between all four parts, and this is my problem: 我正在使用Guice来处理所有四个部分之间的管道,这是我的问题:
In both A s and B s main Guice modules I install a module that extends one that is defined in Common . AB的主要Guice模块中,我安装了一个模块,该模块扩展了Common定义的模块。 At runtime this setup fails with the following error: 在运行时,此设置失败,并显示以下错误:

A binding to common.SomeClass was already configured at common.AbstractCommonModule.configure() . 已经在common.SomeClass common.AbstractCommonModule.configure()配置了对common.SomeClass的绑定。 [ source ] [ 来源 ]

The reason for this is that I'm invoking common.AbstractCommonModule.configure() twice; 原因是我正在调用common.AbstractCommonModule.configure()两次; once by installing a subclass instance of common.AbstractCommonPrivateModule from Component A 's com.a.MainModule.configure() , and a second time from Component B 's com.b.MainModule.configure() . 一旦通过安装的子类实例common.AbstractCommonPrivateModule自Component Acom.a.MainModule.configure()并从部件的第二时间Bcom.b.MainModule.configure()

Installing just one instance of common.AbstractCommonPrivateModule in Main is not an option, because AbstractCommonPrivateModule implements a specific binder method bindComplicatedStuff(ComplicatedStuff) , for which I only know the argument inside A and B , respectively. 仅安装一个实例common.AbstractCommonPrivateModuleMain是不是一种选择,因为AbstractCommonPrivateModule实现了一个特定的粘合剂方法bindComplicatedStuff(ComplicatedStuff)为此,我只知道内部参数AB分别。

I tried working around this whole thing by wrapping A 's and B 's respective main Guice modules in PrivateModule s. 我尝试通过在PrivateModule包装AB各自的主要Guice模块来解决这个问题。 However, this failed with the next error: 但是,由于下一个错误,此操作失败:

Unable to create binding for %s. 无法为%s创建绑定。 It was already configured on one or more child injectors or private modules %s%n If it was in a PrivateModule, did you forget to expose the binding? 它已经在一个或多个子注入器或私有模块上配置%s%n如果它在PrivateModule中,您是否忘记公开绑定? [ source ] [ 来源 ]

In my case, A 's and B 's respective main Guice modules are in fact ServletModule s - which apparently I can install twice from Main . 在我的例子中, AB各自的主要Guice模块实际上是ServletModule - 显然我可以Main安装两次。

How can I get around these errors and install the AbstractCommonPrivateModule module twice? 如何解决这些错误并安装AbstractCommonPrivateModule模块两次?

Edit: I uploaded some example code (with explanation about some details) to GitHub 编辑:向GitHub上传了一些示例代码(有关一些细节的说明)

Rather than having A and B install Common , have them requireBinding() 's for the classes they need from Common . 而不是让AB安装Common ,让它们需要来自Common所需类的requireBinding() Then modules that rely on A or B will need to also install Common . 然后依赖AB模块也需要安装Common This may feel a little odd, but it's actually desirable, since A and B are now less tightly-coupled to Common . 这可能感觉有点奇怪,但实际上这是可取的,因为AB现在与Common不太紧密耦合。


Update 更新

The reason I am installing two ShiroWebModule s is because I want the Jersey resources in the ui module to only be secured using one Shiro configuration (one that unserstands password-protecting resources), while all Jersey resources in the api module should be be secured using an entirely different Shiro configuration (one that understands only bearer tokens as an authentication mechanism). 我安装两个ShiroWebModule的原因是因为我希望ui模块中的Jersey资源只能使用一个Shiro配置(一个解密密码保护资源)来保护,而api模块中的所有Jersey资源都应该使用一种完全不同的Shiro配置(只能将承载令牌理解为认证机制)。

Broadly speaking, this is intractable. 从广义上讲,这是棘手的。 A Guice Injector provides one way of doing something (generally one implementation of an interface) to the whole application; Guice Injector为整个应用程序提供了一种做某事的方法(通常是一个接口的实现); not different mechanisms per package. 每个包装没有不同的机制。 Your two Module s, SwsApiServletModule and SwsUiServletModule provide a number of identical bindings, and SwsModule installs them both together. 您的两个ModuleSwsApiServletModuleSwsUiServletModule提供了许多相同的绑定, SwsModule将它们安装在一起。 In essence you're saying "Guice, please provide a bearer-token-based authentication mechanism" then immediately after saying "Guice, please provide a password-based authentication mechanism". 实质上你说“Guice,请提供基于bearer-token的认证机制”,然后在说“Guice之后,请提供基于密码的认证机制”。 It can only do one or the other, so rather than picking one arbitrarily, it fails-fast. 它只能做一个或另一个,所以不是任意选择一个,而是快速失败。

Of course, there are a number of solutions, depending on what exactly your needs are. 当然,有很多解决方案,具体取决于您的需求。 The most common is to use binding annotations and to have the UI and API code request different annotation. 最常见的是使用绑定注释并使UI和API代码请求不同的注释。 That way you can install two different implementations (with different annotations) of the same interface or class. 这样,您可以安装相同接口或类的两个不同实现(具有不同的注释)。

Here's an example: 这是一个例子:

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());
  }
}

You mention in a comment that you don't have control of the overlapping bindings being installed because they're coming from a third-party module. 您在评论中提到您无法控制正在安装的重叠绑定,因为它们来自第三方模块。 If that is the case (I didn't see where that was happening in your code) it's possible the third party doesn't want you doing what you're trying to do, for security reasons. 如果是这种情况(我没有看到您的代码中发生了什么),出于安全原因,第三方可能不希望您执行您尝试执行的操作。 For example, simply binding the password-based mechanism might introduce vulnerabilities in the whole app. 例如,简单地绑定基于密码的机制可能会在整个应用程序中引入漏洞。 It might be worth trying to better understand how the third party intends for their modules to be used. 可能值得尝试更好地了解第三方打算如何使用其模块。

Another option, which isn't ideal but can work for some use cases, is to use two wholly separate Injector instances, one with each binding. 另一个不理想但可以用于某些用例的选项是使用两个完全独立的Injector实例,每个实例具有一个绑定。 Then you manually pass the instances you need to the UI and API code directly. 然后,您手动将所需的实例直接传递给UI和API代码。 This somewhat defeats the purpose of Guice, but it isn't always the wrong decision. 这在一定程度上违背了Guice的目的,但并不总是错误的决定。 Using child Injector s can make this less painful. 使用儿童Injector可以Injector痛苦。


As an aside, your "sample code" is enormous, and probably more than 90% is unrelated to the problem. 另外,您的“示例代码”非常庞大,可能超过90%与此问题无关。 In the future please take the time to create an SSCCE that contains only the code relevant to the problem at hand. 将来请花点时间创建一个包含与手头问题相关的代码的SSCCE There's simply no way anyone's going to sift through 100+ Java files and 7,300+ lines of code to understand your problem. 没有人会筛选100多个Java文件和7,300多行代码来理解你的问题。 Not only will this make it easier for people who are trying to help you, but simply trying to create an SSCCE that demonstrates the problem will often be enough to help you understand and resolve it yourself. 这不仅会让那些试图帮助您的人更容易,而且只是尝试创建一个能够证明问题的SSCCE通常足以帮助您自己理解和解决问题。

To install the same module twice, override the .equals method in your module to refer to class rather than object equality. 要安装相同的模块两次,请覆盖模块中的.equals方法以引用类而不是对象相等。 Guice won't install a module that is equal to one that has already been installed. Guice不会安装与已安装的模块相同的模块。 This doesn't help much most of the time as you type: 当您键入时,这在大多数情况下都没有帮助:

install new AbstractCommonPrivateModule();

and so each object is a different instance which won't be equal to the last. 所以每个对象都是一个不同的实例,它不会等于最后一个。 Overriding the equals method gets around that: 覆盖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();
}

However, note that this method is often incorrect. 但请注意,此方法通常不正确。

Why not to do the above ? 为什么不这样做呢?

At this point, you're not really making use of Guice or dependency injection. 在这一点上,你并没有真正使用Guice或依赖注入。 Instead, you've tightly coupled the implementation of AbstractCommonPrivateModule with the implementation of B and C which install it. 相反,您已将AbstractCommonPrivateModule的实现与安装它的BC的实现紧密耦合。 As mentioned by @dimo414, it seems like here the OP really wants to use two different ShiroWebModule s, which is exactly what Guice is good at doing by installing those two different modules at a higher level. 正如@ dimo414所提到的,OP似乎真的想要使用两个不同的ShiroWebModule ,这正是Guice通过在更高级别安装这两个不同模块而擅长做的事情。 Likewise, higher level installs let you swap out while testing. 同样,更高级别的安装允许您在测试时换出。 If you actually want to swap one of the modules out at some point, Guice will again break. 如果你真的想在某个时候交换其中一个模块,Guice将再次破解。

This can also break if you override a module (which is another useful tool for testing). 如果覆盖模块(这是另一个有用的测试工具),这也可能会中断。

The OP also wants to install a generic module twice. OP还希望两次安装通用模块。 Wrapping another library's generic module adds additional risk; 包装另一个库的通用模块会增加额外的风险; the original authors may have very good reasons for not implementing the above trick themselves, such as security. 原作者可能有充分的理由不自己实施上述技巧,例如安全性。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM