java 插件化开发

优点

添加各种功能可以不用对系统本身进行修改
可重用性高
灵活

思路

使用 ClassLoader 加载指定的jar
使用 java 反射机制调用指定类的指定方法

步骤

  1. 主项目 , 用户加载插件以及处理其他操作
  2. 公共的接口项目 , 插件实现此接口方法 , 用于判断是否为可用的插件
  3. 若干个实现实现指定接口的项目(插件)
  4. 主项目配置插件信息的文件(plugins.json)
  5. 读取配置文件,将信息转换为实体对象
  6. 使用类加载器加载可用的插件信息
  7. 反射调用插件指定方法

实现

1. 接口项目

只需要一个接口 , 插件项目实现此接口

接口项目的目录结构


package com.lyinlong.pluginloader.relation;

/**
 * 所有的插件必须实现的关联接口
 * @author lyinlong
 */
public interface PluginLoaderInterface {

    /**
     * 执行的方法
     * 插件的入口
     */
    void execute();
}

2. 主项目

  • 配置文件/实体
  • json转换
  • 类加载器

主项目的目录结构


Plugin.java

package com.lyinlong.pluginloader.entity;

/**
 * 插件信息实体
 * @author lyinlong
 */
public class Plugin {

    /**
     * 插件名称
     */
    private String name;
    /**
     * 插件的存放路径
     */
    private String url;
    /**
     * 插件的入口
     */
    private String entrance;
    /**
     * 是否启用这个插件
     */
    private boolean isEnable;

    public Plugin() {}

    public Plugin(String name, String url, String entrance, boolean isEnable) {
        super();
        this.name = name;
        this.url = url;
        this.entrance = entrance;
        this.isEnable = isEnable;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getEntrance() {
        return entrance;
    }

    public void setEntrance(String entrance) {
        this.entrance = entrance;
    }

    public boolean isEnable() {
        return isEnable;
    }

    public void setEnable(boolean isEnable) {
        this.isEnable = isEnable;
    }
}

Loader.java

package com.lyinlong.pluginloader.loader;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;

import net.sf.json.JSONArray;
import net.sf.json.JSONObject;

import com.lyinlong.pluginloader.entity.Plugin;
import com.lyinlong.pluginloader.relation.PluginLoaderInterface;

/**
 * 插件加载
 * @author lyinlong
 */
public class Loader {

    /**
     * 读取classpath下的plugin配置文件
     * @return
     */
    public String readConfig(){

        InputStream inputStream = Loader.class.getClassLoader().getResourceAsStream("plugins.json");

        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
        StringBuilder builder = new StringBuilder();

        String line = null;
        try {
            while ((line = reader.readLine() ) != null) {
                builder.append(line);
            }
        } catch (Exception e) {
            System.err.println("读取插件配置文件出错 ");
        }
        return builder.toString();
    }

    /**
     * json数组转换为实体对象
     * @param json
     * @return
     */
    public List<Plugin> jsonToObject(String json){
        JSONArray jsonArray = JSONArray.fromObject(json);
        JSONObject object = null;
        List<Plugin> plugins = new ArrayList<Plugin>();

        for (int i = 0; i < jsonArray.size(); i++) {
            object = jsonArray.getJSONObject(i);
            plugins.add(new Plugin(object.getString("name"), object.getString("url") ,object.getString("entrance") , object.getBoolean("isEnable")));
        }
        return plugins;
    }

    /**
     * 加载插件的方法
     * @throws Exception
     */
    public void loader() throws Exception{
        String json = readConfig();
        List<Plugin> plugins = jsonToObject(json);
        URL[] urls = new URL[plugins.size()];
        for (int i = 0; i < plugins.size(); i++) {
            if(plugins.get(i).isEnable())
                urls[i] = new URL(plugins.get(i).getUrl());
        }

        URLClassLoader classLoader = new URLClassLoader(urls);

        for (Plugin plugin : plugins) {
            //如果这个插件允许启用
            if(plugin.isEnable()){
                Class clazz = classLoader.loadClass(plugin.getEntrance());
                if(PluginLoaderInterface.class.isAssignableFrom(clazz)){
                    System.out.println("实现了接口");
                    clazz.getMethod("execute").invoke(clazz.newInstance(), null);
                }else{
                    System.out.println("没有实现接口");
                }
            }
        }
    }
}

plugins.json


[
    {
        "name": "插件_sayHello",
        "url": "file:E:/java_workspace/PluginLoader/plugins/sayHello.jar",
        "entrance" : "com.pluginsyahello.main.SayHello" ,
        "isEnable": "true"
    },
    {
        "name": "插件_sayHello",
        "url": "file:E:/java_workspace/PluginLoader/plugins/sayHello.jar",
        "entrance" : "com.pluginsyahello.main.SayHello" ,
        "isEnable": "true"
    }
]

插件项目

引入接口项目 , 实现接口项目的exeucte方法.

简单插件项目的结构

package com.pluginsyahello.main;

import com.lyinlong.pluginloader.relation.PluginLoaderInterface;

public class SayHello implements PluginLoaderInterface{

    @Override
    public void execute() {
        System.out.println("哈哈 , 成功加载了sayHello插件");
    }

}

资源

jsonLib及依赖包

项目源码