Search code examples
javadockerpayara-micro

Payara 5 - Method with [observer @Initialized(ApplicationScoped.class) ServletContext init] is not invoked inside Jar files


I was trying to migrate from payara/micro:4.181 to payara/micro:5.2022.3 and I noticed that the initialization method with observer [@initialized(ApplicationScoped.class) ServletContext init] is not invoked inside Jar files.

public void init(@Observes @Initialized(ApplicationScoped.class) ServletContext init)

It is invoked correctly when using payara/micro:4.181 though.

To reproduce the described behaviour:

  1. download the attached reproduce_observer_issue.zip from github_link_to_illustration_files
  2. unzip the files into "current_dir" the unzipped files contains a Dockerfile with following content:
#FROM payara/micro:5.2022.3
FROM payara/micro:4.181
COPY app.war $DEPLOY_DIR

uncomment the line corresponding to the version of payara/micro you want to run the app with.

  1. Run the following docker commands to deploy the app :
  • docker build -t repissue:v1 .
  • docker run repissue:v1
  1. If you check the path "current_dir\sources\libs\lib\src\main\java\mylib\Library.java" you can see that it contains two init methods, however when deploying on payara/micro:5.2022.3 init(@observes @initialized(ApplicationScoped.class) ServletContext init) won't be invoked (check logs)
package mylib;

import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.Initialized;
import javax.enterprise.event.Observes;
import javax.servlet.ServletContext;

@ApplicationScoped
public class Library {
    public boolean someLibraryMethod() {
        return true;
    }

    public void init(@Observes @Initialized(ApplicationScoped.class) Object init) {
        System.out.println(" ### log-1 mylib.Library.init(java.lang.Object) called ###");
    }

    public void init(@Observes @Initialized(ApplicationScoped.class) ServletContext init) {
        System.out.println(" ### log-2 mylib.Library.init(javax.servlet.ServletContext) invoked ###");
    }
}

Thanks in advance for any eventual reply/hint


Solution

  • I think there is a misunderstanding about what to expect from @Observes @Initialized(ApplicationScoped.class). The JakartaEE specs (8) say:

    An event with this qualifier is fired when a context is initialized, i.e. ready for use.

    And "context" here is meant in the sense of CDI context, which ServletContext is definitely not.

    Also I found no documentation whatsoever about what the type of the event message will be. Hence this is totally implementation specific and there should be made no assumptions about it (if you want to keep your code portable).

    If your Library object received that event twice (once with Objectand once with ServletContext) in Payara 4, then this can be considered a mistake (not to say bug) of that platform.

    I'd recommend to either implement a javax.servlet.ServletContainerInitializer or a javax.servlet.ServletContextListener, if you want to be notified about server startup and get hold of ServletContext during that phase. The big advantage of ServletContextListener (over the other) is that you can easily interact with CDI beans (@ApplicationScoped or @Singleton).

    Here is an example:

    package mylib;
    
    import javax.inject.Inject;
    import javax.servlet.ServletContextEvent;
    import javax.servlet.ServletContextListener;
    import javax.servlet.annotation.WebListener;
    
    @WebListener
    public class MyListener implements ServletContextListener {
    
        @Inject
        Library library;
    
        @Override
        public void contextInitialized(ServletContextEvent sce) {
            System.out.println(" ### log-4 mylib.MyListener.contextInitialized() invoked ###");
            System.out.println(sce.getServletContext());
            System.out.println(library);
            System.out.println(library.someLibraryMethod());
        }
    
        @Override
        public void contextDestroyed(ServletContextEvent sce) {
            System.out.println(" ### log-5 mylib.MyListener.contextDestroyed() invoked ###");
            System.out.println(sce.getServletContext());
    
        }
    }
    

    (tested with your Docker project)

    Hope this helps.

    Update

    I also found the following Gist which seems to be exactly what you want to achieve: https://gist.github.com/mojavelinux/637959

    Note that here @Initialized and @Destroyed are custom qualifiers and not the ones from package javax.enterprise.context.

    Additionally here is an updated version of that Gist using the qualifiers from javax.enterprise.context: https://gist.github.com/nineninesevenfour/d9c643ff5a7f98302f89687720d0a138. With that your original code of Library can to stay unchanged.

    With the updated Gist however these two methods are called twice (once from Payara and once triggered from ServletContextLifecycleNotifier:

    // Library.java
        public void init(@Observes @Initialized(ApplicationScoped.class) Object init) {
            System.out.println(" ### log-1 mylib.Library.init(java.lang.Object) called ###");
        }
    
    // App.java
        public static void init(@Observes @Initialized(ApplicationScoped.class) Object init) {
            System.out.println(new App().getGreeting());
        }