Domanda

I've followed the basic setup instructions on the GWT-GIN tutorial page. I'm on Step #3 (Declare bindings) and am trying to figure out how to use GIN's Binder API.

public class MyModule extends AbstractGinModule {
    @Override
    public void configure() {
        // 1. Declare an instance of EventBus and make sure every
        // injection request pulls back the same instance.
        EventBus eventBus = new EventBus();
        bind(EventBus.class).to??? // no toInstance() method!

        // 2. Declare two instances of Fizz using different constructors,
        // and allow the injection of either of them.
        Fizz f1 = new Fizz(true, "oh yeah", null);
        Fizz f2 = new Fizz();
        bind(Fizz.class).to??? // no toInstance() AND don't know how to choose f1 or f2!

        // 3. Declare a List of Buzz objects and allow them to be
        // injectable.
        List<Buzz> buzzes = new ArrayList<Buzz>();
        configureBuzzes(buzzes); // adds configured Buzz objects to the list
        bind(???).to(buzzes); // no toInstance() methods AND how to bind List<?>.class?!

        // 4. Conditionally bind SomePlace to Place *only* when we want the default Place
        // that 'historyHandler.handleCurrentHistory()' will go to when called onModuleLoad.
        bind(Place.class).to(SomePlace.class); // forces me to only have SomePlace instances!
    }
}

The four use cases above are what I'm struggling with. Respectively:

  1. How to reuse the same instance of EventBus every time a client requests it?
  2. Building from #1 above, how to have 2+ different instances that can be injected under different scenarios?
  3. How to inject a List of anything?
  4. Might be the same as #2 above, but how to bind 2+ Place subclasses to Place.class?

Thanks in advance!

È stato utile?

Soluzione

Good questions that help to shed light on how Guice itself works, and the distinctions between Guice and Gin. Gin is not quite the same as Guice - the configure() method runs when generating your JavaScript, so that the compiler only bakes in the right set of types - otherwise your app could potentially contain the whole JRE! This is slightly cheating for Gin to do this, and once you understand this, GWT DI makes a little more sense.

The basic idea is that the configure() method is only supposed to deal with wiring - not creating instances. This provides the answer to 1), and part of the answer to 2). Actually writing code that will be used when the app is running (Provider objects, @Provides methods, and of course anything annotated with @Inject) needs to be the other way around - it will be compiled only into JS. This means that while you can define methods like configureBuzzes in 3), you need to be careful only to ever call these from inside the configure() method - and never call configure() from regular app code.

The answers for 2), 3), and 4) are mostly to do with how Guice itself generally works. The solution I provide for 1) also works in normal Guice, and I would go so far as to suggest this approach all the time - I find it tend to make more readable code if you don't mix the wiring and the actual object building.

  1. Don't create the instances in your configure() method, just do the bindings. You can set the binding to be a For example

    bind(EventBus.class).to(SimpleEventBus.class).in(Singleton.class);
    

    creates the instance, and scopes it to be a singleton - the default constructor will be used by default.

    • If you want to use a non-default constructor, there are several options. You could annotate the particular constructor with @Inject, and provide some annotation for each value (more on that in a moment), or you could build a provider or @Provides method to create the instance. Again, you may want @Singleton to have this make sense, but that'll depend on your use case (this will be another method in your GinModule):

      @Provides
      //@Singleton //optional, depends on your use case
      public Fizz provideFirstFizz() {
          return new Fizz(true, "oh yeah", null);
      }
      
    • Next, how do you provide two different kinds of the same thing? How do you do this in Guice? And how would you expect your code that gets a Fizz injected to get the right one? It turns out these probably all have the same answer - you need to find a way to indicate which instance you want. They are all the same type, so that isn't enough, but we can provide other hints, like an annotation on the injected field. Say our code that will need f1 and f2 looks like this

      @Inject
      @Red// Make up your own annotation, or use some existing ones
      private Fizz f1;
      
      @Inject @Blue private f2;
      

      Now we have a way to tell the difference, and we need to bind them using those same annotations. Since we're still assuming no @Inject on the Fizz constructor, we can't just do a bind() call, so instead we'll just add @Blue to the provides method:

      @Provides
      @Blue
      //@Singleton //optional, depends on your use case
      public Fizz provideFirstFizz() {
          return new Fizz(true, "oh yeah", null);
      }
      

      We can read this as "This method Provides Blue Fizz instances." For @Red, since we have the default ctor, we can use bind():

      bind(Fizz.class).annotatedWith(Red.class);//... no need to specify type
                                                //in this case, but might want 
                                                //singleton
      

      See https://code.google.com/p/google-guice/wiki/BindingAnnotations for more details on this.

  2. Again, we can use @Provides for this, or create and bind a Provider<T> type. As we've already done several provider methods, lets try a Provider<List<Buzz>>:

    public class BuzzListProvider implements Provider<List<Buzz>> {
        public List<Buzz> get() {
            List<Buzz> buzzes = new ArrayList<Buzz>();
            // Configure them... This might call on a @Inject defined
            // within this BuzzListProvider, on the ctor or a field, or
            // just some code in this method.
            return buzzes;
        }
    }
    

    Then, bind the Provider to that List:

    // cant say List<Buzz>.class, use TypeLiteral instead
    bind(new TypeLiteral<List<Buzz>>(){})
        .toProvider(BuzzListProvider.class);
    //  .in(Singleton.class); if the list needs to be only created once
    
  3. You're exactly right in your summary - this is exactly the same as 2. I usually make a @DefaultPlace annotation (or just plain @Default so I can reuse it all over) to deal with this kind of case.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top