Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Guice TypeListener not notified about the injected class Type

Tags:

java

guice

I have the following Guice module:

class MyModule extends AbstractModule {

    @Override
    protected void configure() {
        bindListener(Matchers.any(), new TypeListener() {...});    
    }

    @Provides
    SomeClass createSomeClass(final Parameter param) {
        log(param.getValue()); <-- this gets logged
        ...
    }
}

What I found strange is that my TypeListener doesn't get notified about the Parameter type. Even though the provider is beign called and returns SomeClass. I also see the log statement so clearly the Parameter was injected by Guice.

@Override
protected void configure() {
    bind(Parameter.class);
    bindListener(Matchers.any(), new TypeListener() {...});    
}

I'm aware of Untargetted bindings and the statement:

An untargetted binding informs the injector about a type, so it may prepare dependencies eagerly.

I would still expect Guice to call the TypeListener for any type which is either explicitly binded or injected for the first time.

So do I need to make untargetted binding for such classes as a rule of thumb?

NOTE: marking the Parameter constructor with @Inject doesn't solve the problem.

EDIT:

The complete example (hope I din't leave too much garbage) is as follows:

public class TestGuice {

    public static void main(String[] args) {
        Injector parentInjector = Guice.createInjector(new ParentModule());
        Injector childInjector = parentInjector.createChildInjector(new SubModule());

        childInjector.getInstance(Runnable.class).run();
    }

    static class ParentModule extends AbstractModule {

        @Override
        protected void configure() {

        }
    }

    static class SubModule extends  AbstractModule {

        @Override
        protected void configure() {
            bind(SampleInjectedClass.class); // <-- Comment/uncomment here

            final TypeListener typeListener = new TypeListener() {
                public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) {
                    System.out.println("Type: " + type.getRawType());
                }
            };

            bindListener(Matchers.any(), typeListener);
        }

        @Provides
        Runnable createRunnable(final SampleInjectedClass sampleClass) {
            return new Runnable() {

                @Override
                public void run() {
                    sampleClass.test();
                }
            };
        }
    }

    static class SampleInjectedClass {
        public void test() {
            System.out.println("Test");
        }
    }
}

If the line is present the output is:

Type: class com.barcap.test.TestGuice$SampleInjectedClass

Type: class com.google.inject.internal.ProviderMethod

Test

If I remove the line I get:

Type: class com.google.inject.internal.ProviderMethod

Test

I noticed that if the injector wasn't created via the createChildInjector code the bind(...) is not necessary.

like image 533
Jan Zyka Avatar asked Oct 25 '13 10:10

Jan Zyka


People also ask

What does @inject do Guice?

Note that the only Guice-specific code in the above is the @Inject annotation. This annotation marks an injection point. Guice will attempt to reconcile the dependencies implied by the annotated constructor, method, or field.

How do you inject a Guice class?

The implementation is very easy to understand. We need to create Injector object using Guice class createInjector() method where we pass our injector class implementation object. Then we use injector to initialize our consumer class. If we run above class, it will produce following output.

What is @named annotation in Guice?

Guice comes with a built-in binding annotation @Named that takes a string: public class RealBillingService implements BillingService { @Inject public RealBillingService(@Named("Checkout") CreditCardProcessor processor, TransactionLog transactionLog) { ... }

How does Guice dependency injection work?

Guice figures out how to give you an Emailer based on the type. If it's a simple object, it'll instantiate it and pass it in. If it has dependencies, it will resolve those dependencies, pass them into it's constructor, then pass the resulting object into your object.


1 Answers

Just-in-time bindings created for child injectors will be created in an ancestor injector whenever possible. This means that, without the bind(SampleInjectedClass.class); line, the binding for SampleInjectedClass is created in the parent injector. Since the parent injector doesn't have your TypeListener, it won't be triggered.

like image 185
Tavian Barnes Avatar answered Sep 19 '22 20:09

Tavian Barnes