Search code examples
javadependency-injectionguicejdbi

Configuring a Provider that returns a generic type in Guice


I'm trying to set up a Provider for DAOs created using JDBI. JDBI uses a Handle object (which is a wrapper around a JDBC Connection) and you can get hold of a DAO by using handle.attach(MyDaoType.class). Rather than having to write a separate Provider implementation for every DAO class I thought it would make sense to do this:

public class DaoProvider<T> implements Provider<T> {

  private final Class<T> daoType;
  private final Handle handle;

  @Injected 
  public DaoProvider(Class<T> daoType, Handle handle) {
    this.daoType = daoType;
    this.handle = handle;
  }

  @Override
  public T get() {
    return handle.attach(daoType);
  }
}

But it seems tremendously difficult to wire this up with Guice. I have tried using the @Assisted annotation on the 1st constructor argument as suggested in this answer. I defined a factory like this:

public interface DAOProviderFactory {
  <T> DAOProvider<T> create(Class<T> daoType);
}

But it's not clear how I should invoke the FactoryModuleBuilder.implemented method as the whole point is that I don't want to have to extend my provider class.

It also seems a bit crazy that I'd have a factory that returns a provider that returns the thing I actually want!

It strikes me that this would be really easy to do with the Spring DI container so I want to believe it's possible with Guice. Can anyone point me in the right direction?


Solution

  • Thanks to @condit for pointing me at something that enabled me to solve the issue. It's actually very simple. I changed the Provider implementation to use field injection for the Handler like this:

    public class DAOProvider<T> implements Provider<T> {
    
      private @Inject Handle handle;
      private final Class<T> daoType;
    
      public DAOProvider(Class<T> daoType) {
        this.daoType = daoType;
      }
    
      @Override public T get() {
        return handle.attach(daoType);
      }
    }
    

    Then in any module or application where I have specific DAO classes I want to bind I can just do something like this:

    bind(UserStore.class).toProvider(new DAOProvider<>(UserStore.class));
    bind(MessageStore.class).toProvider(new DAOProvider<>(MessageStore.class));
    

    Guice then injects the Handle into the DAOProvider instances automatically.