Я был впечатлен и поражен (1). Пришлось попробовать себя, и это действительно так, как вы говорите! Пробовал на WildFly 18.0.1 и 15.0.1, такое же поведение. Я даже запустил jconsole, и график использования кучи имел совершенно здоровую пилообразную форму с памятью, возвращающейся точно к базовой линии после каждого G C, для случая @ApplicationScoped
. Затем я начал экспериментировать.
Я не мог поверить, что CDI фактически уничтожает экземпляры бинов @Dependent
, поэтому я добавил метод PreDestroy
к Book
. Метод никогда не вызывался, как ожидалось, но я начал получать OOME, даже для @ApplicationScoped
CDI-компонента!
Почему добавление @PostConstruct
метода заставляет приложение вести себя по-другому? Я думаю, что правильный вопрос обратный, то есть, почему удаление из @PostConstruct
заставляет OOME исчезнуть? Поскольку CDI должен уничтожать @Dependent
объекты с их родительским объектом - в данном случае Instance<Book>
, он должен хранить список @Dependent
объектов внутри Instance
. Отладьте, и вы увидите это. Этот список содержит ссылки на все созданные объекты @Dependent
и в конечном итоге приводит к утечке памяти. Очевидно (у него не было времени, чтобы найти доказательства) Weld применяет оптимизацию: если объект @Dependent
не имеет методов @PostConstruct
в своем дереве внедрения зависимостей, Weld не добавляет его в этот список. Вот почему (1) работает (1), когда GlobalService
равен @ApplicationScoped
.
CDI должен связывать свой жизненный цикл с жизненным циклом EJB при внедрении EJB в bean-компонент CDI. Очевидно (опять же, я предполагаю) CDI создает хук @PostConstruct
, когда GlobalService
является EJB для связывания двух жизненных циклов. Согласно JSR 365 (CDI 2.0) гл. 18.2:
Сессионный компонент без сохранения состояния должен принадлежать псевдоскопе @Dependent
.
Итак, Book
получает хук @PostConstruct
в своей цепочке из @Dependent
объектов:
Book [@Dependent, no @PostConstruct] -> GlobalService [@Dependent, @PostConstruct]
Поэтому Instance<Book>
нужна ссылка на каждый Book
, который он создает, для вызова метода @PostConstruct
( неявно созданный CDI) зависимого GlobalService
EJB.
Разобравшись с тайной (1) (надеюсь), перейдем к (2):
createBook2()
Недостатком является то, что пользователь должен знать, что целевой бин @Dependent
. Если кто-то меняет сферу, уничтожать ее неуместно (если вы действительно не знаете, что делаете). И тогда сохранение ссылки на мертвый экземпляр кажется жутким:) createBook3()
: один недостаток состоит в том, что GlobalFactory
должен знать зависимости Book
. Возможно, это не так уж и плохо, для фабрики разумно, чтобы книги знали свою зависимость. Но тогда вы не получите такие полезные свойства CDI, как @PostConstruct
/ @PreDestroy
, перехватчики для книги (например, транзакции реализованы как перехватчики в CDI). Другим недостатком является то, что простой объект имеет ссылки на компоненты CDI. Если они принадлежат к узкой области (например, @RequestScoped
), вы можете хранить ссылки на них за пределами их нормальной продолжительности жизни с непредсказуемыми результатами.
Теперь для (3) и что является лучшим Решение, я думаю, это сильно зависит от вашего конкретного случая использования. Например, если вам нужны все возможности CDI (например, перехватчики) на каждом Book
, вы можете отслеживать книги, которые вы создаете вручную, и массово уничтожать, когда это необходимо. Или, если book - это POJO, для которого просто нужно установить свой идентификатор, просто включите go и используйте createBook3()
.