HubSpot / dropwizard-guice

Adds support for Guice to Dropwizard
Apache License 2.0
266 stars 95 forks source link

Binding annotations aren't being consistently honored #91

Open chriskessel opened 8 years ago

chriskessel commented 8 years ago

I tried to file this with Guice, but they won't look at it because I'm using Dropwizard, so maybe this is the right place?

I have two modules, each provides an instance of the same class (a specific Gson configuration). So, each module has a method like so:

@Provides
@ModuleA (this is @ModuleB in the other module)
public Gson provideGson() { ... }

Then I have constructors for classes that use those Gson items, that look like:

@Inject
public FooBar(@ModuleA gson) { ... }

This does NOT work consistently. Sometimes it injects the @ModuleB version of Gson into things annotationed with @ModuleA.

I'm using Dropwizard 0.8.5 with dropwizard-guice 0.8.4 and Guice 4.1.

jhaber commented 8 years ago

Do you have an example I can use to reproduce this?

chriskessel commented 8 years ago

No, I'll have to work on one. And since it's non-deterministic, I'm not sure what part of the complexity of my current setup might be related.

chriskessel commented 8 years ago

Ok, I think I have a stand alone test. I ran this, hit both endpoints:

http://localhost:8080/a/foo http://localhost:8080/b/foo

In my log output, I can see:

INFO  [2016-09-13 19:49:38,180] org.eclipse.jetty.server.Server: Started @3827ms
I'm A and I got: module a
127.0.0.1 - - [13/Sep/2016:19:52:06 +0000] "GET /a/foo HTTP/1.1" 200 - "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36" 70
I'm B and I got: module a
127.0.0.1 - - [13/Sep/2016:19:52:09 +0000] "GET /b/foo HTTP/1.1" 200 - "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36" 5

As you can see, the ModuleBApi got injected with the wrong string despite the binding annotation. Unless I'm doing something wrong with annotations?

I crammed everything into one file, so hopefully this is easy for you to test:

package com.kessel.test;

import com.codahale.metrics.annotation.ExceptionMetered;
import com.codahale.metrics.annotation.Timed;
import com.google.inject.AbstractModule;
import com.google.inject.BindingAnnotation;
import com.google.inject.Provides;
import com.google.inject.Stage;
import com.hubspot.dropwizard.guice.GuiceBundle;
import io.dropwizard.Application;
import io.dropwizard.Configuration;
import io.dropwizard.setup.Bootstrap;
import io.dropwizard.setup.Environment;

import javax.inject.Inject;
import javax.inject.Singleton;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.Response;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

public class TestMain extends Application<TestMain.TestConfig> {

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

    public void initialize(Bootstrap<TestConfig> bootstrap) {
        GuiceBundle.Builder<TestConfig> guiceBundleBuilder = GuiceBundle.newBuilder();
        guiceBundleBuilder.addModule(new GuiceModuleA());
        guiceBundleBuilder.addModule(new GuiceModuleB());
        GuiceBundle<TestConfig> guiceBundle = guiceBundleBuilder.enableAutoConfig(getAutoConfigPackages()).build(Stage.DEVELOPMENT);
        bootstrap.addBundle(guiceBundle);
    }

    protected String[] getAutoConfigPackages() {
        return new String[]{this.getClass().getPackage().getName()};
    }

    public void run(TestConfig config, Environment environment) throws Exception {
    }

    public static class TestConfig extends Configuration {
    }

    public static class GuiceModuleA extends AbstractModule {

        @Override
        protected void configure() {
        }

        @Provides
        @Singleton
        @ModuleA
        public String provideModuleAString() {
            return "module a";
        }

        @BindingAnnotation
        @Target({FIELD, PARAMETER, METHOD})
        @Retention(RUNTIME)
        public @interface ModuleA {
        }
    }

    public static class GuiceModuleB extends AbstractModule {

        @Override
        protected void configure() {
        }

        @Provides
        @Singleton
        @ModuleB
        public String provideModuleString() {
            return "module b";
        }

        @BindingAnnotation
        @Target({FIELD, PARAMETER, METHOD})
        @Retention(RUNTIME)
        public @interface ModuleB {
        }
    }

    @Path("/a")
    public static class ModuleAApi {

        @Inject
        public ModuleAApi(@GuiceModuleA.ModuleA String injectableThing) {
            System.out.println("I'm A and I got: " + injectableThing);
        }

        @GET
        @Timed
        @ExceptionMetered
        @Path("foo")
        public Response foo() {
            return Response.ok().build();
        }
    }

    @Path("/b")
    public static class ModuleBApi {

        @Inject
        public ModuleBApi(@GuiceModuleB.ModuleB String injectableThing) {
            System.out.println("I'm B and I got: " + injectableThing);
        }

        @GET
        @Timed
        @ExceptionMetered
        @Path("foo")
        public Response foo() {
            return Response.ok().build();
        }
    }
}