I'm working on a project that consists of four parts:
Main
project that brings everything together. This contains the public static void main(String... args)
entry point.A
B
Common
component that both A
and B
refer to.I'm using Guice for the plumbing between all four parts, and this is my problem:
In both A
s and B
s main Guice modules I install a module that extends one that is defined in Common
. At runtime this setup fails with the following error:
A binding to
common.SomeClass
was already configured atcommon.AbstractCommonModule.configure()
. [source]
The reason for this is that I'm invoking common.AbstractCommonModule.configure()
twice; 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()
.
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.
I tried working around this whole thing by wrapping A
's and B
's respective main Guice modules in PrivateModule
s. However, this failed with the next error:
Unable to create binding for %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? [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
.
How can I get around these errors and install the AbstractCommonPrivateModule
module twice?
Edit: I uploaded some example code (with explanation about some details) to GitHub
Using GuiceIn each of your constructors that need to have something injected in them, you just add an @Inject annotation and that tells Guice to do it's thing. Guice figures out how to give you an Emailer based on the type. If it's a simple object, it'll instantiate it and pass it in.
Guice is an open source, Java-based dependency injection framework. It is quiet lightweight and is actively developed/managed by Google. This tutorial covers most of the topics required for a basic understanding of Google Guice and to get a feel of how it works.
The Guice module helps you to inject Guice managed components into your play application. The injection points are defined by the upcoming @javax. inject. Inject annotation, which is bundled with play at the moment.
AbstractModule is a helper class used to add bindings to the Guice injector.
Rather than having A
and B
install Common
, have them requireBinding()
's for the classes they need from Common
. Then modules that rely on A
or B
will need to also install Common
. This may feel a little odd, but it's actually desirable, since A
and B
are now less tightly-coupled to Common
.
Update
The reason I am installing two
ShiroWebModule
s is because I want the Jersey resources in theui
module to only be secured using one Shiro configuration (one that unserstands password-protecting resources), while all Jersey resources in theapi
module should be be secured using an entirely different Shiro configuration (one that understands only bearer tokens as an authentication mechanism).
Broadly speaking, this is intractable. A Guice Injector
provides one way of doing something (generally one implementation of an interface) to the whole application; not different mechanisms per package. Your two Module
s, SwsApiServletModule
and SwsUiServletModule
provide a number of identical bindings, and SwsModule
installs them both together. 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". 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. 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. Then you manually pass the instances you need to the UI and API code directly. This somewhat defeats the purpose of Guice, but it isn't always the wrong decision. Using child Injector
s can make this less painful.
As an aside, your "sample code" is enormous, and probably more than 90% is unrelated to the problem. In the future please take the time to create an SSCCE that contains only the code relevant to the problem at hand. There's simply no way anyone's going to sift through 100+ Java files and 7,300+ lines of code to understand your problem. 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.
To install the same module twice, override the .equals
method in your module to refer to class rather than object equality. Guice won't install a module that is equal to one that has already been installed. 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:
@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. Instead, you've tightly coupled the implementation of AbstractCommonPrivateModule
with the implementation of B
and C
which install it. 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. 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.
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. 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With