71

Is it possible to tell Guice to call some method (i.e. init()) after instantinating an object of given type?

I look for functionality similar to @PostConstruct annotation in EJB 3 (and Spring).

Raedwald
  • 46,613
  • 43
  • 151
  • 237
mgamer
  • 13,580
  • 25
  • 87
  • 145
  • 1
    unfortunately, it looks like the authors of Guice have no intention to add @PostConstruct https://github.com/google/guice/issues/62#issuecomment-115452493 , which does strongly limit the applicability of Guice (there are workarounds, but those are quite verbose). You might want to look into some other frameworks, like Spring or JEE CDI (eg, Weld) – arcuri82 Dec 26 '16 at 19:21

8 Answers8

65

You can just add the @Inject annotation to your init() method. It will get run automatically after the object is instantiated.

Simon Nickerson
  • 42,159
  • 20
  • 102
  • 127
  • 16
    The problem is that this approach does not work if you have optional dependencies, because there is no way to tell guice to call your init() method as the last method as far as I know. IMHO they need @PostConstruct support. – bogdan.mustiata Apr 04 '12 at 13:12
  • I am using constructor injection where i have to do some initialization stuff that is depending on other dependencies. – Ortwin Angermeier Nov 04 '14 at 15:27
  • 1
    @OrtwinAngermeier, if I understand you correctly, you can put an `@Inject` annotation on your constructor _and_ on your init method. – Mansoor Siddiqui Oct 02 '15 at 20:12
  • Also not a really good option for abstract methods with @Inject, The TypeListener approach is definitely the way to go to stick with Guice implementations – Marc Magon Jul 02 '18 at 11:43
46

Actually, it is possible.

You need to define a TypeListener to get the functionality going. Something along the lines of the following in your module definition:

bindListener(Matchers.subclassesOf(MyInitClass.class), new TypeListener() {
    @Override
    public <I> void hear(final TypeLiteral<I> typeLiteral, TypeEncounter<I> typeEncounter) {
        typeEncounter.register(new InjectionListener<I>() {
            @Override
            public void afterInjection(Object i) {
                MyInitClass m = (MyInitClass) i;
                m.init();
            }
        });
    }
});
m4tx
  • 4,139
  • 5
  • 37
  • 61
gpampara
  • 11,989
  • 3
  • 27
  • 26
  • 5
    Also an option is to use GuicyFruit, which claims to support @PostConstruct (see http://code.google.com/p/guiceyfruit/), and while it doesn't answer this question, I think it is worth mentioning that if you (solely) use constructor injection, you don't need such functionality as you can do all initialization in the constructor. – Eelco Feb 28 '10 at 09:19
  • saved my day. @PostConstruct is not supported by guiceyfruit yet – Boris Pavlović Mar 10 '10 at 09:41
  • 10
    Matchers.subclassesOf(MyInitClass.class) will actually cause a compile-time error: "The method bindListener(Matcher super TypeLiteral>>, TypeListener) in the type AbstractModule is not applicable for the arguments (Matcher, new TypeListener(){})" I think you'll have to extend AbstractMatcher to make your code work – Andrey Feb 28 '12 at 16:25
  • 7
    Agreed, this example contains a compilation error. The following blog post describes how to bind listeners in excellent detail: http://developer.vz.net/2012/02/08/extending-guice-2/ – pestrella Oct 02 '12 at 16:19
  • Here you can download GuiceSubclassMatcher utility class to make it work: http://moi.vonos.net/java/guice-listeners/ – Daniel Hári Apr 08 '16 at 20:21
  • 1
    Or you can simple change to Matchers.any(), and you check type before calling init(). – Daniel Hári Apr 08 '16 at 20:32
  • Can we do similar thing for shutdown() – user2677679 Jun 25 '20 at 07:16
9

guiceyfruit does what you're after for methods annotated with @PostConstruct or implementing spring's InitializingBean. It's also possible to write your own listeners to do this. Here's an example that calls a public init() method after objects are created.

import com.google.inject.*;
import com.google.inject.matcher.*;
import com.google.inject.spi.*;

public class MyModule extends AbstractModule {
  static class HasInitMethod extends AbstractMatcher<TypeLiteral<?>> {
    public boolean matches(TypeLiteral<?> tpe) {
      try {
        return tpe.getRawType().getMethod("init") != null;
      } catch (Exception e) {
        return false;
      }
    }

    public static final HasInitMethod INSTANCE = new HasInitMethod();
  }

  static class InitInvoker implements InjectionListener {
    public void afterInjection(Object injectee) {
      try {
        injectee.getClass().getMethod("init").invoke(injectee);
      } catch (Exception e) {
        /* do something to handle errors here */
      }
    }
    public static final InitInvoker INSTANCE = new InitInvoker();
  }

  public void configure() {
    bindListener(HasInitMethod.INSTANCE, new TypeListener() {
      public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) {
        encounter.register(InitInvoker.INSTANCE);
      }
    });
  }
}
kapex
  • 28,903
  • 6
  • 107
  • 121
