Java 加载指定位置java文件 -- 插件机制随想

引言

这几天因为工作的原因,看了一些关于加载工程以外的java文件的代码(对应随笔:Java 加载指定位置java文件).这种加载一个pojo类并没有什么实际意义.方法调用也需要提前知道这个外部类中有哪些方法名才能用反射进行调用.
想着插件机制不也是把外部文件加载到已有的工程中么.

思路及代码

思路

  • 插件接口定义

因为插件的工作机制应该是类似的,这里使用模板设计模式定义抽象模板类PluginTemplate

  • 具体实现类

工程内写一个插件,测试流程

  • 加载外部文件

工程外定义一个插件,测试及与工程内定义的插件对比

代码

抽象模板类

package reflect.plugin;

/**
 * @author Wang Yuanye
 * @version v1.0
 * @apiNote 插件模板类
 * @date : 2021/3/11 下午10:07
 **/
public abstract class PluginTemplate {
    private String data;

    // 模板方法
    protected final void handle(){
        printInfo(data);
        if (needBeforeProcess()) {
            data = (String)beforeProcess(data);
        }
        data = (String)process(data);
        if (needPostProcess()) {
            postProcess(data);
        }
    }

    public void setData(String data) {
        this.data = data;
    }

    // 打印插件信息
    protected abstract void printInfo(String data);

    // 钩子方法,决定模板处理流程
    protected boolean needBeforeProcess() {
        return true;
    }
    
    protected boolean needPostProcess() {
        return true;
    }

    // 前置处理:如数据处理等
    protected abstract Object beforeProcess(String data);

    // 数据处理
    protected abstract Object process(String data);

    // 后置处理
    protected abstract Object postProcess(String data);
}

工程内插件

package reflect.plugin;

/**
 * @author Wang Yuanye
 * @version v1.0
 * @apiNote 语言插件
 * @date : 2021/3/11 下午10:21
 **/
public class LangPlugin extends PluginTemplate{

    @Override
    protected void printInfo(String data) {
        System.out.println("插件信息: 皮肤插件. " +
                "数据:" +data);
    }
    // 自定义模板方法执行流程:不进行前置处理
    @Override
    protected boolean needBeforeProcess() {
        return false;
    }

    @Override
    protected Object beforeProcess(String data) {
        return data + ";beforeProcess 已处理";
    }

    @Override
    protected Object process(String data) {
        return data + "process 已处理";
    }

    @Override
    protected Object postProcess(String data) {
        data = data + ";postProcess 已处理";
        System.out.println(data);
        return data;
    }
}

LangPlugin 测试类

/**
 * @author Wang Yuanye
 * @version v1.0
 * @apiNote 测试类
 * @date : 2021/3/11 下午10:51
 **/
public class MainTest {
    public static void main(String[] args) {
        PluginTemplate plugin = new LangPlugin();
        plugin.setData("6666");
        plugin.handle();
    }
}

执行结果

插件信息: 皮肤插件. 数据:6666
6666;process 已处理;postProcess 已处理

外部插件定义


import reflect.plugin.PluginTemplate;
/**
 * @author Wang Yuanye
 * @version v1.0
 * @apiNote 皮肤插件
 * @date : 2021/3/11 下午10:21
 **/
public class SkinPlugin extends PluginTemplate{

    protected void printInfo(String data) {
        System.out.println("插件信息: 皮肤插件. " +
                "数据:" +data);
    }

    protected Object beforeProcess(String data) {
        data = data + ";beforeProcess 已处理";
        return data;
    }

    protected Object process(String data) {
        return data + ";process 已处理";
    }

    protected Object postProcess(String data) {
        data = data + ";postProcess 已处理";
        System.out.println(data);
        return data;
    }
}

加载外部java文件

package reflect.plugin;

import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

/**
 * @author Wang Yuanye
 * @version v1.0
 * @apiNote
 * @date : 2021/3/10 下午9:00
 **/
public class LoadTest {

    public static void main(String[] args) throws IllegalAccessException, InstantiationException {
        //OSS下载或某些特定文件夹
        String path = "/Users/mars/codeSpace/0bat/3source_code/1jdk/upload/SkinPlugin.java";
        Class<?> aClass = compileAndLoad(path);
        Class<?> superclass = aClass.getSuperclass();
        Class<PluginTemplate> pluginClass = PluginTemplate.class;
        // class对象比较
        if (pluginClass.equals(superclass)) {
            PluginTemplate o = (PluginTemplate) aClass.newInstance();
            o.setData("css data");
            o.handle();
        }
        Object o = aClass.newInstance();
        // 对象实例比较
        if (o instanceof PluginTemplate) {
            PluginTemplate plugin = (PluginTemplate) aClass.newInstance();
            plugin.setData("so ... it's over");
            plugin.handle();
        }
    }
    
    //编译并加载
    public static Class<?> compileAndLoad(String path) {
        String type = ".class";
        JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
        // 存放编译信息
        int result = javaCompiler.run(null, null, null, path);
        if (result != 0) {
            throw new RuntimeException("编译失败");
        }
        Class resultClass = null;
        String[] split = path.split("\.");
        String classLocation = split[0]+ type;
        // 自定义类加载器
        MyClassLoader myClassLoader = new MyClassLoader();
        try {
            resultClass = myClassLoader.findClass(classLocation);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return resultClass;
    }


    // 自定义类加载器
    static class MyClassLoader extends ClassLoader{
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            Path path = null;
            byte[] bytes = null;
            try {
                path = Paths.get(name);
                bytes = Files.readAllBytes(path);
            } catch ( IOException e) {
                e.printStackTrace();
            }
            String[] split = name.split("/");
            String fileName = split[split.length - 1];
            String className = fileName.split("\.")[0];
            // 调用父类
            return super.defineClass(className, bytes, 0, bytes.length);
        }
    }
}

执行结果

插件信息: 皮肤插件. 数据:css data
css data;beforeProcess 已处理;process 已处理;postProcess 已处理
--------------
插件信息: 皮肤插件. 数据:css data
css data;beforeProcess 已处理;process 已处理;postProcess 已处理

其他

  • 定义的插件需要继承PluginTemplate抽象类,因此需要导入PluginTemplate类,否则编译报错.

即不导入类,也不写继承,对于插件开发者是最方便的,考虑将文件读入并转为string后,检测是否存在 import reflect.plugin.PluginTemplate,如果没有则拼接进去. 但是将 extend PluginTemplate 拼接到源码字符串中首次出现类名的位置,这个好像不太靠谱

  • 实现了上一步的前提下,又如何校验插件有效性呢?

想法是校验读入的源码字符串中是否存在PluginTemplate所有的抽象方法,其实不用做额外操作,java编译器编译时会校验源文件合法性.如: SkinPlugin中如果没有实现抽象方法process(),编译报错:

I'll put a flower in your hair~
原文地址:https://www.cnblogs.com/Yuanye-Wong/p/14520151.html