Посещать Посещать для получения полной документации
Год назад я столкнулся с необходимостью динамически загружать фрагменты кода в свои Java-приложения без необходимости каждый раз перекомпилировать или переупаковывать свое программное обеспечение. По сути, я искал систему плагинов.
В то время существовала только одна система плагинов с открытым исходным кодом, заслуживающая внимания, PF4J , но я чувствовал, что она была немного неуклюжей и громоздкой. Я хотел чего-то предельно простого, что я мог бы использовать, чтобы быть продуктивным в считанные минуты. Поэтому я решил написать свой собственный.
Введите Поверхность заглушки . И первая итерация была чертовски дерьмовой, потому что в то время я был всего лишь стажером в своей компании, все еще изучал Java в реальном мире и не обладал знаниями для написания хорошего, полезного фреймворка.
Первая реализация была не такой простой, как я хотел. Были интерфейсы для реализации, отражение для использования, файлы конфигурации для записи, изолированная система разрешений, наполовину работающая/наполовину сломанная попытка внедрения зависимостей между плагинами, а также много пота и слез от меня, чтобы написать и использовать его. Не самое лучшее программное обеспечение в мире. Вот пример старого плагина Plug Face:
package org.plugface.demo.plugins.greet; import org.plugface.Plugin; import org.plugface.PluginConfiguration; import org.plugface.PluginStatus; import org.plugface.demo.app.sdk.Greeter; import org.plugface.impl.DefaultPluginConfiguration; import java.util.Collections; public class GreeterPlugin implements Plugin, Greeter{ private final String name; private PluginConfiguration configuration; private PluginStatus status; private boolean enabled; public GreeterPlugin () { name = "greeter"; configuration = new DefaultPluginConfiguration(); status = PluginStatus.READY; enabled = false; } @Override public void start() { throw new UnsupportedOperationException("This plugin operates in single mode only"); } @Override public void stop() { throw new UnsupportedOperationException("This plugin operates in single mode only"); } @Override public String execute(String[] parameters) { return greet(); } @Override public PluginConfiguration getPluginConfiguration() { return (PluginConfiguration) Collections.unmodifiableMap(configuration); } @Override public void setPluginConfiguration(PluginConfiguration configuration) { this.configuration = configuration; } @Override public String getName() { return name; } @Override public PluginStatus getStatus() { return status; } @Override public void setStatus(PluginStatus pluginStatus) { status = pluginStatus; } @Override public void enable() { this.enabled = true; } @Override public void disable() { this.enabled = false; } @Override public boolean isEnabled() { return enabled ; } @Override public String greet() { return "Hello PlugFace!": } }
Эти твари были монстрами , интерфейс был огромным и полным спорно полезных функций (таких как статус и имя), и их было непрактично реализовывать. Если вы хотели более сложного поведения, чем “запуск”, “остановка” и “выполнение”, вам нужно было добавить свои собственные интерфейсы и привести свой плагин к этому типу, чтобы использовать его или получить доступ к методам через отражение.
И тут меня осенила идея. Зачем ограничивать то, как пользователь взаимодействует с плагином, когда все, с чем мог справиться фреймворк, – это динамическая загрузка классов?
С версией 0.6 фреймворк возродился. Это было (почти) полное переписывание. Я отказался от всей истории интерфейса ради гораздо более чистой @Plugin("имя") аннотации и НУЛЕВЫХ мнений о том, как должен вести себя ваш плагин. Просто заставьте его реализовать какой-либо интерфейс, известный вашему приложению, а затем получить доступ к нему через него.
Вот тот же плагин, но с новой системой:
package org.plugface.demo.plugins.greet;
import org.plugface.core.annotations.Plugin;
import org.plugface.demo.app.sdk.Greeter;
@Plugin("greeter")
public class GreeterPlugin implements Greeter {
@Override
public String greet() {
return "Hello PlugFace!";
}
}
Он реализует интерфейс Приветствие , предоставляемый моим приложением. Теперь мой код может просто извлечь плагин и поместить его в переменную типа Greeter, даже не зная о классе GreeterPlugin . Аккуратно!
Вот код моего приложения.
final PluginManager manager = PluginManagers.defaultPluginManager();
manager.loadPlugins(PluginSources.jarSource("path/to/my/plugin/jars"));
final Greeter greeter = manager.getPlugin(Greeter.class);
greeter.greet(); // => "Hello PlugFace!"
PluginManager – это класс утилит для загрузки плагинов из различных источников, еще одно улучшение по сравнению со старой версией, в которой использовалась только жестко запрограммированная загрузка JAR, и доступ к PluginContext для добавления/получения/удаления плагинов как по имени, так и по типу.
Я также полностью отказался от функций песочницы, которые использовали функции контекста безопасности Java для ограничения плагинов в том, что им было разрешено делать. Поскольку с этим было очень трудно справиться, я предпочел полностью удалить его, чтобы сосредоточиться на основных функциях этой новой версии. Я мог бы повторно ввести его в будущей версии, если найду способ реализовать его удобным способом.
Особенностью, которую пыталась внедрить старая система, была возможность внедрения плагинов друг в друга. Он достиг этого, поместив плагины в Конфигурацию плагина объект (который в основном представлял собой прославленную Карту <Строка, объект> , он работал, но был довольно непрактичным в использовании.
Плагин Face 0.6 представил полноценную систему внедрения зависимостей с помощью конструкторов, используя стандарт @javax.inject. Добавьте аннотацию (которая в будущем станет необязательной в случае наличия одного конструктора), чтобы классы плагинов можно было использовать с любой стандартной платформой DI, такой как Spring или Guice . Он поддерживает сложные графики зависимостей с использованием топологической сортировки , с обнаружением циклических зависимостей и всем этим хорошим материалом.
package org.plugface.demo.plugins.math;
import org.plugface.core.annotations.Plugin;
import org.plugface.demo.app.sdk.Mathematics;
import javax.inject.Inject;
@Plugin("math")
public class MathPlugin implements Mathematics {
private final SumPlugin sum;
private final MultPlugin mult;
@Inject
public MathPlugin(SumPlugin sum, MultPlugin mult) {
this.sum = sum;
this.mult = mult;
}
@Override
public Integer sum(int a, int b) {
return sum.sum(a, b);
}
@Override
public Integer mult(int a, int b) {
return mult.mult(a, b);
}
}
Spring Framework – это потрясающий инструментарий для Java. В нем есть практически все, что угодно, но его основной особенностью явно является система внедрения зависимостей. Поскольку я часто использую Spring почти во всех своих проектах, я хотел, чтобы Plug Face был хорошо интегрирован с ним. Если вы хотите использовать обычную ванильную заглушку, вы можете импортировать модуль заглушка-ядро , но при использовании Spring вы можете переключиться на специальный модуль заглушка-пружина . Он не только поддерживает автоматически настроенные компоненты как для PluginManager, так и для PluginContext, но также добавляет поддержку извлечения плагина из ApplicationContext (это означает, что если объект не найден в качестве плагина, он будет найден среди компонентов Spring) и, возможно, что более важно, он делает компоненты Spring жизнеспособными целями для внедрения зависимостей в плагины.
Правильно, с штекерной торцевой пружиной вы можете не только вводить другие плагины в свои плагины, но и любой компонент Spring, который вы зарегистрировали в своем приложении.
Ваши плагины могут получать доступ к классам сервисов, репозиториям, синглетонам утилит, в основном ко всему, что находится внутри контекста Spring.
package org.plugface.demo.plugins.user;
import org.plugface.core.annotations.Plugin;
import org.plugface.demo.app.sdk.Greeter;
import org.plugface.demo.app.sdk.TestService;
import javax.inject.Inject;
@Plugin("greeter")
public class UserPlugin implements UserDetails {
private final UserService userService; //this is a Spring service
@Inject
public GreeterPlugin(UserService userService) {
this.userService = userService;
}
@Override
public String getUsername() {
final User user = userService.getUserById(0L);
return user.getUsername();
}
}
В будущем я также хочу интегрироваться с другими фреймворками DI, такими как Guice, и расширить текущий набор функций, если появится какой-либо новый вариант использования плагинов в Java. До тех пор, спасибо за внимание и счастливого кодирования!
Оригинал: “https://dev.to/matteojoliveau/plugface-reborn—the-java-plugin-system-18bm”