Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

java

guice

I'm working on a project that consists of four parts:

  • The Main project that brings everything together. This contains the public static void main(String... args) entry point.
  • Component A
  • Component B
  • A 3rd party 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 As and Bs 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 at common.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 PrivateModules. 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 ServletModules - 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

like image 806
derabbink Avatar asked Nov 22 '15 17:11

derabbink


People also ask

How does Guice dependency injection work?

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.

What is Guice dependency injection?

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.

What are Guice modules?

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.

What is a Guice AbstractModule?

AbstractModule is a helper class used to add bindings to the Guice injector.


2 Answers

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 ShiroWebModules 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).

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 Modules, 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 Injectors 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.

like image 163
dimo414 Avatar answered Oct 01 '22 01:10

dimo414


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 ShiroWebModules, 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.

like image 20
hubatish Avatar answered Oct 01 '22 01:10

hubatish