Skip to content
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

ApplicationScoped bean not invokded in @PostConstruct #834

Closed
hantsy opened this issue Jan 17, 2025 · 11 comments
Closed

ApplicationScoped bean not invokded in @PostConstruct #834

hantsy opened this issue Jan 17, 2025 · 11 comments

Comments

@hantsy
Copy link
Contributor

hantsy commented Jan 17, 2025

Describe the bug

I tried to update an EJB singleton to a CDI application. I created an issue on concurrency but it was considered an issue of CDI, see: jakartaee/concurrency#656

The original EJB @Singleton beans with a @Startup annotation.

@Startup
@Singleton // imported from ejb
public class CargoInspectedStub {
    private static final Logger LOGGER = Logger.getLogger(CargoInspectedStub.class.getName());

    @Inject @CargoInspected Event<Cargo> cargoEvent;
    @Resource TimerService timerService;

    @PostConstruct
    public void initialize() {
        LOGGER.log(Level.INFO, "raise CDI event after 5 seconds...");
        timerService.createTimer(5000, "delayed 5 seconds to execute");
    }

    @Timeout
    public void raiseEvent(Timer timer) {
        LOGGER.log(Level.INFO, "raising event: {0}", timer.getInfo());
        cargoEvent.fire(
                new Cargo(
                        new TrackingId("AAA"),
                        new RouteSpecification(
                                SampleLocations.HONGKONG,
                                SampleLocations.NEWYORK,
                                LocalDate.now().plusMonths(6))));
    }
}

The converted CDI bean is like the following.

@ApplicationScoped
public class CargoInspectedSseEventStub {
    private static final Logger LOGGER =
            Logger.getLogger(CargoInspectedSseEventStub.class.getName());

    @Inject @CargoInspected Event<Cargo> cargoEvent;
    @Inject ManagedScheduledExecutorService scheduledExecutorService;

    @PostConstruct
    public void initialize() {
        LOGGER.log(Level.INFO, "raise event after 5 seconds...: {0}", startup);
        scheduledExecutorService.schedule(this::raiseEvent, 5000, TimeUnit.MILLISECONDS);
    }

    private void raiseEvent() {
        Cargo cargoEvent =
                new Cargo(
                        new TrackingId("AAA"),
                        new RouteSpecification(
                                SampleLocations.HONGKONG,
                                SampleLocations.NEWYORK,
                                LocalDate.now().plusMonths(6)));
        LOGGER.log(Level.INFO, "raise event: {0}", cargoEvent);
        this.cargoEvent.fire(cargoEvent);
    }
}

It does not work, there are no exceptions, and no useful information is provided to tell developers what is wrong.

However I changed to observing the CDI Startup event, and it worked.

public void initialize(@Observes Startup startup) {
        LOGGER.log(Level.INFO, "raise event after 5 seconds...: {0}", startup);
        scheduledExecutorService.schedule(this::raiseEvent, 5000, TimeUnit.MILLISECONDS);
    }

Expected behavior
It should work as expected, or explain what is wrong.

@hantsy
Copy link
Contributor Author

hantsy commented Jan 17, 2025

I think this is related to #424

@hantsy
Copy link
Contributor Author

hantsy commented Jan 17, 2025

From the javadoc of PostConstruct, https://jakarta.ee/specifications/platform/9/apidocs/jakarta/annotation/postconstruct

The PostConstruct annotation is used on a method that needs to be executed after dependency injection is done to perform any initialization.

The injected scheduled executor service should be ready for the @PostConstruct method.

@Ladicek
Copy link
Contributor

Ladicek commented Jan 17, 2025

It should work as expected, or explain what is wrong.

There's nothing wrong, you just have to understand that a normal scoped bean is initialized lazily, on the first invocation to any of its methods. You could replace @ApplicationScoped with @jakarta.inject.Singleton, that would be initialized eagerly. Or, as you figured out, add an observer of Startup, which forces eager initialziation (by invoking the observer method at startup). It doesn't matter if you do initialization in the Startup observer or in the @PostConstruct method, both should work.

@manovotn
Copy link
Contributor

Ladislav is right.
Your code is good, you just need to replace the EJB eager init (@Startup) with some CDI equivalent such as the observer.

@hantsy
Copy link
Contributor Author

hantsy commented Jan 18, 2025

@manovotn Omnifaces has an annotation @Eager to initialize bean eagerly.

@Ladicek
Copy link
Contributor

Ladicek commented Jan 20, 2025

We could add something like @Eager (I don't have a better name, to be honest) to CDI, as it seems pretty frequent. Implementation-wise, it would be equivalent to declaring an empty observer of Startup.

@manovotn
Copy link
Contributor

We could add something like @Eager (I don't have a better name, to be honest) to CDI, as it seems pretty frequent. Implementation-wise, it would be equivalent to declaring an empty observer of Startup.

How would you implement this short of bytecode generation?
With OM, the eager init works by being able to resolve the bean - potentially its client proxy - and invoke a method hence triggering bean creation if it was just a proxy.
I don't see how you'd do this equivalently?

@manovotn
Copy link
Contributor

We might be able to implement it differently though.

Gather all the occurrences for this, remember those beans as type/qualifiers and after starting the container, we would have to trigger their creation. This can be basically done by resolving them and, assuming lazy init, we'd need to trigger creation of proxied beans. In Weld terms, this should be doable through one of the APIs we already have.
It is merely a thought, didn't really try to implement it yet :)

If we want that, we should have a dedicated issue for it - this one can IMO be closed as the initial question was answered, right @hantsy?

@hantsy
Copy link
Contributor Author

hantsy commented Jan 20, 2025

We could add something like @Eager (I don't have a better name, to be honest) to CDI, as it seems pretty frequent. Implementation-wise, it would be equivalent to declaring an empty observer of Startup.

Alternatives:

  • @Lazy(true/false) on beans
  • Add a lazy or eager attribute to all existing scopes or NormalScope, to make compatible, lazy by default.

@hantsy
Copy link
Contributor Author

hantsy commented Jan 20, 2025

If we want that, we should have a dedicated issue for it - this one can IMO be closed as the initial question was answered, right @hantsy?

Is there an seperate issue to track this?

@manovotn
Copy link
Contributor

I've created #835, so let's shift the discussion regarding this feature there.

Closing this issue as the original question was answered.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants