Рубрики
Без рубрики

Лицо Штекера Возрождается – система плагинов Java

Новая и обновленная система плагинов для JVM. С тегами java, плагины, spring, показать разработчика.

Посещать Посещать для получения полной документации

Год назад я столкнулся с необходимостью динамически загружать фрагменты кода в свои 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”