Design pattern 6 plug in pattern

Code address: https://github.com/showkawa/springBoot_2017/tree/master/spb-demo/spb-script

Plug in mode does not belong to the category of classic design mode, but it will also be encountered in project opening. In particular, small partners engaged in micro front end development will have a deeper understanding of plug-in mode. I write this plug-in mode here because my project has some tool type project development, which needs to be pluggable and convenient for other business projects to customize their own plug-ins.

The following is the UML of my plug-in pattern (for the basic knowledge of UML class diagram, you can check the blog: https://www.cnblogs.com/hlkawa/p/12854562.html ). There are three core plug-in patterns here: Plugin interface, PluginRegister interface and implementation class SimplePluginRegister

1. Plugin interface

Plugin defines the method supotrs to determine whether to activate and use the current plug-in according to the input parameter delimiter

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

2. PluginRegister interface

The PluginRegister interface mainly defines some initialization plug-ins and methods to obtain plug-ins and statistical plug-ins

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 class

The implementation class of SimplePluginRegister is the default implementation class of PluginRegister. If you don't want to use the default implementation, you can implement and extend the PluginRegister interface according to your own business. I won't do more about the specific methods below. The code logic is very simple

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();
    }
}

The following is how to activate and utilize plug-in functions. Here, I take Maven command plugin as an example, and the dependency is shown in the figure below

CommandPlugin defines the method run(), which is the functional logic of the specific plug-in

package com.kawa.script.plugin;


import com.kawa.script.Plugin;

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

maven commnadservice corresponds to maven's plug-in function. The following run() method code logic doesn't pay much attention (that is, to realize java calling maven instruction), but mainly focuses on the supports() method to determine whether the plug-in is activated

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");
    }
}

Now start the initial session plug-in. You can see that three plug-ins (jpscommandservice, maven commandservice, gitcommandservice) are initialized in the static code block through new simplepluginregistry < > ()

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 <<<<<<<<<<")));
    }

}

Then in the method simplepluginregistry Get and activate the plug-in in getpluginfor (command), and call back supports() in this method to determine which plug-in to activate

    @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, now demonstrate that the java program uses the maven plug-in function, and bring the parameter {maven test in the Idea test

You will find that there are calls to maven plug-ins and execute the test instruction (of course, this is not the key point. The key point is to de initialize the plug-ins through the plug-in design mode, activate the corresponding activation plug-ins according to the parameter entry conditions, and then run to create the corresponding business logic)

This completes the plug-in mode

Benefits of plug-in mode:

1. Provides the possibility of extending the program

2. Adding plug-ins is also very convenient

 

Reference source: spring-plugin (https://github.com/spring-projects/spring-plugin)

Keywords: Programming

Added by gardnc on Wed, 26 Jan 2022 10:37:38 +0200