Лучший способ интеграции EJB (JNDI) и CDI

В настоящее время у нас есть архитектура развертывания, в которой набор сервисов, ориентированных на данные, предоставляется бизнес-сервисам через RMI. Оба типа (сервисы, ориентированные на данные, и бизнес-сервисы) являются сеансовыми компонентами без сохранения состояния. Каждый пакет интерфейса службы данных (содержащий удаленные интерфейсы) также имеет локатор, который выполняет поиск JNDI. Мы делаем это так, чтобы мы могли вызывать сервис, ориентированный на данные, из любой точки логики бизнес-сервиса.

Вот как выглядит локатор:

public final class OMRLocator {

    private static final Logger LOG = Logger.getLogger( OMRLocator.class );

    private static final String ORG_WILDFLY_INITIAL_CTX_FACTORY = "org.wildfly.naming.client.WildFlyInitialContextFactory";

    private OMRLocator() {
    }

    @Produces
    public static OrganisationsAndMandatesRegister locate() {
        try {
            Properties ctxProp = new Properties();
            ctxProp.put( Context.INITIAL_CONTEXT_FACTORY, ORG_WILDFLY_INITIAL_CTX_FACTORY );
            InitialContext ctx = new InitialContext( ctxProp );
            return (OrganisationsAndMandatesRegister) ctx.lookup( OrganisationsAndMandatesConstants.REMOTE_NAME );
        }
        catch ( NamingException ex ) {
            LOG.log( Level.WARN, "Cannot reach: " + OrganisationsAndMandatesConstants.REMOTE_NAME, ex );
            return null;
        }
    }
}

Мы работали на JBOSS EAP6 и начали экспериментировать с CDI. Следовательно, мы добавили beans.xml в bean-компоненты службы данных и @Produces, чтобы сделать (в данном случае OrganisationAndMandatesRegister CDI инжектируемым). Идея состоит в том, что в будущем мы могли бы переупаковать наше приложение и упаковать службы данных вместе с бизнес-службой. в одном корпоративном архиве.

В последнее время мы перешли на JBOSS EAP7.2 (Wildfly 8?) и вдруг увидели всевозможные непредвиденные задержки и проблемы с транзакциями.

Я подозреваю, что причиной этих проблем является то, как мы получаем бобы. Например: я предполагаю, что область действия зависит от жизненного цикла бизнес-компонента EJB, но для каждого вызова locate() в бизнес-службе создается новый экземпляр службы данных.

Итак: как лучше всего создать удаленный компонент (через RMI) при использовании CDI? Должен ли я принимать во внимание область действия, учитывая, что оба типа служб не имеют состояния (или это делается автоматически)?


person Sjaak    schedule 17.04.2019    source источник


Ответы (1)


Если в методе производителя область не определена, используется @Dependend, поэтому найдите подходящую область, например, @RequestScoped. Когда вы извлекаете EJB из JNDI, вы не получаете новый экземпляр, вы получаете экземпляр из пула, который может быть одним и тем же при нескольких вызовах. Ваша проблема может заключаться в перехватчиках EJB, потому что при зависимой области экземпляр EJB всегда остается одним и тем же после внедрения и никогда не выпускается.

Избавьтесь от @Produces, потому что CDI интегрируется с EJB, а EJB можно внедрять через @Inject или @EJB. Если вы хотите сохранить класс Locator, вы можете внедрить туда EJB и вернуть правильный экземпляр EJB (который на самом деле является прокси), при этом Locator должен быть @ApplicationScoped. Другой способ - использовать Instance, который позволяет выполнять программный поиск. С типом Object вы можете получить доступ ко всем компонентам CDI (включая EJB) контейнера, поэтому общий интерфейс будет полезен для минимизации количества доступных компонентов.

Дополнительные сведения см. по следующим ссылкам.

https://docs.jboss.org/weld/reference/latest/en-US/html/injection.html#_obtaining_a_contextual_instance_by_programmatic_lookup

Внедрить EJB без сохранения состояния с @Inject в CDI Weld ManagedBean (приложение JSF 1.2 EJB на jboss 6 AS)

http://www.adam-bien.com/roller/abien/entry/inject_vs_ejb


