Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create multiple instances of the same class with Guice

Sorry if it's a trivial question, I am new to Guice. Let's say I have the following class:

public class MyClass {
     @Inject
     public MyClass(final MyDependency myDependency) {
          this.name = myDependency.getName();
     }

     public String getName() {
          this.name;
     }
}

Then somewhere else I want to do:

public class SomeOtherClass {
    public void test() {
        MyClass instanceFoo = injector.getInstance(MyClass.class);
        MyClass instanceBar = injector.getInstance(MyClass.class);

        assertTrue("foo", instanceFoo.getName());
        assertTrue("bar", instanceBar.getName());
    }
}

I would like to have two instances of "MyClass", one which has a name of "foo" and one with a name of "bar" (i.e. each of them has a different instance of it's dependency as well). How can I add those 2 instances to my injector, and how can I retrieve each of them with "injector.getInstance"?

I don't want to create a subclass of "MyClass" for each possible dependency.

Thanks!

like image 247
Henri Lapierre Avatar asked Feb 19 '17 00:02

Henri Lapierre


People also ask

Can you create multiple instances of one class?

A program may create many objects of the same class. Objects are also called instances, and they can be stored in either a named variable or in an array or collection.

What is @named annotation in Guice?

Guice comes with a built-in binding annotation @Named that takes a string: public class RealBillingService implements BillingService { @Inject public RealBillingService(@Named("Checkout") CreditCardProcessor processor, TransactionLog transactionLog) { ... }

What does @inject do Guice?

Note that the only Guice-specific code in the above is the @Inject annotation. This annotation marks an injection point. Guice will attempt to reconcile the dependencies implied by the annotated constructor, method, or field.

What is @inject annotation in Guice?

Annotation Type Inject. @Target(value={METHOD,CONSTRUCTOR,FIELD}) @Retention(value=RUNTIME) @Documented public @interface Inject. Annotates members of your implementation class (constructors, methods and fields) into which the Injector should inject values.


1 Answers

Your question, as in the title, has a simpler solution: If you want an arbitrary number of instances of MyClass or MyDependency, you can inject a Provider<MyClass> or Provider<MyDependency>. This is true whether or not you've actually bound a Provider in a Module; for any T available in the graph Guice can actually inject either T or Provider<T>. (These correspond to Injector.getInstance and Injector.getProvider respectively.)

As in the body of the question, there are two pieces: Making more than one injectable Key for the same Class, and setting up the instances the way you want with different names and injected dependencies.

Keys and Binding Annotations

Guice identifies bindings using a Key, which is a fully-qualified class (like MyClass or List<MyClass>) combined with an optional "binding annotation". This is an annotation that is itself annotated with BindingAnnotation or javax.inject.Qualifier; you can create your own, or use the built in one called @Named that takes a String (so @Named("foo") is different from @Named("bar")).

Most of the time, you can get by without using Key directly: Use annotatedWith in your bind calls or add the annotation onto @Provides methods, and request them by putting the annotation onto constructor parameters or @Inject-annotated fields. However, you can still use getInstance and getProvider by creating a Key manually, using the static methods on Key. (For complicated cases, use TypeLiteral or Names.named; see their docs for details.)

Setting up instances

Now that you know how to inject @Named("foo") MyClass or @Foo MyClass, how do you provide them? Depending on your needs, I'd choose one of three options: binding toInstance, using a @Provides method, or creating an "assisted injection" factory.

  • If your MyClass instances don't need injection themselves, and you don't mutate or manipulate instance state, you could just prepare the instances named as needed and then bind them with toInstance.

  • You could also write a @Provides @Named("foo") MyClass method that takes a MyClass parameter (which Guice supplies through the injector), sets the name, and returns the instance. This is a low-overhead alternative to writing a Provider class or instance, and will get you a fresh instance rather than the sharing that a toInstance binding would imply.

  • If you really want the name to be a part of your class's constructor parameters, perhaps to keep the instance immutable, you can use "Assisted Injection" to tell Guice which parameters you supply yourself and which come from the Guice injector. This would allow you to inject a MyClass.Factory and call myClassFactory.create("foo"), which you could do directly in your consuming classes, or using the @Provides technique above. The details are a little beyond the scope of the question, but look up "Assisted Injection" for details on syntax and on adding the appropriate JAR.

like image 181
Jeff Bowman Avatar answered Oct 23 '22 06:10

Jeff Bowman