Guice/Gin. How to inject multiple implementations
Asked Answered
A

2

7

I have a webapp that use GIN to inject dependencies at entry point.

private InjectorService injector = GWT.create(InjectorService.class);

@GinModules({PlaceContollerInject.class, RootViewInject.class})
public interface InjectorService extends Ginjector {

  RootView getRootView();
  PlaceController getPlaceConroller();

}

public class RootViewInject extends AbstractGinModule {

  @Override
  protected void configure() {
    bind(RootView.class).to(RootViewImpl.class);
  }
}

I need a mobile version that use different RootView implementation. Dependencies are described in the following module

public class RootViewMobileInject extends AbstractGinModule {

  @Override
  protected void configure() {
    bind(RootView.class).to(RootViewMobileImpl.class);
  }
}

The question is how to choose needed dependency conditionally whether we need mobile or default version. I've seen GWT-GIN Multiple Implementations, but haven't figured out that solution because the Provider breaks the dependencies' chain and the Factory Pattern breaks testability. In "Big Modular Java with Guice" video here (12 minute) Guice's injector with modules was presented as a replacement to Factories. So my question is should I create different Ginjector for mobile and default versions (like MobileFactory and DefaultFactory) of my app or it would be bad practice and I should configure one instance of Ginjector with all needed versions. For example with the annotation bindings like this.

public class RootViewMobileInject extends AbstractGinModule {

  @Override
  protected void configure() {
    bind(RootView.class).annotatedWith(Mobile.class).to(RootViewMobileImpl.class);
  }
}

and use @Mobile annotated bindings at GWT entry point

  @Inject
  private void setMobileRootView(@Mobile RootView rw) {
    this.rw = rw;
  }

In such a simplified example as above it might be possible. But if an application have more dependencies which need mobile and default versions. It looks like back to untestable "ugly" (as at Guice's presentation was said) factories. Sorry for my English. Any help is appreciated.

Alliterative answered 4/12, 2011 at 22:52 Comment(1)
I just wanted to add that the idea here and solution from @aldanok might be used also for client specific customization in the application. Client specific: gwt.xml + gin module + interface implementations.Plagiarize
I
9

I believe you'll want to use GWT deferred binding, using class replacement to bind a different version of your InjectorService depending on the user-agent. This will ensure the mobile version only has the mobile implementations compiled in (and downloaded)

So you would have InjectorServiceDesktop, InjectorServiceMobile, which both extend from InjectorService, then GWT.create(InjectorService.class), and let deferred binding decide which implementation it should use.

http://code.google.com/webtoolkit/doc/latest/DevGuideCodingBasicsDeferred.html#replacement

One instance of Ginjector with all versions seems bad as it means all code for both versions is always downloaded (and you certainly don't want to download all your desktop views into your mobile app)

EDIT: As Thomas points out in the comments, since the Injectors are generated classes, you'll need to put each InjectorServiceXXX inside a simple holder class that GWT.create()'s the InjectorServiceXXX, and use replacement to switch between the holders.

Incomprehension answered 5/12, 2011 at 9:56 Comment(4)
+1, except that won't work like this: you cannot have replacement and generation at the same time, you have to create a "InjectorServiceHolder" with 2 subclasses. Each one GWT.create()s a different InjectorService sub-interface. You use replacement on the "holder" to choose which InjectorService to use, and the GWT.create() on the InjectorService subinterface will trigger GIN's code generation.Eonian
Thank you for the answer. I rather agree with you that I should use different Ginjectors. it's very simple and convenient. But what do you suppose to do with the same problem on Guice's side which has no the deferred binding technique. What technique would you prefer to override base module - Modules.override or just Java's inheritance. Why? And more... how to configure Ginjector/Injector when all of the versions are needed (let's say DefaultView and LoggedInView)?Alliterative
Well, since Guice is running on your server rather than client, you don't have the same problem. If you want to inject different implementations for different configurations (for a Test environment for eg), its pretty straightforward with guice, just create a different AbstractModule subclass that uses the alternate implementations, and use this in your test environment. Its important to remember that that while gin & guice share the same annotations and purpose, they operate very differently, guice is runtime, and gin is compile time.Incomprehension
sorry, I see you are probably wondering about testing your client-side implementations in a JRE environment with guice. I can't really offer any advice on this I'm afraid!Incomprehension
R
1

To do what you want is actually rather complicated because your common injector interface, which is annotated with your Gin module, can not be pointing to an abstract Gin module. The Gin module pointed to by your Ginjector interface must be a concrete one. A concrete module cannot satisfy multiple configurations at the same time.

So what you do is: (a) Create your Ginjector interface, say ClientGinjector and your Module, ClientModule, for a desktop application.

(b) Create a second Ginjector interface, say ClientGinjectorTablet, extending the one you created in (a) but with an GinModule annotation pointing to a differnt Module, say ClientModuletablet.

-- Now you have two Ginjecor interfaces a default one and a secondary one for tablets, each one pointing to a module with its own Configure() implementations.

(c) Now you want to create Factory to get your Right Ginjector implementation. You can do this because the Ginjector you careated in (a) and (b) have a common demonitador which is the default interface created in (a). So you create an abstract facotry with a method such as this: public abstract ClientGinjector getInjector(); You create two children concrete classes One to get the Desktop/Default Ginjector and another one to get the Tablet Ginjector.

(d) Now you configure your module's gwt.xml just like Google IO on youtube explains you should do get your desired facotry during runtime, using GWT deferred bindings for each of your Ginjector factory.

(e) On your entrypoint the frist thing you is not to get a Ginjector but your factory for Ginjectors using GWT deferred binding. You call the abstract method that returns a ClientGinjector, your set.

(f) The epic fail at the end. Guice will not let you bind two times the same key (class plus annotation), even if you would be using different injectors (one for desktop and one for tablet). It seems that the key binding definitons are global, as soon as you have two modules redefining the same keys, that's the end of the adventure.

Roubaix answered 1/8, 2012 at 15:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.