CDI 2.0, Java SE - conditional observer method not called in weld-se-shaded 3.0.5.Final

617 views Asked by At

I'm facing a problem with a conditional observer method that is not being called. Here is the code, starting with a junit test:

    import static org.hamcrest.CoreMatchers.notNullValue;
    import static org.hamcrest.CoreMatchers.nullValue;
    import static org.junit.Assert.assertThat;

    import javax.enterprise.inject.Instance;
    import javax.enterprise.inject.se.SeContainer;
    import javax.enterprise.inject.se.SeContainerInitializer;

    import org.apache.logging.log4j.LogManager;
    import org.apache.logging.log4j.Logger;
    import org.junit.After;
    import org.junit.Before;
    import org.junit.Test;

    public class CDIMinimalConditionalObserverTest
    {
        private final static Logger LOGGER = LogManager.getLogger(CDIMinimalConditionalObserverTest.class);

        private SeContainer container;

        @Before public void before()
        {
            LOGGER.debug("before");
        final SeContainerInitializer initialiser = SeContainerInitializer.newInstance();
        container = initialiser.initialize();
    }

    @After public void after()
    {
        container.close();
        LOGGER.debug("after");
    }

    @Test public void testObservation_observationInManagedNonExistentConditionalObservers()
    {
        CDIMinimalConditionalObserverEvent event = new CDIMinimalConditionalObserverEvent();
        container.getBeanManager().fireEvent(event);
        assertThat(event.msg, nullValue());
    }

    @Test public void testObservation_observationInManagedExistentConditionalObservers()
    {
        // create observer by selection
        Instance<CDIMinimalConditionalObserver> instance = container.select(CDIMinimalConditionalObserver.class);
        CDIMinimalConditionalObserver observer = instance.get();
        assertThat(observer, notNullValue());

        CDIMinimalConditionalObserverEvent event = new CDIMinimalConditionalObserverEvent();
        container.getBeanManager().fireEvent(event);
        observer.doSomething();
        assertThat(event.msg, notNullValue());
    }
}

Here is the class with the conditional observer method:

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.event.Observes;
import javax.enterprise.event.Reception;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import de.jmda.sandbox.cdi.se.CDIMinimalTests.SimpleInnerEvent;

/**
 * {@link Model} annotation assigns non-dependent scope and thereby makes it possible to make {@link
 * #observation(SimpleInnerEvent)} conditional
 */
@ApplicationScoped public class CDIMinimalConditionalObserver
{
    private final static Logger LOGGER = LogManager.getLogger(SimpleConditionalObserver.class);

    public CDIMinimalConditionalObserver()
    {
        LOGGER.debug("constructor");
    }

    @PostConstruct public void postConstruct()
    {
        LOGGER.debug("post construct");
    }

    @PreDestroy public void preDestroy()
    {
        LOGGER.debug("pre destroy");
    }

    public void observation(@Observes(notifyObserver=Reception.IF_EXISTS) CDIMinimalConditionalObserverEvent event)
    {
        event.msg = "observation";
        LOGGER.debug(event.msg);
    }

    public void doSomething()
    {
        LOGGER.debug("doing something");
    }
}

And finally here is the event class:

public class CDIMinimalConditionalObserverEvent { String msg; }

The test fails because event.msg is null though it shouldn't be. Logging output does not show any "observation" output. The test passes if the condition is removed.

Any ideas? Thanks!

1

There are 1 answers

8
LppEdd On BEST ANSWER

When your @ApplicationScoped Bean gets discovered, it does not get instantiated immediately.
CDI is smart enough to initialize the real object, the one behind the scene, only when needed.

You can see that retrieving an ApplicationScoped Bean via

final Instance<App> select = container.select(App.class);
final App app = select.get();

Does indeed return a proxy instance.
At this stage there is still no App Bean attached to the Application context.

enter image description here

Now, try to interact with that object (even just by calling toString), and only after, fire the event.
You'll notice it does work, because the underlying object has been instantiated via its no-arg constructor.

Removing Reception.IF_EXISTS simply signal CDI that it has to create and attach to the context the underlying instance immediatly, so that it can accept incoming events no matter what.

This proxying behavior is documented in the specification (I need to find the page), and it's why a Bean requires a no-arg constructor.

Dependent scope Beans don't suffer of this problem, as they're created every time it is needed, from scratch, and are not tracked by the framework. For singleton, session, or request scoped Beans, a proxy is required to manage them correctly.


Dependent scope Bean, you can see it's a "pure" instance

enter image description here