Geoff Reedy
  • 34,891
  • 3
  • 56
  • 79
  • Thanks for nice example, just wondering if getMethod("") ever returns null because when it does not find specified method it throws NoSuchMethodException and javadoc doesn't comment on this either. – zeratul021 Nov 21 '10 at 02:12
7

I like http://code.google.com/p/mycila/wiki/MycilaGuice. This supports Guice 3, other than http://code.google.com/p/guiceyfruit.

Christian Ullenboom
  • 1,388
  • 3
  • 24
  • 20
  • Note mycila-guice 3.6 works only with Guice 4.0 but not 4.1, yet; see https://github.com/mycila/guice/issues/11. – vorburger Sep 02 '16 at 14:39
2

If you'd like to call a method after the construction of an instance, it means the post-construct method call is actually a step of the instance creation. In this case, I would recommend abstract factory design pattern to solve this problem. The code may look like something like this:


class A {
    public A(Dependency1 d1, Dependency2 d2) {...}

    public postConstruct(RuntimeDependency dr) {...}
}

interface AFactory {
    A getInstance(RuntimeDependency dr);
}

class AFactoryImpl implements AFactory {
    @Inject
    public AFactoryImpl(Dependency1 d1, Dependency2 d2) {...}

    A getInstance(RuntimeDependency dr) {
        A a = new A(d1, d2);
        a. postConstruct(dr);
        return a;
    }
}

// in guice module
bind(AFactory.class).to(AFactoryImpl.class)
Russell Bie
  • 341
  • 2
  • 11
1

GWizard includes a module (gwizard-services) which provides Guava services in a Guice-friendly format. Guava services give you lifecycle management in parallel threads.

https://github.com/stickfigure/gwizard

stickfigure
  • 13,458
  • 5
  • 34
  • 50
0

In case you need to initialize an object using other objects and after both are ready (which is the case if you need to register one with the other and they also depend on each other) you can easily do it like this:

public final class ApplicationModule extends AbstractModule {

  @Override
  protected void configure() {
    requestStaticInjection(ApplicationModule.class);
  }

  @Inject
  static void injectApplication(
      ReslSession reslSession,
      Set<Saga> sagas,
      Set<Reaction> reactions
  ) {
    sagas.forEach(reslSession::registerSaga);
    reactions.forEach(reslSession::registerReaction);
  }

}
kboom
  • 2,279
  • 3
  • 28
  • 43
-1

Based on Geoff's answer you can "make callable" @PostConstruct method:

public class GuiceExample {
    @Inject
    private IDataManager dataManager;

    public GuiceExample() {
        System.out.println("Constructor");
    }

    @PostConstruct
    private void init() {
        dataManager.printData();
    }

    public static void main(String[] args) {
        Injector injector = Guice.createInjector(new AbstractModule() {

            @Override
            protected void configure() {
                bind(IDataManager.class).to(DataManager.class);
                bindListener(HasPostConstructAnnotationMatcher.INSTANCE, new TypeListener() {

                    @Override
                    public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) {
                        encounter.register(PostConstructAnnotationInvoker.INSTANCE);
                    }
                });
            }
        });

        GuiceExample example = injector.getInstance(GuiceExample.class);
    }

    private static class HasPostConstructAnnotationMatcher extends AbstractMatcher<TypeLiteral<?>> {
        private static final HasPostConstructAnnotationMatcher INSTANCE = new HasPostConstructAnnotationMatcher();

        @Override
        public boolean matches(TypeLiteral<?> t) {
            return Arrays.stream(t.getRawType().getDeclaredMethods()).anyMatch(GuiceExample::hasPostConstructAnnotation);
        }

    }

    private static boolean hasPostConstructAnnotation(Method method) {
        Annotation[] declaredAnnotations = method.getAnnotations();
        return Arrays.stream(declaredAnnotations).anyMatch(a -> a.annotationType().equals(PostConstruct.class));
    }

    private static class PostConstructAnnotationInvoker implements InjectionListener<Object> {
        private static final PostConstructAnnotationInvoker INSTANCE = new PostConstructAnnotationInvoker();

        @Override
        public void afterInjection(Object injectee) {
            //@formatter:off
            Arrays.stream(injectee.getClass().getDeclaredMethods())
            .filter(GuiceExample::hasPostConstructAnnotation)
            .forEach(m -> {
                try {
                    m.setAccessible(true);
                    m.invoke(injectee);
                } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
                    e.printStackTrace();
                }
            });
            //@formatter:on
        }

    }

    public static interface IDataManager {
        void printData();
    }

    public static class DataManager implements IDataManager {

        @Override
        public void printData() {
            System.out.println("I print data.");
        }

    }
}

Also, you can have multiple @PostConstruct method but you will not know in which order they are going to be invoked:

@PostConstruct
private void init() {
    dataManager.printData();
}

@PostConstruct
private void init2() {
    System.out.println("Other init method");
}
George Z.
  • 6,643
  • 4
  • 27
  • 47