我正在开展一个由四部分组成的项目:
Main
项目。这包含public static void main(String... args)
入口点。A
B
Common
和A
引用的第三方B
组件。我使用Guice来处理所有四个部分之间的管道,这是我的问题:
在A
和B
主要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)
,我只分别知道A
和B
内的论点。
我尝试通过在PrivateModule
s中包装A
和B
各自的主要Guice模块来解决这一问题。但是,这失败了下一个错误:
无法为%s创建绑定。它已经在一个或多个子注入器或私有模块上配置%s%n如果它在PrivateModule中,您是否忘记公开绑定? [source]
就我而言,A
和B
各自的主要Guice模块实际上是ServletModule
s - 显然我可以< / em>从Main
安装两次。
如何解决这些错误并安装AbstractCommonPrivateModule
模块两次?
修改:我上传了一些示例代码(包含有关详情的说明)to GitHub
答案 0 :(得分:12)
不要让A
和B
安装Common
,而是让他们requireBinding()
为他们所需的课程Common
。然后,依赖A
或B
的模块也需要安装Common
。这可能有点奇怪,但实际上是可取的,因为A
和B
现在与Common
紧密耦合。
<强>更新强>
我安装两个
ShiroWebModule
的原因是因为我希望ui
模块中的Jersey资源只能使用一个Shiro配置(一个解除密码保护资源的配置)来保护,而所有应使用完全不同的Shiro配置(仅将承载令牌理解为身份验证机制)来保护api
模块中的泽西资源。
从广义上讲,这是棘手的。 Guice Injector
为整个应用程序提供了一种做某事的方法(通常是一个接口的实现);每个包装没有不同的机制。您的两个Module
,SwsApiServletModule
和SwsUiServletModule
提供了许多相同的绑定,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 Injector
s可以减少痛苦。
顺便说一句,您的&#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
的实施与安装它的B
和C
的实施紧密结合。正如@ dimo414所提到的,OP似乎真的想要使用两个不同的ShiroWebModule
,这正是Guice通过在更高级别安装这两个不同模块而擅长的。同样,更高级别的安装允许您在测试时换出。如果你真的想在某个时候交换其中一个模块,Guice将再次破解。
如果覆盖模块(这是另一个有用的测试工具),这也可能会中断。
OP还希望两次安装通用模块。包装另一个库的通用模块会增加额外的风险;原作者可能有很好的理由不自己实施上述技巧,例如安全性。