Просто резюмируя:

вариант а) Оставить как есть. Возможно, сделайте область явной с помощью @Dependent, чтобы указать, что это вызывается при создании вызывающего компонента (внедрение в конструктор вызывающего компонента)

вариант б) Использовать сеансовый компонент @ApplicationScoped без сохранения состояния

@LocalBean // otherwise @EJB will not work
@ApplicationScoped // this instance should be created only once per ear
public class OMRLocator {

    @EJB // does implicitly a remote (default) JNDI lookup
    private OrganisationsAndMandatesRegister instance;

    @Produces
    @Dependent // just to make it explicit
    public OrganisationsAndMandatesRegister locate() {
       return instance;
    }
}
person Thomas Herzog    schedule 03.05.2019
comment
Спасибо за развернутый ответ. Как только у меня будет возможность попробовать это в нашей (настоящей) удаленной установке, я вернусь к этому. - person Sjaak; 11.05.2019
comment
Герцог, я не хочу вводить больше бобов. Однако я немного поэкспериментировал с @Produces. Что меня удивляет (когда я отлаживаю), так это то, что простой @Produces приводит к тому, что метод locate вызывается только один раз. Однако, когда я добавляю @RequestScoped к @Produces, locate внезапно вызывается каждым вызовом bean-компонента (чего я и ожидал в первую очередь, поскольку область действия зависит). Я пока этого не понимаю. - person Sjaak; 17.05.2019
comment
Bean-компоненты DependendScoped создаются только один раз для внедрения на экземпляр bean-компонента, который его внедряет. Они похожи на обычные экземпляры, ими не управляет прокси-сервер, они принадлежат и управляются bean-компонентом, который его внедрил. Bean-компоненты RequestScoped создаются и внедряются для каждого запроса. Таким образом, locate должен вызываться один раз для каждого созданного EJB для каждого запроса. - person Thomas Herzog; 17.05.2019
comment
Ах. Копейка начинает падать. Теперь я понимаю, что 1) если вы используете аннотацию области CDI вместе с @Produces, она полностью не зависит от жизненного цикла внедрения конструктора bean-компонента без сохранения состояния. 2) если вы не используете аннотацию области CDI в сочетании с @Produces (== по умолчанию == @Dependent), она следует жизненному циклу сеансового компонента без сохранения состояния, а не запроса .... Вы упомянули, что локатор должен быть @ ApplicationScoped это означает, что связь между вызовом EJB и удаленным EJB существует на протяжении всей жизни приложения и никогда не обновляется. Разве это не рискованно? - person Sjaak; 18.05.2019
comment
Каждый раз, когда вызывается производитель, создается экземпляр класса, содержащего производителя с зависимой областью действия. Если область приложения ограничена, экземпляр создается только один раз. Если вы оставите поиск jndi, это не должно быть проблемой. Если вы внедрите EJB через аннотацию EJB и вернете ссылку, то это не должно быть проблемой, потому что вы возвращаете прокси, который получает доступ к фактическому bean-компоненту контейнера ejb. Может быть, вы попробуете отладить. - person Thomas Herzog; 18.05.2019
comment
В порядке. Итак, если я правильно вас понимаю, есть разница между @EJB и простым поиском JNDI с @Produces, как указано выше?. (прокси против без прокси)....... Компоненты (у меня их несколько) были @Produces фактически внедрены, имеют внедрение конструктора, которое я предпочитаю полю (модульный тест). Насколько я знаю, @EJB можно использовать только в полях. Я попытался удалить локатор целиком, но, похоже, я не могу использовать @Inject для удаленных EJB (Weld жалуется). Я думаю, что @EJB работает только с корпоративными компонентами, поэтому сам локатор должен быть @ApplicationScoped (локальным) без сохранения состояния фасоль, если я последую твоему совету. - person Sjaak; 19.05.2019
comment
И я, конечно, сделал некоторую отладку. Но иногда слишком много переменных, чтобы понять, что вы видите :). Электронная документация по CDI и EJB не всегда кратка именно по этим темам. Добавьте к этому новую (версию) сервера приложений и легко заблудитесь. Это очень полезно для меня. - person Sjaak; 19.05.2019