Vortex
Vortex

Reputation: 789

Guice assisted inject, binding order

I ran into an error while implementing assisted injection.

Assisted injection worked up until I introduced another class called Manager which relies on assisted Person class. Manager wants to use Person (@Assited Address). The code breaks at the point of constructing injector graph. It does not go further.

Injector injector = Guice.createInjector(myModule); 

Intuitively, I understand when object A is assisted then B (who depends on A) is in fact also implicitly assisted through A.

Pls note, I checked SO. I think someone like ColinD would definitely know the answer How to use Guice's AssistedInject? How to bind Assisted Injected class to interface?

Out of curiosity, are there good techniques/tools to spot Guice misconfiguration and ease learning curve? I turned on ProvisionListener and using graph library. That helps a bit.

public class Person implements PersonInterface {
private String name;
private Address address;

 @Inject
 public Person(String name, @Assisted Address address) {
        this.name = name;
        this.address = address;
 }

}

public interface PersonInterface {
  public String getName();
  public Address getAddress();
}

public interface PersonFactory {
  public PersonInterface create(Address address);
}

public class Address {
 private final String address;  

 public Address(String address) {
    super();
    this.address = address;
 }
}

public class Manager implements IManager {  
 private final Person person;   
 @Inject
 public Manager(Person person) {
    this.person=person;
 }
 ...
 }

 configure() {

    install(new FactoryModuleBuilder()
             .implement(PersonInterface.class, Person.class)
             .build(PersonFactory.class));

    //
    bind(IManager.class).to(Manager.class);
}

Actual error is

com.google.inject.CreationException: Unable to create injector, see the following errors:

1) No implementation for ...assisted_inject.Address annotated with @com.google.inject.assistedinject.Assisted(value=) was bound.
  while locating ....assisted_inject.Address annotated with @com.google.inject.assistedinject.Assisted(value=)
    for parameter 2 at ....assisted_inject.Person.<init>(Person.java:13)

Upvotes: 1

Views: 5654

Answers (1)

Lachezar Balev
Lachezar Balev

Reputation: 12021

When you put this binding into your module:

bind(IManager.class).to(Manager.class);

Guice will try to create a new instance of The Manager class. It looks for either one (but only one) constructor annotated with @Inject or a as a fallback zero-argument constructor that is not private. This is the constructor that Guice will use:

@Inject
public Manager(Person person) {
   this.person=person;
}

Now following the same rule Guice will try to instantiate a Person by using an appropriate constructor and it will get stuck here:

@Inject
public Person(String name, @Assisted Address address) {
    this.name = name;
    this.address = address;
}

It will give up when trying to instantiate the address because of the @Assisted annotation. This annotation is a BindingAnnotation and Guice treats these specially - it tries to find explicit bindings for them and there are none. Read about binding annotations and you will understand why.

Since your manager is stateful and apparently manages a single person you may want to create a factory for these managers, e.g.:

public interface IManagerFactory {
    public IManager getIManager(PersonInterface p);
}

Then you will have an IManager, e.g.:

public interface IManager {
    public String getPersonName();
}

And an implementation that uses assisted injection:

public class Manager implements IManager {
    private final PersonInterface person;

    @Inject
    public Manager(@Assisted PersonInterface person) {
        this.person = person;
    }

    @Override
    public String getPersonName() {
        return person.getName();
    }

}

You can bind these in your module:

class MyModule extends AbstractModule {
     protected void configure() {
        install(new FactoryModuleBuilder()
           .implement(PersonInterface.class, Person.class)
                 .build(PersonFactory.class));


        install(new FactoryModuleBuilder()
            .implement(IManager.class, Manager.class)
            .build(IManagerFactory.class));
      }
    }

Inject the factories:

@Inject
PersonFactory pf;

@Inject
IManagerFactory manF;

And use them accordingly, e.g.:

public void testGuice() {
    PersonInterface pi = pf.create(new Address("boh"));
    IManager im = manF.getIManager(pi);

    System.out.println(im.getPersonName());
}

Upvotes: 4

Related Questions