#java #events #conditional #cdi-2.0
#java #Мероприятия #условные операторы #cdi-2.0
Вопрос:
Я столкнулся с проблемой с методом условного наблюдателя, который не вызывается. Вот код, начинающийся с теста junit:
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());
}
}
Вот класс с методом условного наблюдателя:
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");
}
}
И, наконец, вот класс event:
public class CDIMinimalConditionalObserverEvent { String msg; }
Тест завершается неудачей, потому что event.msg имеет значение null, хотя этого не должно быть. Выходные данные журнала не показывают никаких выходных данных «наблюдения». Тест проходит, если условие удалено.
Есть идеи? Спасибо!
Ответ №1:
Когда ваш @ApplicationScoped
компонент обнаруживается, он не создается немедленно.
CDI
достаточно умен, чтобы инициализировать реальный объект, находящийся за сценой, только при необходимости.
Вы можете видеть, что извлечение ApplicationScoped
компонента через
final Instance<App> select = container.select(App.class);
final App app = select.get();
Действительно возвращает экземпляр прокси.
На этом этапе по-прежнему нет App
компонента, подключенного к контексту приложения.
Теперь попробуйте взаимодействовать с этим объектом (даже просто вызывая toString
), и только после запуска события.
Вы заметите, что это действительно работает, потому что базовый объект был создан с помощью конструктора без аргументов.
Удаление Reception.IF_EXISTS
просто сигнализирует CDI
о том, что он должен немедленно создать и присоединить к контексту базовый экземпляр, чтобы он мог принимать входящие события, несмотря ни на что.
Это поведение прокси задокументировано в спецификации (мне нужно найти страницу), и именно поэтому для компонента требуется конструктор без аргументов.
Dependent
компоненты области видимости не страдают от этой проблемы, поскольку они создаются каждый раз, когда это необходимо, с нуля, и не отслеживаются фреймворком. Для компонентов с одноэлементной, сессионной областью или запросом требуется прокси для правильного управления ими.
Dependent
компонент scope, вы можете видеть, что это «чистый» экземпляр
Комментарии:
1. @ruu Да, чтобы действительно досконально разобраться в CDI, вам нужно немного прочитать спецификацию. Однако я могу сказать о CDI только положительные вещи. Для DI это лучше, чем Spring.
2. Я добавил вызов «doSomething ()» для наблюдателя непосредственно перед запуском события, а затем метод наблюдателя вызывается по желанию. Большое спасибо, ваше объяснение отличное! Однако я должен сказать, что это кажется довольно странным. Я хочу избежать CDI, создающего экземпляры для каждого метода наблюдателя, который он способен обнаружить. Вот почему я сделал метод наблюдателя условным. Но условные методы не разрешены в зависимых компонентах. Вот почему я сделал свои приложения bean Scoped. Теперь это вынуждает меня выполнить трюк, который вы описали. Хуже всего то, что CDI игнорирует событие абсолютно бесшумно!!!
3. Извините @LppEdd, я заменил свой первоначальный комментарий, пока вы на него отвечали. Наш разговор теперь кажется немного странным 😉
4. Вы заметили, что методы @PostConstruct не помогают?
5. @ruu Если вы удалите условие наблюдателя и по-прежнему будете применять ApplicationScoped, будет доступен только один экземпляр. Никакой другой никогда не будет создан. Этот единственный экземпляр был бы доступен в любом случае, даже без наблюдателя, просто его экземпляр мог быть создан позже по времени.