Cannot create injector for Guava Service Manager in Dropwizard application



I want to use Guava’s Service Manager to manage services in my Dropwizard application.

ServiceManagerProvider provides the service manager:

@Singleton
public class ServiceManagerProvider implements Provider<ServiceManager> {
    @Inject
    Set<Service> services;

    public static Multibinder<Service> serviceRegistry(Binder binder) {
        return Multibinder.newSetBinder(binder, Service.class);
    }

    @Override
    public ServiceManager get() {
        return new ServiceManager(services);
    }
}

ManagedGuavaServices is a Managed object that interacts with the Service Manager to start/stop services:

public class ManagedGuavaServices implements Managed {
    @Inject
    private ServiceManager _serviceManager;

    @Inject
    public ManagedGuavaServices(ServiceManager serviceManager) {
        _serviceManager = serviceManager;
    }

    @Override
    public void start() throws Exception {
        _serviceManager.startAsync();
    }

    @Override
    public void stop() throws Exception {
        _serviceManager.stopAsync();
    }
}

MyModule is the module where the Guice bindings are specified:

public class MyModule extends DropwizardAwareModule<MyConfig> {
  ...
  @Override
    protected void configure() {
      bind(ServiceManager.class).toProvider(ServiceManagerProvider.class);
      bind(Managed.class).to(ManagedGuavaServices.class).in(Singleton.class);
    }
}

And MyApplication is the Dropwizard application that depends on MyModule:

public class MyApplication extends Application<MyConfig> {
  @Inject
  private Managed managedServices;

  ...

  @Override
  public void initialize(Bootstrap<MyConfig> bootstrap) {
    bootstrap.addBundle(GuiceBundle.builder()
                .printDiagnosticInfo()
                .printGuiceBindings()
                .enableAutoConfig(getClass().getPackage().getName())
                .modules(
                        new MyModule()
                )
                .build());
  }

  ...

  @Override
  public void run(MyConfig config, Environment environment) {
    environment.lifecycle().manage(managedServices);
  }
}

It seems like everything has been wired together, but when I run the application, I get the error message:

java.lang.NullPointerException
    at java.base/java.util.Objects.requireNonNull(Objects.java:221)
    at io.dropwizard.lifecycle.setup.LifecycleEnvironment.manage(LifecycleEnvironment.java:45)
    at com.example.MyApplication.run(MyApplication.java:188)
    at com.example.MyApplication.run(MyApplication.java:60)
    at io.dropwizard.cli.EnvironmentCommand.run(EnvironmentCommand.java:44)
    at io.dropwizard.cli.ConfiguredCommand.run(ConfiguredCommand.java:87)
    at io.dropwizard.cli.Cli.run(Cli.java:78)
    at io.dropwizard.Application.run(Application.java:94)
    at com.example.MyApplication.main(MyApplication.java:70)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at com.webobjects._bootstrap.WOBootstrap.main(WOBootstrap.java:118)

which essentially means that the injected managedServices in MZPaymentApplication is null when the application is run.

What is wrong here? Is it the same problem as in this SO question? If so, where should the @PostConstruct be?

Answer

The problem is in managedServices being field in Application and not injectable, since app object is not configured by Guice (usually in Dropwizard it is created via new keyword). So you should store your GuiceBundle instance in initialize method as field and then access any Guice binding in run method with guiceBundle.getInjector().getInstance(), e.g.:

public class MyApplication extends Application<MyConfig> {

  private GuiceBundle<MyConfig> guiceBundle;

  public static void main(String[] args) throws Exception {
    new MyApplication().run(args);
  }

  @Override
  public void initialize(Bootstrap<MyConfig> bootstrap) {

    guiceBundle = GuiceBundle.builder()
                .printDiagnosticInfo()
                .printGuiceBindings()
                .enableAutoConfig(getClass().getPackage().getName())
                .modules(
                        new MyModule()
                )
                .build();

    bootstrap.addBundle(guiceBundle);
  }

  @Override
  public void run(MyConfig config, Environment environment) throws Exception {
    environment.lifecycle().manage(guiceBundle.getInjector().getInstance(Managed.class));
  }
}


Source: stackoverflow