-
Notifications
You must be signed in to change notification settings - Fork 25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature request: Allow factory beans to have a @PreDestroy method to match/mirror a @Bean method #749
Comments
I like it, when I get back I'll add it. In the mean time, have you tried registering a sibling bean of type Could maybe be done like. @Factory
public class MyContext {
@Bean
public Vertx vertx() {
var opts = new VertexOptions().setUseDaemonThread(true);
return Vertx.vertx(opts);
}
@Bean
public AutoCloseable vertxReaper(Vertx vertx) {
return ()-> vertx.close().blockingAwait();
}
} |
I didn't try that but will give it a go. Thanks! |
This could be made to work easily enough.
Specifying a destroyMethod as In this way we can allow chained method invocations like @bean(destroyMethod="close().blockingAwait()") ... and really that isn't conceptually different to the current direct methods supported. It's just a chained method invocation instead.
The issue I see with this enhancement is this is that as soon as we can go The enhancement might be a good idea but it isn't clear to me yet. |
While it could work, what if you need to pass parameters to the closure method, such as a scheduler to a subscribe call? It would also be fragile if you treated it as pure executable code rather than a method name, since you could have cases like this: class Foo {
int bar;
public int bar() { return bar; }
}
@Factory
class Baz {
@Bean(destroyMethod = "bar")
Foo foo() { return new Foo(); }
} ...you'd now have ambiguity between the field and the method without further semantic analysis. Another case I thought of is that you technically leak encapsulation by doing this too. @Bean(destroyMethod = "this.somethingElse()")
public Xxx yyy() { ... } where I think being able to pass params to close hooks would align with other frameworks, and in a less hacky way. That's be my ideal solution here. Although it could be possible to do both, I guess? |
There is effectively a race condition being created wrt the params [the state of the params when the destroy method is invoked]. So as I see it passing params in this fashion is actually the more implicit and "hacky" and prone to error in that people are not actually taking control the dependencies required in order to shutdown something. Being explicit for these cases is what we need to do today and I think that is good. Perhaps you could be more explicit with the use case. Give us a practical example?
You can't do that - it is a limitation [and I'm suggesting that is a good limitation], instead developers with shutdown logic that have dependencies need to own that explicitly [just as they need to today]. Note that the
Via
That doesn't make sense to me? You don't and should not have access to the BeanScopeBuilder in a PreDestroy method. I don't follow this part. |
No ambiguity. PreDestory methods are method invocations only. |
Would this not require more complicated validation logic in this case then? Otherwise it results in garbage being passed into the code generator... that is the point I was trying to make.
So suppose you have a type like so, from a third party library outside your control. class MessageConsumer {
...
void close(boolean awaitThreadTermination) { ... }
} How would you handle this case? |
People can put garbage into the String of the @bean(destroyMethod = "... garbage ...") ... and the source code generated will contain
2 special cases here in this example in that A) it has a parameter and B) it is a "Message Consumers" and should be in a specific place in overall A) Write the code that determines the value of B) Overall graceful shutdown ordering should be:
|
So in the case that the MessageConsumer threads are in an ExecutorService, you'd call |
I always thought this was the hacky way of doing things, as you're basically coding without an ide. The problem I find with generating un-compilable code is that it can cause problems for any apt that reads it in subsequent apt rounds. Annotation processor problems tend to cascade, which can hide the real issue that caused compilation to fail. Trying to diagnose processor problems due to improperly generated code has been really annoying for me personally, so I'm not exactly a fan of it anymore. |
Hacky in a sense yes but it has the advantage of no ambigiuty in what its trying to do.
This has the advantage that it has a 1 to 1 relationship with the bean being created so there is no ambiguity in the concept of what it does / what it is trying to achieve / why it is there [and Spring and Micronaut do this exact same thing so its expected]. The only way this messes up is the stringy conversion to source code creating a compile error ... but imo this is the "best sort of failure" in that it simply will not work. When we say, add a feature to say allow a method on a factory to be marked as |
I understood the request to have a feature of pre-destroy in general, not specific to factories.
at least for spring you seem to be able to do stuff like this: @PreDestroy
public void close() {
vertx().close().blockingAwait();
} Which to me is even less intuitive than what @ascopes seems to be proposing
Any kind, it'll work like how method injection happens today. (or see that other PR I made for postConstruct)
As many as one needs
It'll generate in a similar way as in method injection.
I never really envisioned this as a factory exclusive feature. In fact it can potentially be useful for
what? |
For example: `@Bean(destroyMethod = "reaper().stop()")`
So going back to the original enhancement request ...
import io.avaje.inject.Bean;
import io.avaje.inject.Factory;
import io.vertx.core.VertxOptions;
import io.vertx.rxjava3.core.Vertx;
@Factory
final class MyContext {
@Bean
Vertx vertx() {
var opts = new VertexOptions().setUseDaemonThread(true);
return Vertx.vertx(opts);
}
@PreDestroy
void destroy(Vertx vertx) {
vertx.close().blockingAwait();
}
} This makes sense to me as long as we remove the ambiguous cases. Along the lines:
This is not as I understand it @SentryMan. To me, what you are suggesting seems too far away from the spec and concept of Edit: I'm going to suggest that this feature must ONLY relate to factories because any other components can and should have their own |
I've always used For me, the main reason to allow a beanscope in
With this constraint, I find the feature much less interesting. For factories, I believe the same can essentially be accomplished with even more potency using: @Bean
public AutoCloseable vertxReaper(Vertx vertx) {
return () -> vertx.close().blockingAwait();
} |
For me, I don't think of them as symmetrical because PreDestroy methods are destructive. The ideal is for components to know how to shutdown/close themselves and then just invoke those in the correct order. The ideal is for shutdown to be as simple as possible and to keep it as simple as possible. We don't really want to see shutdown logic that depends on other components that themselves have shutdown logic etc.
I initially thought that too but when I looked at it there is a relatively subtle issue that without qualifiers it breaks once we want more than 1 AutoClosable and to me I think it will break in a confusing way. Only 1 AutoClosable will be wired and only 1 AutoClosable will actually run (unless we use qualifiers). There might be another way to look at it but currently I think for that to be an option an |
Is it appropriate for Avaje to dictate this where the shutdown logic is user-provided, or is it down to the user to ensure their shutdown logic is sensibly ordered and maintainable? I appreciate that special cases should not be special enough to break the rules, but at the same time I am not sure if the concern of application logic outside dependency injection is something that Avaje should be attempting to control. It makes assumptions about the codebases and libraries outside Avaje that are being used being implemented in an "ideal" way, which can result in the user being penalized for something that is not in their control either, arguably (if no alternatives exist, anyway). What do other CDI frameworks do for this? I have experience with Spring and a small amount of experience with Guice (but only for Maven Plugins). |
Assuming I understand the question correctly - No, we want user logic to control interesting stuff like that. I gave that example just to point out why we don't want avaje-inject allows via avaje-inject uses the [non-standard] That's it, the rest is up to the developer.
Avaje-Inject has the non-standard Not sure if that answered all of your question? |
For what purpose? It can all be handled via the one. @Factory
public class SomeFactory {
@Bean
public TrickyClose c1() {
//return something;
}
@Bean
public TrickyClose2 c2() {
//return something;
}
@Bean
public AutoCloseable reaper(TrickyClose c1, TrickyClose2 c2) {
return () -> {
// close them both
};
}
} |
…@bean(destroyMethod) Example: ``` @factory final class MyFactory { @bean MyComponent myComponent() { ... } @PreDestroy(priority = 3000) void destroyMyComponent(MyComponent bean) { ... } ``` - ONLY valid on factory components - An alternative to @bean(destroyMethod) - The @PreDestroy method matches to a @bean by type only (ignores qualifiers, only 1 argument to the destroy method)
One across ALL factories though [as in 1 AutoClosable in the BeanScope]. I'm not sure it's ideal for the case where they have different priorities per se? Hmmm. |
To clarify, I don't see the AutoCloseable closure approach working due to the subtle issue around "1 AutoClosable in the BeanScope / needs qualifiers etc". I'm comfortable that we can merge/include #756 and that it is intuitive enough that the ambiguities around possible multiple PreDestroy can be managed. |
…@bean(destroyMethod) (#756) Example: ``` @factory final class MyFactory { @bean MyComponent myComponent() { ... } @PreDestroy(priority = 3000) void destroyMyComponent(MyComponent bean) { ... } ``` - ONLY valid on factory components - An alternative to @bean(destroyMethod) - The @PreDestroy method matches to a @bean by type only (ignores qualifiers, only 1 argument to the destroy method)
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 chainingSo for example
@Bean(destroyMethod="close().blockingAwait()")
now works. This change was added via #754Change 2 - Allow Factories to have a
@PreDestroy
method that matches a@Bean
methodFactories 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: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:
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:
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:
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:
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
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:
...however, the BeanScopeBuilder does not appear to be able to inject itself, so I get a NullPointerException.
Registering a sibling bean
...this never appeared to be invoked.
Registering a singleton
This works, but feels very clunky and verbose.
Hacking around with
destroyMethod
hooks on the@Bean
annotationThis does not work (and would be a fragile hack regardless).
Closing Vertx outside the bean scope
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
The text was updated successfully, but these errors were encountered: