1

I'm working on a platform for processing messages, and I'm running into a problem with Guice bindings. I've written a sample project that displays the problem I'm having, which is an exception that looks like this:

1) com.icacarealign.worker_example.PreprocessorMiddleware<MessageType> cannot be used as a key; It is not fully specified.

Here comes the code.

The Interfaces

public interface Middleware {
  void configure();
}

public interface PreprocessorMiddleware<MessageType> extends Middleware {
  void prework(Context<MessageType> contextObj);
}

public interface PostprocessorMiddleware<MessageType> extends Middleware {
  void postwork(Context<MessageType> contextObj);
}

The Guice Binding

public class MiddlewareModule<MessageType> extends AbstractModule {
  private final List<Class<? extends PostprocessorMiddleware<MessageType>>> _postprocessors;
  private final List<Class<? extends PreprocessorMiddleware<MessageType>>> _preprocessors;

  public MiddlewareModule(
    List<Class<? extends PreprocessorMiddleware<MessageType>>> preprocessors,
    List<Class<? extends PostprocessorMiddleware<MessageType>>> postprocessors
  ) {
    _preprocessors = preprocessors;
    _postprocessors = postprocessors;
  }

  @Override
  protected void configure() {
    bindAllMiddleware(_preprocessors, new TypeLiteral<PreprocessorMiddleware<MessageType>>() {});
    bindAllMiddleware(_postprocessors, new TypeLiteral<PostprocessorMiddleware<MessageType>>() {});
  }

  private <T extends Middleware> void bindAllMiddleware(List<Class<? extends T>> middleware, TypeLiteral<T> type) {
    Multibinder<T> multibinder = Multibinder.newSetBinder(binder(), type);

    middleware.forEach(middlewareType -> bindMiddleware(multibinder, middlewareType));
  }

  private <T extends Middleware> void bindMiddleware(Multibinder<T> binder, Class<? extends T> type) {
    binder().bind(type).in(Singleton.class);
    binder.addBinding().to(type);
  }
}

The Main Method

public class Main {
  public static void main(String[] args) {
    List<Class<? extends PreprocessorMiddleware<Message>>> preprocessorMiddlewares = new ArrayList<>();
    List<Class<? extends PostprocessorMiddleware<Message>>> postprocessorMiddlewares = new ArrayList<>();

    preprocessorMiddlewares.add(ArbitraryPrepreprocessorMiddleware.class);
    postprocessorMiddlewares.add(ArbitraryPostprocessorMiddleware.class);

    MiddlewareModule<Message> module = new MiddlewareModule<>(preprocessorMiddlewares, postprocessorMiddlewares);

    Injector injector = Guice.createInjector(module);
  }
}

What am I doing wrong?

Lee Crabtree
  • 1,196
  • 2
  • 12
  • 30

2 Answers2

1

As jacobm describes, you need to work around the generics, and will probably need to pass in the type you're binding. Luckily, you can use Guice's Types.newParameterizedType to create your fully-specified TypeLiteral.

public class MiddlewareModule<MessageType> extends AbstractModule {
  private final Class<MessageType> _clazz;
  private final List<Class<? extends PostprocessorMiddleware<MessageType>>> _postprocessors;
  private final List<Class<? extends PreprocessorMiddleware<MessageType>>> _preprocessors;

  public MiddlewareModule(
    // Accept the message type in a way that survives erasure.
    Class<MessageType> clazz,
    List<Class<? extends PreprocessorMiddleware<MessageType>>> preprocessors,
    List<Class<? extends PostprocessorMiddleware<MessageType>>> postprocessors
  ) {
    _clazz = clazz;
    _preprocessors = preprocessors;
    _postprocessors = postprocessors;
  }

  @Override
  protected void configure() {
    // Use the Class to create your fully-specified TypeLiteral.
    bindAllMiddleware(_preprocessors,
        Types.newParameterizedType(PreprocessorMiddleware.class, _clazz));
    bindAllMiddleware(_postprocessors,
        Types.newParameterizedType(PostprocessorMiddleware.class, _clazz));
  }

  private <T extends Middleware> void bindAllMiddleware(List<Class<? extends T>> middleware, TypeLiteral<T> type) {
    Multibinder<T> multibinder = Multibinder.newSetBinder(binder(), type);

    middleware.forEach(middlewareType -> bindMiddleware(multibinder, middlewareType));
  }

  private <T extends Middleware> void bindMiddleware(Multibinder<T> binder, Class<? extends T> type) {
    bind(type).in(Singleton.class);  // Don't call binder() explicitly.
    binder.addBinding().to(type);
  }
}

Note that this is untested code, since I don't have a full SSCCE; you may need to tweak the type parameters in order to convince Guice that your use of wildcards in generics is safe.

Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251
  • That `ParameterizedType` doesn't match up with the `TypeLiteral` wanted by `bindAllMiddleware`. Is there a way to turn a `ParameterizedType` into a `TypeLiteral`? – Lee Crabtree Jun 12 '18 at 19:35
  • @LeeCrabtree Sorry about that! You can always pass a [Key](https://google.github.io/guice/api-docs/latest/javadoc/com/google/inject/Key.html), and [`Key.get` takes Types](https://google.github.io/guice/api-docs/latest/javadoc/com/google/inject/Key.html#get-java.lang.reflect.Type-). – Jeff Bowman Jun 12 '18 at 20:15
  • Phew. That worked. Resulted in some weird unchecked warnings, but they're hidden away, so I don't think it'll impact anything. Thanks! – Lee Crabtree Jun 12 '18 at 20:55
0

You can't use a type variable (in this case MessageType) in a TypeLiteral for the purpose of Guice bindings. Instead, you need to refactor to pass a TypeToken that is constructed without use of any type variables (probably this means passing it in from the callsite).

Here is a much more involved answer of a similar situation. The upshot is, if you really need to avoid making a type literal at every callsite you can abstract a little more, but it comes at a pretty high complexity cost.

jacobm
  • 13,790
  • 1
  • 25
  • 27