javajakarta-eecdiweld

@PostConstruct is not invoked for @ApplicationScoped on initialisation?


I have faced with the following issue. I am using Weld implementation of the CDI.

I have found that if a service is annotated with @ApplicationScoped then @PostConstruct section is not invoked until the first usage of the service. Here is a code to reproduce this behaviour:

import org.jboss.weld.environment.se.Weld;
import org.jboss.weld.environment.se.WeldContainer; 

import javax.annotation.PostConstruct;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.spi.CDI;

public class TestCdi {

    public static void main(String[] args) {
        try (WeldContainer weldContainer = new Weld().containerId("test").initialize()) {
             FooService fooService = CDI.current().select(FooService.class).get();

             fooService.test();
             System.out.println("Done");
        }
    }

    @ApplicationScoped
    public static class FooService {

        @PostConstruct
        public void  init() {
            System.out.println("Post construct");
        }

        public void test() {
            System.out.println("test");
        }
    }
}

So, if fooService.test(); is commented, then FooService.init() is not invoked. But remove @ApplicationScoped and it is working again!

This seems strange for me and I can't find and description of such behaviour.

Furthermore, the specification of javax.inject.Provider.get() says that:

Provides a fully-constructed and injected instance of T.

So, what's the issue? Is it designed so or this is a bug? And what is more important for me: how to bypass this issue? I need my service to be @ApplicationScoped.


Solution

  • What you are seeing is Weld's lazy approach to bean initialization. With all normal scoped beans (anything except @Dependent from CDI-provided scopes), you in fact inject a proxy which delegates calls to contextual instance. And until you try to invoke any bean method on that proxy, the contextual instance is not created.

    CDI specification does not mandate beans to be eager or lazy, this is implementation-based choice (I am not sure whether Weld docs mention this now). In case of Weld this is mainly performance choice as many of those beans would be initialized for nothing (never used, for instance) and it would slow down bootstrap a lot.

    Please note that this is not an inconsistent state, it works like this for every scope Weld provides. It is also not a contradiction to javax.inject.Provider.get() as it does not state that @PostConstruct has to be invoked before you get the instance back. Furthermore the instance you in fact get is the proxy instance and that one is fully initialized anyway.

    So it boils to to general problem of lazy versus eager init and which is better and/or which feels more natural.

    As for a "solution":

    As a side note - in your example the @Inject annotation on your constructor is of no use. It is only required for constructors with params.