Плагин Java EE Framework с использованием итератора CDI Instance

У меня есть приложение, которое состоит из войн, основного ejb с большим количеством служебных компонентов в jar и удаленных интерфейсов в другом jar. Все упаковано в ухо и работает на Glassfish 4.1.

Теперь я хочу добавить точки расширения или поддержку плагинов к основному ejb.

Цель состоит в том, чтобы иметь различные сервисы импорта данных с возможностью "горячей" замены, которые используют один и тот же интерфейс, поскольку они получают и нормализуют финансовые данные от таких поставщиков, как Reuters и Bloomberg.

Эти плагины должны быть обнаружены и управляться bean-компонентом "Plugin Manager" в ядре ejb jar. Плагины должны поддерживать загрузку, выгрузку и замену во время выполнения.

В идеале интерфейсы плагинов находятся в отдельном пакете, так что кто-то другой может разрабатывать против них без необходимости в моем приложении или Glassfish и даже без стека Java EE. Я также хочу развернуть плагины по требованию, а не всегда все приложение.

В настоящее время я пытаюсь использовать итератор CDI Instance, который прекрасно работает с двумя реализациями службы импорта, если они находятся в ядре ejb. Если я поместил одну реализацию в отдельный ejb jar, то CDI просто не найдет его. Я предполагаю, что проблема в том, что Glassfish загружает каждый ejb jar как приложение в отдельный загрузчик классов.

Теперь идет мой текущий упрощенный код!

Интерфейс плагина в отдельном пакете jar:

package com.photon.extensions;

import java.io.Serializable;

public interface ImportServiceExtension extends Serializable {
    String getImportServiceName();
}

Реализация плагина в отдельном пакете ejb jar, который не найден:

package com.photon.services.extensions.vitrex.services;

import com.photon.extensions.ImportServiceExtension;
import javax.ejb.Remote;
import javax.ejb.Stateless;

@Remote(ImportServiceExtension.class)
@Stateless
public class ReutersImportService implements ImportServiceExtension {
    @Override
    public String getImportServiceName() {
        return "Reuters";
    }
}

Реализация плагина в базовом пакете ejb jar:

package com.photon.services.extensions;

import com.photon.extensions.ImportServiceExtension;
import javax.ejb.Stateless;

@Stateless
public class BloombergImportService implements ImportServiceExtension {
    @Override
    public String getImportServiceName() {
        return "Bloomberg";
    }
}

Удаленный интерфейс для "Менеджера плагинов" в jar удаленных интерфейсов:

package com.photon.services.extensions;

import java.util.List;
import javax.ejb.Remote;

@Remote
public interface ImportServiceExtensionsRemote {
    List<String> getImportServiceNames();
}

Реализация bean-компонента "Менеджер плагинов" в ядре ejb jar:

package com.photon.services.extensions;

import com.photon.extensions.ImportServiceExtension;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.ejb.Stateless;
import javax.enterprise.inject.Instance;
import javax.inject.Inject;

@Stateless
public class ImportersService implements ImportServiceExtensionsRemote {

    @Inject private Instance<ImportServiceExtension> importServiceExtensions;

    @Override
    public List<String> getImportServiceNames() {
        Iterator<ImportServiceExtension> iter = importServiceExtensions.iterator();
        List<String> names = new ArrayList<>();
        while ( iter.hasNext() ) {
            ImportServiceExtension extension = iter.next();
            names.add(extension.getImportServiceName());
        }
        return names;
    }
}

И, наконец, контроллер, который отображает имена веб-сайтов в войнах:

package com.photon.website;

import com.photon.services.extensions.ImportServiceExtensionsRemote;
import javax.ejb.EJB;
import javax.enterprise.context.RequestScoped;
import javax.inject.Named;

@RequestScoped
@Named
public class ImportController implements Serializable {

    @EJB private ImportServiceExtensionsRemote importServiceExtensions;

    public String getImportServiceNames() {
        String names = "";
        for ( String name : importServiceExtensions.getImportServiceNames() ) {
            names += name;
        }
        return names;
    }
}

В конце концов, только "Bloomberg" оказывается.

Теперь мои вопросы:

  1. Я на правильном пути?

  2. Если так, что я пропускаю в коде?

  3. Есть ли лучшие решения этой проблемы (OSGI, custom clazz.forName, ...)?

1 ответ

Решение

Я не могу дать вам полный ответ, но здесь есть пища для размышлений...

Я предполагаю, что проблема в том, что Glassfish загружает каждый ejb jar как приложение в отдельный загрузчик классов.

Ты сделал это. К сожалению, это соответствует спецификации JEE, поэтому это ожидаемое поведение.

Я не знаю, что такое Glassfish, но может быть какая-то функция, позволяющая вам совместно использовать загрузчик классов между развертываниями (есть некоторые в Wildfly - так называемая изоляция развертывания). Это может решить вашу проблему.

Другая вещь, которую я знаю из Wildfly, - это то, что вы можете развернуть какое-то приложение в виде серверного модуля, который затем получит доступ ко всем другим развертываниям (и их загрузчикам классов). Если есть что-то подобное в Glassfish, вы можете попробовать это. В случае, если вы хотите дать шанс Wildfly, вот ссылка на вопрос, где это обсуждалось.

Теперь, с точки зрения CDI, это поведение также является правильным, и я боюсь, что вы не сможете изменить его, потому что у вас нет доступа к загрузчику классов из других развертываний (если у вас есть, вы можете загрузить BeanManager для данного развертывания и поиска его для соответствующего компонента).

Я надеюсь, что это даст вам хоть какое-то понимание.

Другие вопросы по тегам