設計模式6 插件模式 Plugin Pattern

代碼地址:https://github.com/showkawa/springBoot_2017/tree/master/spb-demo/spb-script

插件模式不屬於經典設計模式的範疇,但是在項目開放中也會碰到,特別是做微前端(Micro-Frontend)開發的小夥伴會對插件化插件模式體會更深。我這邊寫這個插件模式是因爲我這邊項目有一些工具型的項目開發,需要做到可插拔和方便其他業務項目定製自己的插件。

下面是我的插件模式的UML (關於UML類圖的基礎知識可以查看博客: https://www.cnblogs.com/hlkawa/p/12854562.html)。這邊的插件模式核心有三個,分別是Plugin接口,PluginRegister接口和實現類SimplePluginRegister.

1. Plugin接口

Plugin定義了方法suppotrs根據入參delimiter來判斷是否激活和使用當前插件

public interface Plugin<S> {
    boolean supports(S delimiter);
}

2. PluginRegister接口

PluginRegister接口主要定義了一些初始化插件,以及獲取插件和統計插件的方法

public interface PluginRegistry<T extends Plugin<S>, S> extends Iterable<T> {
    
    public static <S, T extends Plugin<S>> PluginRegistry<T, S> empty() {
        return of(Collections.emptyList());
    }

    @SafeVarargs
    public static <S, T extends Plugin<S>> PluginRegistry<T, S> of(T... plugins) {
        return of(Arrays.asList(plugins));
    }

    public static <S, T extends Plugin<S>> PluginRegistry<T, S> of(List<? extends T> plugins) {
        return of(plugins);
    }

    Optional<T> getPluginFor(S delimiter);

    List<T> getPluginsFor(S delimiter);

    int countPlugins();

    boolean contains(T plugin);

    boolean hasPluginFor(S delimiter);

    List<T> getPlugins();
}

3. SimplePluginRegister類

SimplePluginRegister的實現類是PluginRegister的默認實現類,如果不想使用默認的實現也可以根據自己業務去實現和擴展PluginRegister接口,下面具體的方法我就不多做介紹了代碼邏輯很簡單

package com.kawa.script;

import org.springframework.util.Assert;

import java.util.*;
import java.util.stream.Collectors;


public class SimplePluginRegistry<T extends Plugin<S>, S> implements PluginRegistry<T, S>, Iterable<T> {

    private List<T> plugins;
    private boolean initialized;

    protected SimplePluginRegistry(List<? extends T> plugins) {
        Assert.notNull(plugins, "Plugins must not be null!");
        this.plugins = plugins == null ? new ArrayList<>() : (List<T>) plugins;
        this.initialized = false;
    }

    public List<T> getPlugins() {
        if (!initialized) {
            this.plugins = initialize(this.plugins);
            this.initialized = true;
        }
        return plugins;
    }

    protected synchronized List<T> initialize(List<T> plugins) {
        Assert.notNull(plugins, "Plugins must not be null!");
        return plugins.stream()
                .filter(it -> it != null)
                .collect(Collectors.toList());
    }

    @Override
    public Iterator<T> iterator() {
        return getPlugins().iterator();
    }


    public static <S, T extends Plugin<S>> SimplePluginRegistry<T, S> empty() {
        return of(Collections.emptyList());
    }

    @SafeVarargs
    public static <S, T extends Plugin<S>> SimplePluginRegistry<T, S> of(T... plugins) {
        return of(Arrays.asList(plugins));
    }


    public static <S, T extends Plugin<S>> SimplePluginRegistry<T, S> of(List<? extends T> plugins) {
        return new SimplePluginRegistry<>(plugins);
    }

    @Override
    public Optional<T> getPluginFor(S delimiter) {
        Assert.notNull(delimiter, "Delimiter must not be null!");
        return getPlugins().stream()
                .filter(it -> it.supports(delimiter))
                .findFirst();
    }

    @Override
    public List<T> getPluginsFor(S delimiter) {
        Assert.notNull(delimiter, "Delimiter must not be null!");
        return getPlugins().stream()
                .filter(it -> it.supports(delimiter))
                .collect(Collectors.toList());
    }

    @Override
    public int countPlugins() {
        return getPlugins().size();
    }

    @Override
    public boolean contains(T plugin) {
        return getPlugins().contains(plugin);
    }

    @Override
    public boolean hasPluginFor(S delimiter) {
        return getPluginFor(delimiter).isPresent();
    }
}

下面就是如何激活和利用插件功能,這邊我以MavenCommandPlugin爲例,依賴關係如下圖

 其中CommandPlugin定義了方法run(),就是具體的插件的功能邏輯

package com.kawa.script.plugin;


import com.kawa.script.Plugin;

public interface CommandPlugin extends Plugin<String> {
    void run(String parma);
}

MavenCommnadService對應maven的插件功能,下面的run()方法代碼邏輯不用太關注(就是實現java調用maven指令),主要是關注supports()方法決定該插件是否被激活

package com.kawa.script.service.command;

import com.kawa.script.plugin.CommandPlugin;
import org.apache.maven.shared.invoker.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;

public class MavenCommandService implements CommandPlugin {

    private static final Logger log = LoggerFactory.getLogger(MavenCommandService.class);

    @Override
    public void run(String parma) {
        log.info("parma:{}", parma);
        ClassLoader classLoader = MavenCommandService.class.getClassLoader();
        String classPath = classLoader.getResource("").getPath();
        Path path = Paths.get(classPath.replace("/target/classes/", "/pom.xml"));
        InvocationRequest invocationRequest = new DefaultInvocationRequest();
        invocationRequest.setPomFile(path.toFile());
        invocationRequest.setGoals(Collections.singletonList(parma));
        InvocationResult result = null;
        try {
            result = new DefaultInvoker()
                    .setMavenHome(Paths.get("/usr/share/maven").toFile())
                    .execute(invocationRequest);
        } catch (MavenInvocationException e) {
            e.printStackTrace();
        }
        int exitCode = result.getExitCode();
        if (exitCode != 0) {
            log.info(">>>>>>>>>>> maven run command hit error <<<<<<<<<<");
        }
        log.info(result.toString());
    }

    @Override
    public boolean supports(String s) {
        return s.equals("maven");
    }
}

現在開始初始話插件, 可以看到在靜態代碼塊中通過 new SimplePluginRegistry<>() 初始化三個插件 (JpsCommandService, MavenCommandService, GitCommandService)

package com.kawa.script;

import com.kawa.script.plugin.CommandPlugin;
import com.kawa.script.service.command.GitCommandService;
import com.kawa.script.service.command.JpsCommandService;
import com.kawa.script.service.command.MavenCommandService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Arrays;
import java.util.Optional;


public class BrianScriptApplication {

    private static final Logger log = LoggerFactory.getLogger(BrianScriptApplication.class);

    private static SimplePluginRegistry<CommandPlugin, String> simplePluginRegistry;

    static {
        simplePluginRegistry = new SimplePluginRegistry<>(Arrays.asList(
                new JpsCommandService(), new MavenCommandService(), new GitCommandService()));
    }

    public static void main(String[] args) {
        if (args == null || args.length <= 0) {
            log.info(">>>>>>>>> no args, exit!!!");
            return;
        }
        String value = null;
        String command = args[0];
        if (args.length > 1) {
            value = args[1];
        }
        Optional<CommandPlugin> pluginFor = simplePluginRegistry.getPluginFor(command);
        String finalValue = value;
        pluginFor.ifPresentOrElse(cp -> {
            log.info(">>>>>>>>>> {} run <<<<<<<<<<", cp.getClass().getSimpleName());
            cp.run(finalValue);
        }, new Thread(() -> log.info(">>>>>>>>>> invalid command <<<<<<<<<<")));
    }

}

然後在方法 simplePluginRegistry.getPluginFor(command) 中獲取和激活插件,在這個方法裏面回調supports()來判斷激活哪個插件

    @Override
    public Optional<T> getPluginFor(S delimiter) {
        Assert.notNull(delimiter, "Delimiter must not be null!");
        return getPlugins().stream()
                .filter(it -> it.supports(delimiter))
                .findFirst();
    }

OK,現在演示下java程序使用maven插件功能, 在Idea測試 帶上參數  maven test 

會發現有調用到maven插件並執行test指令(當然這不是重點,重點是通過插件的設計模式的去初始化插件,以及根據入參條件去激活對應的激活插件然後運行創建對應的業務邏輯)

到此插件模式講完.

插件模式的好處:

1、提供了擴展程序的可能

2、增加插件也很方便

 

參考來源:spring-plugin (https://github.com/spring-projects/spring-plugin)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章