Плагин 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" оказывается.
Теперь мои вопросы:
Я на правильном пути?
Если так, что я пропускаю в коде?
Есть ли лучшие решения этой проблемы (OSGI, custom clazz.forName, ...)?
1 ответ
Я не могу дать вам полный ответ, но здесь есть пища для размышлений...
Я предполагаю, что проблема в том, что Glassfish загружает каждый ejb jar как приложение в отдельный загрузчик классов.
Ты сделал это. К сожалению, это соответствует спецификации JEE, поэтому это ожидаемое поведение.
Я не знаю, что такое Glassfish, но может быть какая-то функция, позволяющая вам совместно использовать загрузчик классов между развертываниями (есть некоторые в Wildfly - так называемая изоляция развертывания). Это может решить вашу проблему.
Другая вещь, которую я знаю из Wildfly, - это то, что вы можете развернуть какое-то приложение в виде серверного модуля, который затем получит доступ ко всем другим развертываниям (и их загрузчикам классов). Если есть что-то подобное в Glassfish, вы можете попробовать это. В случае, если вы хотите дать шанс Wildfly, вот ссылка на вопрос, где это обсуждалось.
Теперь, с точки зрения CDI, это поведение также является правильным, и я боюсь, что вы не сможете изменить его, потому что у вас нет доступа к загрузчику классов из других развертываний (если у вас есть, вы можете загрузить BeanManager
для данного развертывания и поиска его для соответствующего компонента).
Я надеюсь, что это даст вам хоть какое-то понимание.