Ответ 1
Ты почти прав с продюсером CDI. Единственное, что вы должны использовать метод производителя вместо поля производителя.
Если вы используете Weld в качестве контейнера CDI (GlassFish 4.1 и WildFly 8.2.0), то ваш пример объединения @Produces
, @PersistenceContext
и @RequestScoped
в поле производителя должен выбросить это исключение во время развертывания:
org.jboss.weld.exceptions.DefinitionException: WELD-001502: Поле источника ресурсов [Поле создателя ресурсов [EntityManager] с квалификаторы [@Any @Default], объявленные как [[BackedAnnotatedField] @Produces @RequestScoped @PersistenceContext com.somepackage.EntityManagerProducer.entityManager]] должен быть @Dependent scoped
Оказывается, что контейнер не требуется для поддержки какой-либо другой области, кроме @Dependent, при использовании поля производителя для поиска ресурсов Java EE.
CDI 1.2, раздел 3.7. Ресурсы:
Контейнер не требуется для поддержки ресурсов с областью действия другой чем @Dependent. Переносимые приложения не должны определять ресурсы с любой областью действия, кроме @Dependent.
Эта цитата была посвящена полям производителей. Использование метода-производителя для поиска ресурса полностью законно:
public class EntityManagerProducer {
@PersistenceContext
private EntityManager em;
@Produces
@RequestScoped
public EntityManager getEntityManager() {
return em;
}
}
Во-первых, контейнер будет создавать экземпляр производителя, а ссылка с менеджером сущности, управляемой контейнером, будет введена в поле em
. Затем контейнер вызовет ваш метод производителя и перенесет то, что он возвращает, в прокси-сервер CDI с запросом. Этот прокси-сервер CDI получает код вашего клиента при использовании @Inject
. Поскольку класс-производитель @Dependent (по умолчанию), базовая ссылка диспетчера сущностей, управляемая контейнером, не будет использоваться другими прокси-серверами CDI. Каждый раз, когда другой запрос запрашивает диспетчер сущностей, будет создан экземпляр нового экземпляра класса продюсера, а в производитель будет введена новая ссылка менеджера объектов, которая, в свою очередь, будет завершена в новый прокси-сервер CDI.
Чтобы быть технически корректным, базовый и неназванный контейнер, который выполняет инъекцию ресурсов в поле em
, может повторно использовать старые администраторы сущностей (см. сноску в спецификации JPA 2.1, раздел "7.9.1 Обязанности контейнеров", стр. 357). Но до сих пор мы чтим модель программирования, требуемую JPA.
В предыдущем примере не имело бы значения, если вы отметите EntityManagerProducer
@Dependent или @RequestScoped. Использование @Dependent семантически более корректно. Но если вы ставите более широкий охват, чем область запроса в классе производителя, вы рискуете подвергнуть ссылку на базовый менеджер сущности многим потокам, которые мы оба знаем, не очень хорошо. Реализация менеджера основного объекта, вероятно, является поточно-локальным объектом, но переносимые приложения не могут полагаться на детали реализации.
CDI не знает, как закрыть все, что угодно, что вы помещаете в связанный с запросом контекст. Более того, управляющий сущностью, управляемый контейнером, не должен быть закрыт кодом приложения.
JPA 2.1, раздел "7.9.1 Обязанности контейнеров":
Контейнер должен выдать исключение IllegalStateException, если приложение вызывает EntityManager.close в менеджере сущностей, управляемых контейнером.
К сожалению, многие люди используют метод @Disposes
для закрытия диспетчера сущности контейнера. Кто может обвинить их, когда официальный учебник Java EE 7, предоставленный Oracle, а также спецификация CDI сам использует средство удаления, чтобы закрыть диспетчер сущности, управляемый контейнером. Это просто неправильно, и вызов EntityManager.close()
будет вызывать IllegalStateException
независимо от того, где вы помещаете этот вызов, в метод удаления или где-то еще. Пример Oracle является самым большим грешником из двух, объявив класс производителя @javax.inject.Singleton
. Как мы узнали, этот риск раскрывает ссылку на основной менеджер объектов на множество разных потоков.
Было доказано здесь, что с использованием производителей CDI и средств устранения неправомерно: 1) не зависящий от потоков объект менеджер объекта может быть просочился во многие потоки и 2) утилизатор не действует; оставляя менеджера объекта открытым. То, что произошло, было исключение IllegalStateException, которое контейнер проглотил, не оставляя следов (загадочная запись в журнале, в которой говорится, что была "ошибка, разрушающая экземпляр" ).
Как правило, использование CDI для поиска менеджеров сущностей, управляемых контейнерами, не является хорошей идеей. Приложение, скорее всего, лучше использовать только @PersistenceContext
и быть довольным им. Но всегда есть исключения из правила, как в вашем примере, и CDI также может быть полезен для абстрагирования EntityManagerFactory
при обработке жизненного цикла управляемых сущностями приложений.
Чтобы получить полное представление о том, как получить управляемый сущностью контейнер, управляемый контейнером и как использовать CDI для поиска сущностей, вы можете прочитать this и this.