Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

@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.

like image 596
Andremoniy Avatar asked Mar 14 '18 13:03

Andremoniy


People also ask

When @PostConstruct is invoked?

The PostConstruct annotation is used on a method that needs to be executed after dependency injection is done to perform any initialization. This method MUST be invoked before the class is put into service. This annotation MUST be supported on all classes that support dependency injection.

What does @PostConstruct do in spring boot?

When we annotate a method in Spring Bean with @PostConstruct annotation, it gets executed after the spring bean is initialized. We can have only one method annotated with @PostConstruct annotation. This annotation is part of Common Annotations API and it's part of JDK module javax.

When @PostConstruct is called in Java?

Methods marked with the @PostConstruct will be invoked after the bean has been created, dependencies have been injected, all managed properties are set, and before the bean is actually set into scope.

How many times a @PostConstruct method is called?

2. @PostConstruct. Spring calls the methods annotated with @PostConstruct only once, just after the initialization of bean properties.


1 Answers

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":

  • You can use EJB's @javax.ejb.Singleton and use @Startup annotation. This will behave pretty much like @ApplicationScoped would so it might be good enough if you are in EE environment of course.
  • Or you can create a dummy ping() method on your @ApplicationScoped bean and invoke it as soon as your application starts. This will force the creation of the bean hence invoking @PostConstruct - much like you did with test() method in your code sample above.

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.

like image 137
Siliarus Avatar answered Oct 23 '22 04:10

Siliarus