cdi icon indicating copy to clipboard operation
cdi copied to clipboard

Consider introducing @Eager or similar annotation for bean eager init

Open manovotn opened this issue 11 months ago • 19 comments

This has been discussed and requested repeatedly (last I know of was here). Note that this is not a new feature as the same can be achieved via declaration of Startup event observer in any given bean. However, that is often perceived as a workaround instead of a solution and is admittedly longer than using an annotation.

The idea would be to introduce an annotation such as @Eager/@Startup (name is subject to change), declarable at bean class level, which would make CDI initialize the bean on container startup.

Some thoughts:

  • This makes most sense for @ApplicationScoped (or @Singleton) beans but can technically work for all scopes
    • Should we forbid it for some scopes? Doesn't seem to make much sense for dependent beans for instance.
    • What about contexts that aren't active at startup? We should probably align this with how a startup event observer would behave.
  • Nothing in CDI specification requires impl to behave lazily (not create a bean until its method is invoked) yet this features indirectly assumes it as it would otherwise have no effect. This doesn't prevent us from adding it; just saying :)

FTR, some discussion about implementation took place in the aforementioned issue (here). It boils down to:

  • Gathering information about eager-init beans during discovery
  • Resolving them after container starts (along the lines of Instance#get())
    • Beans without client proxies will be created as soon as they are resolved.
    • Bean with client proxies will need to be "forcefully" initialized.
      • Dumb version is invoking their toString() which would work in 99% cases.
      • Otherwise needs to manually invoke their creation cycle for which some impls already have code anyway (here's Weld bit)

manovotn avatar Jan 20 '25 12:01 manovotn

Agree this doesn't make much sense for other scopes than @Signleton and @ApplicationScoped. For @Dependent, the solution is simple, just create an instance and immediately destroy it; for other scopes, such as @RequestScoped, it's more problematic. I'd say it's probably best to make it a deployment problem if non-singleton beans are @Eager.

Another question: should this be limited to class-based beans? I guess producers and synthetic beans could benefit from this as well.

Ladicek avatar Jan 20 '25 13:01 Ladicek

It would seem odd if we used "eager" to mean "created at startup", rather than "created eagerly". I would expect an "eager" @RequestScoped bean, to be created at the start of every request.

IIRC though, there's no requirement for scopes to have a defined beginning?

Azquelt avatar Jan 20 '25 13:01 Azquelt

My other thought is that, if eager initialization is only going to be used to run something at startup, then observing Startup to run something at startup is far more logical.

The only problem is when this startup logic is also some kind of initialization for your bean. Having it in @PostConstruct guarantees (almost?) that it runs before any other methods on your bean are called, whereas having it in a Startup observer method doesn't. Then you end up putting your initialization logic in a @PostConstruct method and having an empty Startup observer method, which is ugly.

Azquelt avatar Jan 20 '25 14:01 Azquelt

For @Dependent, the solution is simple, just create an instance and immediately destroy it

Sure, but if we are talking an annotation, it doesn't make much sense as you won't execute anything and it will get destroyed immediately? If you want some logic executed and it is a dependent bean, the observer seem like much better fit.

Another question: should this be limited to class-based beans? I guess producers and synthetic beans could benefit from this as well.

Hm, I assume the idea there is to pre-create beans that are time consuming to create on demand? However, in such case we are no longer just making it just more convenient than the OM you can currently use as this adds functionality that you cannot otherwise have.

It would seem odd if we used "eager" to mean "created at startup", rather than "created eagerly". I would expect an "eager" @RequestScoped bean, to be created at the start of every request.

Yes, I thought about that as well but personally I find creating beans on scope/context start very awkward.

IIRC though, there's no requirement for scopes to have a defined beginning?

They are basically defined by firing an initialized event which you can observe. Can't think of anything else.

manovotn avatar Jan 20 '25 15:01 manovotn

My other thought is that, if eager initialization is only going to be used to run something at startup, then observing Startup to run something at startup is far more logical.

The only problem is when this startup logic is also some kind of initialization for your bean. Having it in @PostConstruct guarantees (almost?) that it runs before any other methods on your bean are called, whereas having it in a Startup observer method doesn't. Then you end up putting your initialization logic in a @PostConstruct method and having an empty Startup observer method, which is ugly.

I basically agree. Yet I keep hearing about @Startup from ejb and omnifaces. I suppose the other use case is not having application logic executed but just initializing your bean that's time consuming to create right from the get go? Although I am not sure what would have to be inside that bean to make it that costly...

Either way, I am not sold on this annotation either, I just want to create a tracking issue and see if there is any interest in it 🤷

manovotn avatar Jan 20 '25 15:01 manovotn

Very much in support of this. I come across this repeatedly. People always miss the event observer. A simple annotation would greatly reduce ongoing confusion.

m-reza-rahman avatar Jan 20 '25 19:01 m-reza-rahman

+1 for @Eager as shortcut to @Overserves @Initialized(SomeScope) Object event

tandraschko avatar Jan 20 '25 20:01 tandraschko

I didn't follow up with the spec since while, but if its not there ( specially in the App scope and Singleton, as mentioned in one of the comments) it should be; in many designs we desire to fail early in case of mis-configuration or initialization problems than in th middle of a process that has customers engagement. There is alot to learn from the Servlets specs.

kiswanij avatar Jan 20 '25 20:01 kiswanij

I didn't follow up with the spec since while, but if its not there ( specially in the App scope and Singleton, as mentioned in one of the comments) it should be; in many designs we desire to fail early in case of mis-configuration or initialization problems than in th middle of a process that has customers engagement. There is alot to learn from the Servlets specs.

@kiswanij I am not sure I understand the case you want to cover with this - detection of misconfiguration can be performed without initializing beans. Or, if you need/want to do it this way, it probably means you will have to place it in post construct in which case you might as well just have observer for Startup event?

+1 for @Eager as shortcut to @Overserves @Initialized(SomeScope) Object event

@tandraschko Ok, that's slightly different. The initial comment describes after startup initialization - a shorthand for @Observes Startup essentially. What use case do you have in mind for annotation in combination with context initialization? I mean, in which case is the annotation better than observing that particular context event?

manovotn avatar Jan 21 '25 00:01 manovotn

OmniFaces has one since a decade: https://showcase.omnifaces.org/cdi/Eager. It could be used as an example.

BalusC avatar Jan 21 '25 01:01 BalusC

i have some in my mind:

  • using @Initialized(RequestScoped.class) for tracing requests or similar stuff you would do with servlet listeners
  • using @Initialized(SessionScoped.class) for initializing user-data e.g. after authentication

i know, both are possible with plain e.g. jakarta security or servlet artifacts / events but also via CDI

therefore @Eager would be just more reuseable as @Startup

tandraschko avatar Jan 21 '25 09:01 tandraschko

+1 for @Eager as shortcut to @Overserves @Initialized(SomeScope) Object event

I quite like this because it's general and can be easily generalised to any normal scope which fires an @Initialized event.

However, one of the reasons for introducing the Startup event in #496 was because some runtimes may initialize the application scope at build time. If we're making an "easy" alternative to observing events, we probably wouldn't want an @Eager @ApplicationScoped bean to initialize at build time, so we'd at least need an exception for application scoped beans.

Azquelt avatar Jan 21 '25 10:01 Azquelt

Dumb version is invoking their toString() which would work in 99% cases.

I just want to chime in and say I've seen support tickets where the problem turned out come from calling toString() on CDI beans when writing out trace. So I'd recommend against this. calling equals(null) would be a better dumb solution since the contract for equals() says it returns false if you pass in null; and that's usually implemented by putting a null check with a return statement at the top of the equals method.

Apart from that I'll just note I like @Eager.

benjamin-confino avatar Jan 21 '25 11:01 benjamin-confino

I've seen support tickets where the problem turned out come from calling toString() on CDI beans when writing out trace. So I'd recommend against this. calling equals(null) would be a better dumb solution

Unfortunately the only method declared on Object that is guaranteed to be forwarded to the contextual instance is toString(), see https://jakarta.ee/specifications/cdi/4.1/jakarta-cdi-spec-4.1.html#client_proxy_invocation

Ladicek avatar Jan 21 '25 11:01 Ladicek

Ladislav is right on the money - there is no other method which can be used according to the specification. That being said, impls should have other means to initialize beans so we needn't do this.

manovotn avatar Jan 21 '25 11:01 manovotn

This issue was discussed in the CDI call today (Jan 28, 2025) with the following notes:

  • There are two perspectives for how this feature should work in this issue
    • Annotation should be equivalent to having @Startup observer
    • Annotation should be equivalent to having @Initialized(X.class) observer
  • We need to differentiate what use cases are valid for this annotation versus for having an explicit observer method
    • It is fitting for bean eager initialization if you want to then use the bean within context and, for instance, its init may be costly
      • For @ApplicationScoped and @Singleton beans, the annotation would be equivalent to @Startup observer due to built-time env. where these contexts may start earlier (see the reason why we added @Startup)
      • For other normal built-in scopes, those beans could be initialized on context activation, i.e. same as having @Initialized(X.class) observer
      • Not yet sure how custom scopes would work, but probably the same way as previous bullet
    • It is not meant to replace the observers for when you want to execute a custom application logic (not related to the bean itself)
    • This, among other things, implies that @Eager for @Dependent bean doesn't make sense (due to their lifecycle)
  • @Eager is not a bad name but might be misleading; we should see if we can come up with something that would be more fitting

manovotn avatar Jan 28 '25 15:01 manovotn

For what it is worth, the primary use case I have ever always seen for this sort of thing is always application scoped and singleton beans. That's basically what it is for EJB (here is a good reference for anyone that might find it helpful: https://www.mastertheboss.com/java-ee/ejb-3/how-to-create-an-ejb-startup-service/). I think just limiting to those cases to start with would be just fine. Of course it doesn't mean there aren't use cases I have not seen.

m-reza-rahman avatar Jan 28 '25 15:01 m-reza-rahman

We unfortunately don't have the luxury of adding a @Startup annotation, because we already added the Startup event. Hence, the proliferation of ideas coming with the name @Eager.

If we could come up with a name that is closer to "startup", that might solve the perception problem. I have no idea, honestly.

(Actually, I do. We could establish a convention of @OnXXX, where XXX is a pre-existing event, and define that this annotation present on a class is equivalent to an empty observer of the event being present, and this annotation present on a method is equivalent to this method declaring the @Observes annotation on an extra parameter that is not used anywhere. The binding wouldn't be based on names, of course, there would be something like @Shortcut(XXX.class) on the @OnXXX annotation type. So @OnStartup would have a @Shortcut(Startup.class) and would be a shortcut for an observer of Startup. Way too complex? I thought so.)

Ladicek avatar Jan 28 '25 16:01 Ladicek

  • Annotation should be equivalent to having @Startup observer
  • Annotation should be equivalent to having @Initialized(X.class) observer

IMO the second should be leading. @Startup should be equivalent to having @Initialized(ApplicationScoped.class).

This, among other things, implies that @Eager for @Dependent bean doesn't make sense (due to their lifecycle)

If @Initialized(Dependent.class) is not possible then it can just throw the same error.

@Eager is not a bad name but might be misleading; we should see if we can come up with something that would be more fitting

@AutoInitialized? @EagerInitialized? @EagerlyInitialized?

The name "eager" on OmniFaces @Eager is by the way taken over from old JSF managed bean facility which used <managed-bean eager="true"> resp @ManagedBean(eager=true). Also JPA is using "eager" on its FetchType, in turn taken over from legacy Hibernate. So developers are usually familiar with this term.

BalusC avatar Jan 28 '25 17:01 BalusC