Contexts and Dependency Injection (CDI) is a powerful Java framework that can occasionally leave even seasoned developers scratching their heads.
Recently, I found myself down in one of those rabbit holes—spending far too many hours debugging why lifecycle annotations like @Observes
, @PostConstruct
, and @PreDestroy
have no effect when using @Producer
methods.
I debugged, searched the web and, because I’m a hopeless optimist, discussed with a LLM[1] for longer than I’d like to admit.
But then I found an explanation on Stack Overflow, which led me on the right track:
[…] However, in case of producer method you have full control, how the object is created, so you can call any needed method by yourself. Take notice, that in producer methods object is usually created using new, so annotated fields are not initialized. […] Gas, Stack Overflow, Oct 17, 2014
So, if you—not the container—create the bean, you are responsible for calling the lifecycle methods? That sounds reasonable.
[…] The application may call bean constructors directly. However, if the application directly instantiates the bean, no parameters are passed to the constructor by the container; the returned object is not bound to any context; no dependencies are injected by the container; and the lifecycle of the new instance is not managed by the container. Ch. 3.5 Bean Constructors, CDI 3.0 Spec
I’m reading this as: “If you call new
on a bean class yourself, the container won’t manage the lifecycle of the instance you created”.
In hindsight, this seems painfully obvious.
The Problematic Code
// this logs +1 foo as expected, because it's called by the container
@Singleton class Foo {
@PostConstruct void onCreate() {
System.out.println("+1 foo");
}
}
// this does not, since we're responsible for the lifecycle
class Foo {
@PostConstruct void onCreate() {
System.out.println("+1 foo");
}
}
@Dependent class FooProducer {
@Produces @Singleton void newFoo() {
return new Foo();
}
}
A Simple Fix
class Foo {...}
@Dependent class FooProducer {
@Produces @Singleton void newFoo() {
var foo = new Foo();
foo.onCreate();
return foo;
}
}
For completeness’s sake: @PreDestroy
would be handled with a disposer method:
class Foo {
// [...]
@PreDestroy void onDispose() {
System.out.println("-1 foo");
}
}
@Dependent class FooProducer {
// [...]
@Disposes void disposeFoo(Foo foo) {
foo.onDispose();
}
}
So today I learned something very… basic? Still, it was pretty hard to find an explanation online—which is why I wrote this post!
-
https://chatgpt.com/share/6747b5e4-4fc8-800a-bbbb-c77eacf78469 ↩
-
If you haven’t at least glanced over it, I really recommend it. I find myself referring to it often. ↩