Java Dependency Injection and a Useless Annotation

Posted by & filed under , .

java codeI’ve stepped into the Guice territory rather recently — coming from the Spring framework side of things — and I guess I had so far a similar love/hate experience as with Spring. I rely mostly on the javax.annotation standards anyway so to a certain degree whether it’s Spring or Guice I guess doesn’t make that much difference to me. However, recently I have uncovered a rather big mess: the @PreDestroy annotation — and this seems to be particularly bad with Guice!

I’ve used before @PostConstruct and in one particular case I thought it’s time to bring in its counterpart @PreDestroy. So I’ve set off to write my services to make use of it — and nothing happened! I mean really, my @PreDestroy‘s never got called. Not once. Ever!

So I’ve set off to dig into this and wrote some code around it. (For the impatients amongst you, here’s the Github codebase accompanying this: https://github.com/liviutudor/JavaInjectPreDestroy you can run it and see for yourselves).

I’ve tried 2 cases:

  • Simply using just Google Guice — no other frameworks. Just create one of their injectors and see how @PostConstruct and @PreDestroy behave
  • I’ve thrown in Netflix Governator too — after my findings in the first step — and check the same things.

I got some results … but nothing I expected to be honest! So let’s look at each case individually.

Google Guice Only

Having operated for a while in Netflix Governator environments, I completely didn’t realise that actually Google Guice does NOT support @PostCreate and @PreDestroy — it doesn’t have any lifecycle annotation support at all! I found this shocking, but it’s true.

To prove it (to myself more than anything else) I set up this code (plus some scaffolding around it which you can find in the Github repo): a simple StartApp class which gets injected other “services” classes such as MyService (ingenious name, I know!). Each one of these classes implements a constructor, a finalize() method, a @PostConstruct and a @PreDestroy. All these methods do is just print on the console when they are invoked — so I can:

  • first of all see that they are called
  • secondly, check the order of invocation.

The reason behind it is that @PostConstruct annotation is pretty self-explanatory: it executes after the object got constructed (so after the constructor kicked in and initialized our object). Why is it needed you might ask? Because there are DI frameworks which allow you for instance to mix @Inject annotations with other annotations that only kick in after your instance got created — so in such cases you won’t have access to some of the member data until after your instance has been created. So you need another checkpoint right before you finally start putting the instance created to (good) use. Even the JavaDoc for this annotation sums this up by stating:

This method MUST be invoked before the class is put into service.

So after the object is constructed but before any methods are called.

However, things for @PreDestroy are as clear as mud. The (idiotic) JavaDoc for PreDestroy simply states this:

The PreDestroy annotation is used on methods as a callback notification to signal that the instance is in the process of being removed by the container. The method annotated with PreDestroy is typically used to release resources that it has been holding.

OK, so a callback before destruction — but when is that? Because the second part of it sounds very very closely to finalize()! When does the container drop an instance? Is it at garbage collection time? Before? Because of the garbage collection? Or just triggered by other conditions? No explanation to that but the same JavaDoc goes to enforce this:

This annotation MUST be supported by all container managed objects that support PostConstruct except the application client container in Java EE 5.

OK, so that means if I use @PostConstruct I should be also able to use @PreDestroy — so that’s why (and how!) I set up to write this simple code: on the assumption that once I get @PostConstruct to be called (and I have definitely seen and used this with Guice and Governator) then I should see @PreDestroy kick in too! Well, little did I realise how wrong I was 🙁

Getting back to my simple code, here is my simple StartApp class which is injected all the other instances — nothing fancy as you can see:

package liv.tudor;
 
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.inject.Inject;
 
public class StartApp {
    private final MyService service;
 
    @Inject
    public StartApp(MyService service) {
        System.out.println("StartApp constructor");
        this.service = service;
        System.out.println("StartApp constructor finished");
    }
 
    @PostConstruct
    public void init() {
        System.out.println("StartApp PostConstruct");
    }
 
    public void run() {
        System.out.println("StartApp run start");
        service.doStuff();
        System.out.println("StartApp run end");
    }
 
    @PreDestroy
    public void deregister() {
        System.out.println("StartApp PreDestroy");
    }
 
    @Override
    protected void finalize() throws Throwable {
        System.out.println("StartApp finalize");
    }
}

Then alongside this I’m using this “service” class which looks very similar:

package liv.tudor;
 
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
 
public class MyService {
    public MyService() {
        System.out.println("MyService constructor");
    }
 
    @PostConstruct
    public void init() {
        System.out.println("MyService PostConstruct");
    }
 
    public void doStuff() {
        System.out.println("MyService.doStuff");
    }
 
    @PreDestroy
    public void deregister() {
        System.out.println("MyService PreDestroy");
    }
 
    @Override
    protected void finalize() throws Throwable {
        System.out.println("MyService finalize");
    }
}

To tie it all together I’m using this Java main class:

public class MainDriver {
    public static void log(String message) {
        System.out.println(message);
    }
 
    public static void main(String[] args) {
        try {
            log("Starting program");
 
            log("Starting injection");
            // this is regular Guice
            Injector injector = Guice.createInjector(new PreDestroyTestModule());
            log("Finished injection startup");
 
            log("Retrieving StartApp instance");
            StartApp app = injector.getInstance(StartApp.class);
            log("StartApp instance created");
            app.run();
            log("StartApp finished running");
 
            app = null;
            log("Suggesting GC");
            System.gc();
            log("Suggesting GC finished");
            TimeUnit.SECONDS.sleep(5);
            log("Wait finished");
        } catch (Throwable e) {
            e.printStackTrace();
        }
 
        log("Ending program");
    }
}

As you can see, I’m creating the Guice Injector, requesting my StartApp instance and invoke the main method — then I go and explicitly nullify it, suggest GC (so the object get finalized), and since the GC is just a suggestion, I even give it a few seconds to allow GC to kick in and clean up.

This is what I expected the code to print out:

  • starting up
  • injection startup
  • Retrieving StartApp instance
  • MyService constructor
  • MyService PostConstruct
  • StartApp constructor + finished
  • StartApp PostConstruct
  • Startapp instance created
  • … all the stuff about running the StartApp run() method
  • StartApp PreDestroy
  • StartApp finalize()
  • MyService PreDestroy
  • MyService finalize()

This is based on my assumption that somehow the PreDestroy will be triggered by garbage collection.

Well, it turns out if you run this code you get this instead:

Starting program
Starting injection
Finished injection startup
Retrieving StartApp instance
MyService constructor
StartApp constructor
StartApp constructor finished
StartApp instance created
StartApp run start
MyService.doStuff
StartApp run end
StartApp finished running
Suggesting GC
Suggesting GC finished
MyService finalize
StartApp finalize
Wait finished
Ending program

Look closely here — there is no @PostConstruct or @PreDestroy kicking in! Wait, what???

As it turns out, Google Guice on its own does not provide any lifecycle management support — I found this post from ages ago mentioning it: https://groups.google.com/forum/#!topic/google-guice/0M3molmK4IU ; as a suggestion to the Guice peeps, it would be good if their (otherwise excellent) docco states this clearly too. Since they are referencing the java.annotation package, one is lead to believe they support this too — I definitely was.

OK, so note to all Guice users: unless you add other libraries in your app you do not get any lifecycle support such as @PostCreate and @PreDestroy in your app!

Using Netflix Governator

Having learned that, I have turned my attention back to the Netflix Governator — a framework we use extensively in Netflix (d’er!). And one which I know damn well it has support for @PostConstruct as I have used it myself tons of time!

And based on the above JavaDoc, this means it should support @PreDestroy too, right? So then I should be able to find out when does this damn code kick in.

Let’s keep my StartApp and MyService classes the same and only change the way we create the injector — that’s the beauty of DI, right? I can switch provider with no code changes! — such that we create a Governator lifecycle management-enabled injector. Sadly the Governator wiki is a bit of out date, their example of creating an injector is outdated — I have used instead this code:

Injector injector = InjectorBuilder.fromModule(new PreDestroyTestModule()).createInjector();

(The PreDestroyTestModule is just an empty Google Guice module by the way.)

Now I should see something similar to my expectations above — or if my instances are being dropped by the container for other reasons then I should see these way before the finalize() kicking in. Or perhaps I will see it in when the JVM exits and the DI container cleans up after itself. Either way, I was excited to finally see @PreDestroy in action … and I got this:


Starting program
Starting injection
Finished injection startup
Retrieving StartApp instance
MyService constructor
MyService PostConstruct
StartApp constructor
StartApp constructor finished
StartApp PostConstruct
StartApp instance created
StartApp run start
MyService.doStuff
StartApp run end
StartApp finished running
Suggesting GC
Suggesting GC finished
Wait finished
Ending program

Notice that this looks much better than the previous one: I’m actually seeing @PostConstruct methods kicking in, and it is in the order I expected it, thus confirming my understanding of this annotation. But no sign of @PreDestroy! Nothing! It gets worse — you can in fact play with the above code and do all sorts of things, forcing injector shutdown’s and so on to no avail. It seems there is no @PreDestroy support built in this either.

Conclusion

At this point I give up — I believe the @PreDestroy annotation is somewhere in between a myth and a useless annotation as I don’t think it is supported by that many frameworks (or at least not supported by these 2 major ones). I blame this on Oracle and their poor specification — if you look at the JavaDoc again, there is no requirements as to when this method should be evaluated and as such any platform can claim they are implementing this but just choose to say invoke the @PreDestroy‘s when the JVM shuts down (or in fact any other weird condition that they can think of). The JDK spec does a good job of trying to enforce that both @PostConstruct and @PreDestroy are implemented together but failing to strongly specify when and how should both be implemented renders one of these useless. It’s a half-arsed specification and it looks like the Oracle folks lost the steam by the time they got to @PreDestroy. Sad … and useless!

My take from this is @PreDestroy is a no go — it might get implemented by some DI frameworks but because of this probability anyone using it should be aware that they are going down the vendor-lockin path. Which, again, to my original point, means that the @PreDestroy annotation is now useless.

The (very very simple) code accompanying this post is on Github here: https://github.com/liviutudor/JavaInjectPreDestroy