Search code examples
javadependency-injectionguavaguiceguice-3

Binding a guava supplier using guice


I want do a binding like this,

bind(Supplier<TestClass>).toProvider(Provider<Supplier<TestClass>>).in(Singleton.class);

The provider is returned by an external function so, inside the toProvider(), I call that function and it returns Provider<Supplier<TestClass>>.

The supplier is from guava, the reason to do this kind of thing is, there is a file associated with the TestClass and I need to read that file and assign those values to the respective fields of TestClass.

And that file change at run time, So I need a way to refresh the values stored in TestClass. To-Do that guava supplier I used. Guava supplier has a get method and when that get method is called, if I used memoizeWithExpiration() to create the instance then it checks the TTL value and if it passed, then I can specify a lambda function to read the file and assign values.

So I need to inject Supplier<TestClass> like this

@Inject
Supplier<TestClass> someClassSupplier;

But For doing that binding with Guice is confusing for me.


Solution

  • You can use the following kind of code to do what you want:

    class ServiceModule extends AbstractModule {
      private TestClass readTestClassFromFile() {
        return new TestClass();
      }
    
      // Cache an instance for 5 seconds.
      private final Supplier<TestClass> testClassSupplier = Suppliers.memoizeWithExpiration(this::readTestClassFromFile, 5, SECONDS);
    
      @Provides TestClass provideTestClass() { // Don't declare as singleton
        return testClassSupplier.get();
      }
    }
    

    Then, in your class:

    class Service {
      
      @Inject
      Provider<TestClass> testClassProvider; // Inject the provider, not the instance itself, or any supplier.
      
      void doSomething() throws Exception {
        TestClass a = testClassProvider.get();
        TestClass b = testClassProvider.get();
    
        Thread.sleep(6000); // Sleep for 6 seconds
    
        TestClass c = testClassProvider.get();
    
        System.out.println(a == b); // Prints true
        System.out.println(a == c); // Prints false
      }
    }
    

    You requested for a generic way of doing this, so here it is, check the bindSupplier method:

    import static com.google.common.base.Suppliers.memoizeWithExpiration;
    
    import com.google.inject.*;
    import java.util.concurrent.TimeUnit;
    import java.util.function.Supplier;
    
    public class Main {
    
      public static void main(String[] args) throws Exception {
        Guice.createInjector(new ServiceModule())
            .getInstance(Service.class)
            .doSomething();
      }
    
      static class ServiceModule extends AbstractModule {
        Dependency createDependency() { return new Dependency(); }
    
        // For Java 8+
        private <T> void bindSupplier(Class<T> type, Supplier<? extends T> supplier) {
          // Definitely avoid .in(Singleton.class) because you want the scope to be defined by the Supplier.
          bind(type).toProvider(supplier::get);
        }
    
    // For Java 7 and less
    //    private <T> void bindSupplier(Class<T> type, final Supplier<? extends T> supplier) {
    //      bind(type).toProvider(new Provider<T>() {
    //          @Override public T get() { return supplier.get(); }
    //        });
    //    }
    
        @Override protected void configure() {
          bindSupplier(Dependency.class,
              memoizeWithExpiration(this::createDependency, 3, TimeUnit.SECONDS));
        }
    
      }
    
      static class Dependency {}
    
      static class Service {
    
        @Inject Provider<Dependency> dependencyProvider;
    
        void doSomething() throws InterruptedException {
          Dependency a = dependencyProvider.get();
          Dependency b = dependencyProvider.get();
          Thread.sleep(4000);
          Dependency c = dependencyProvider.get();
          System.out.printf("a == b ? %s%n", a == b); // true
          System.out.printf("a == c ? %s%n", a == c); // false
        }
      }
    }