Description
Edit: This is an edit to the summary as a TLDR on what this enhancement request has become.
The problem is specific to @Factory
components that have methods that create @Bean
components, and wanting to have a @PreDestroy
method invoked on those beans.
The use case is that using Vertx the desired PreDestroy is actually a chained method invocation of vertx.close().blockingAwait();
.
Change 1 - Change @Bean(destroyMethod)
to support method chaining
So for example @Bean(destroyMethod="close().blockingAwait()")
now works. This change was added via #754
Change 2 - Allow Factories to have a @PreDestroy
method that matches a @Bean
method
Factories naturally have @Bean
methods to create components and it seems natural to allow those factories to have a @PreDestroy
method that would match, for example:
@Factory
final class MyFactory {
@Bean
Vertx vertx() {
var opts = new VertexOptions().setUseDaemonThread(true);
return Vertx.vertx(opts);
}
@PreDestroy
void destroy(Vertx vertx) {
vertx.close().blockingAwait();
}
}
This is supported via #756
Also changing the title to reflect that this issue relates specifically to factories only. Normal @Singleton
components can and should have their own @PreDestroy
methods and shutdown logic (and should not have an extra dependencies required to execute the shutdown logic - aka normal @PreDestroy
methods are not allowed to have arguments).
First off, thanks for the awesome annotation processor library! This has slotted really nicely into some work I am doing to make some components much easier to manage without the overhead and hassle of a full runtime CDI framework like Spring!
I have a small issue and I am wondering if a feature could be introduced to help address it.
Background
In my application, I am making use of Vert.x RXJava3 components. I have some configuration resembling the following:
import io.avaje.inject.Bean;
import io.avaje.inject.Factory;
import io.vertx.core.VertxOptions;
import io.vertx.rxjava3.core.Vertx;
@Factory
public class MyContext {
@Bean
public Vertx vertx() {
var opts = new VertexOptions().setUseDaemonThread(true);
return Vertx.vertx(opts);
}
}
My issue revolves around the ability to safely destroy the vertx bean so that the threadpool is closed prior to the BeanScope being fully closed. This is useful as it means I do not have to worry about Vertx retaining ports in the background between integration test cases.
Since this is a reactive shim around "regular" Vert.x, methods for object closure are non-blocking, as one may expect.
This means that the following or something similar has to be invoked to shut down my event loop:
vertx.close().blockingAwait();
Unfortunately, I cannot find a "nice" way of handling this with Avaje. In Spring, I'd usually "abuse" the cglib method proxying to allow referencing the bean within the component, like so:
@Configuration
public class MyConfiguration {
@Bean
public Vertx vertx() { ... }
@PreDestroy
public void close() {
// calls to the bean are intercepted by spring and injected instead.
vertx().close().blockingAwait();
}
}
In Avaje, as expected, this will not work as cglib proxies that modify method call behaviours are not used.
Feature request
What I'd like to be able to say is something like this:
import io.avaje.inject.Bean;
import io.avaje.inject.Factory;
import io.vertx.core.VertxOptions;
import io.vertx.rxjava3.core.Vertx;
@Factory
public class MyContext {
@Bean
public Vertx vertx() {
var opts = new VertexOptions().setUseDaemonThread(true);
return Vertx.vertx(opts);
}
@PreDestroy
public void destroy(Vertx vertx) {
vertx.close().blockingAwait();
}
}
i.e. where dependencies can be injected into the signature of @PreDestroy
hooks.
What I have tried
I have tried some workarounds:
Storing Vertx in a field
@Factory
public class MyContext {
private final Vertx vertx;
public MyContext() {
var opts = new VertexOptions().setUseDaemonThread(true);
vertx = Vertx.vertx(opts);
}
@Bean
public Vertx vertx() {
return vertx;
}
@PreDestroy
public void destroy(Vertx vertx) {
vertx.close().blockingAwait();
}
}
This works but feels like it violates the purpose of factory methods.
Registering destroy hooks with the BeanScope
The BeanScope itself does not let me add hooks by the looks of things, so I instead tried this:
@Factory
public class MyContext {
@Bean
public Vertx vertx(BeanScopeBuilder beanScopeBuilder) {
var opts = new VertexOptions().setUseDaemonThread(true);
var vertx = Vertx.vertx(opts);
beanScopeBuilder.addPreDestroy(() -> vertx.close().blockingAwait());
return vertx;
}
}
...however, the BeanScopeBuilder does not appear to be able to inject itself, so I get a NullPointerException.
Registering a sibling bean
@Factory
public class MyContext {
@Bean
public Vertx vertx() {
var opts = new VertexOptions().setUseDaemonThread(true);
return Vertx.vertx(opts);
}
@Bean
public Object vertxReaper(Vertx vertx) {
return new Object() {
@PreDestroy
public void destroy() {
vertx.close().blockingAwait();
}
};
}
}
...this never appeared to be invoked.
Registering a singleton
@Factory
public class MyContext {
@Bean
public Vertx vertx() {
var opts = new VertexOptions().setUseDaemonThread(true);
return Vertx.vertx(opts);
}
}
@Singleton
public class VertxReaper {
private final Vertx vertx;
@Inject
public VertxReaper(Vertx vertx) {
this.vertx = vertx;
}
@PreDestroy
public void destroy() {
vertx.close().blockingAwait();
}
}
This works, but feels very clunky and verbose.
Hacking around with destroyMethod
hooks on the @Bean
annotation
@Factory
public class MyContext {
@Bean(destroyMethod="close().blockingAwait")
public Vertx vertx() {
var opts = new VertexOptions().setUseDaemonThread(true);
return Vertx.vertx(opts);
}
}
This does not work (and would be a fragile hack regardless).
Closing Vertx outside the bean scope
public class Main {
public static void main(String[] args) {
try (var ctx = BeanScope.builder().modules(new Mymodule()).build()) {
try {
ctx.get(ThingThatRuns.class).await();
} finally {
ctx.get(Vertx.class).close().blockingAwait();
}
}
}
}
This works but means my other classes now have to be called from outside the CDI context, which feels like it is creating fragile cross-cutting concerns.
If you any suggestions on this, it would be greatly appreciated!
Thanks