插件(Plugin)是什么不用多说,用过Eclipse就知道Eclipse有很多插件。但本文的内容不是Eclipse插件开发。
插件是根据软件提供的接口编写出来的程序,很多软件都支持插件,例如Eclipse、Photoshop、VisualStudio。插件可以动态给软件添加一些功能,也可以随时删除,这样的好处是任何人都可以给这个软件进行功能上的扩展,而不用去改软件本身的代码。
一、适用场景
比如需要开发一个系统,用来将一些有数据推送给客户,至于是什么数据不是重点。有三个客户:A客户需要把数据组织成一个xml格式的文件,通过FTP上传到客户服务器上;B客户需要把数据组织成一个json,通过HTTP请求提交;C客户希望生成一个Excel文件再通过E-mail发送...以后可能还会有更多的客户,也还会有更多操蛋的需求。
对于这样一个系统的开发,如果使用普通的方式开发,那么每增加一个客户就要修改一次系统代码,在代码中增加一个针对某个客户的功能,很不灵活。如果再减少一个客户,那么其对应的代码也就没有用了,是不是要删除掉又成了问题。
以上只是一个例子,在实际开发中经常会有类似的情形,此时使用插件化的方式会更灵活。
可以把数据的获取和整理这块和客户无关的逻辑放在主程序中,而主程序提供一个客户推送的接口,接口定义一个未实现的抽象方法“推送数据”,这个方法由各个客户对应的插件来实现。这样新增一个客户需求,不需要修改主程序的代码,只需要实现这个接口就行,插件写好打成jar包放在指定目录下,再配置一下,主程序就可以使用这个插件了。当不需要这个插件,也可以通过配置来去掉它。
二、主程序配置插件
上面说到主程序可以通过配置来动态添加和删除插件,配置的方式一般有两种:XML或数据库,二者选其一即可。
方法1:XML
主程序可以通过一个xml配置文件,动态配置插件。
A客户插件
D:/plugin/a-plugin.jar
com.xxg.aplugin.APlugin
B客户插件
D:/plugin/b-plugin.jar
com.xxg.bplugin.BPlugin
C客户插件
D:/plugin/c-plugin.jar
com.xxg.cplugin.CPlugin
主程序通过解析这个XML来调用插件,
方法2:数据库
如果使用数据库来配置插件,需要一个插件表:
插件表(plugin_info):
id |
int |
主键 |
name |
varchar |
插件名称 |
jar |
varchar |
插件jar文件路径 |
class |
varchar |
实现主程序接口的类的包名加类名 |
两种方法的区别:
两种方式从功能上来说是一样的。使用数据库方式的好处是可以很方遍的再开发一个管理界面来管理,不好的地方就是依赖数据库。我更推荐XML的方式,不依赖数据库。三、主程序
下面是以XML作为插件配置方式的调用插件的主程序。
主程序需要提供一个接口来提供给插件开发者来实现:
package com.xxg.main;
public interface PluginService
{
public void service();
}
上面是一个接口,包含一个未实现的方法service(),这个方法即和客户相关的逻辑,由插件来实现。
插件封装类:
package com.xxg.main;
public class Plugin
{
private String name;
private String jar;
private String className;
// setter、getter省略…
}
package com.xxg.main;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
public class XMLParser
{
public static List getPluginList() throws DocumentException
{
List list = new ArrayList();
SAXReader saxReader =new SAXReader();
Document document = saxReader.read(new File("plugin.xml"));
Element root = document.getRootElement();
List> plugins = root.elements("plugin");
for(Object pluginObj : plugins)
{
Element pluginEle = (Element)pluginObj;
Plugin plugin = new Plugin();
plugin.setName(pluginEle.elementText("name"));
plugin.setJar(pluginEle.elementText("jar"));
plugin.setClassName(pluginEle.elementText("class"));
list.add(plugin);
}
return list;
}
}
使用URLClassLoader动态加载jar文件,实例化插件中的对象:
package com.xxg.main;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.List;
public class PluginManager
{
private URLClassLoader urlClassLoader;
public PluginManager(List plugins) throws MalformedURLException
{
init(plugins);
}
private void init(List plugins) throws MalformedURLException
{
int size = plugins.size();
URL[] urls = new URL[size];
for(int i = 0; i < size; i++)
{
Plugin plugin = plugins.get(i);
String filePath = plugin.getJar();
urls[i] = new URL("file:" + filePath);
}
// 将jar文件组成数组,来创建一个URLClassLoader
urlClassLoader = new URLClassLoader(urls);
}
public PluginService getInstance(String className) throws ClassNotFoundException, InstantiationException, IllegalAccessException
{
// 插件实例化对象,插件都是实现PluginService接口
Class> clazz = urlClassLoader.loadClass(className);
Object instance = clazz.newInstance();
return (PluginService)instance;
}
}
package com.xxg.main;
import java.util.List;
public class Main
{
public static void main(String[] args)
{
try
{
List pluginList = XMLParser.getPluginList();
PluginManager pluginManager = new PluginManager(pluginList);
for(Plugin plugin : pluginList)
{
PluginService pluginService = pluginManager.getInstance(plugin.getClassName());
System.out.println("开始执行[" + plugin.getName() + "]插件...");
// 调用插件
pluginService.service();
System.out.println("[" + plugin.getName() + "]插件执行完成");
}
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
插件开发很简单,只需要把主程序的jar包引入到项目中,再实现主程序提供的接口就行:
package com.xxg.aplugin;
import com.xxg.main.PluginService;
public class APlugin implements PluginService
{
@Override
public void service()
{
System.out.println("A客户插件正在执行~");
}
}
插件实现完成后,打个jar包,注意不要把主程序的部分也打到jar里。
再实现其他插件,插件实现完成后,配置主程序的plugin.xml。
五、执行结果
配置好plugin.xml,插件jar放到配置的路径下:运行主程序main方法:
开始执行[A客户插件]插件...
A客户插件正在执行~
[A客户插件]插件执行完成
开始执行[B客户插件]插件...
B客户插件正在执行~
[B客户插件]插件执行完成
开始执行[C客户插件]插件...
C客户插件正在执行~
[C客户插件]插件执行完成六、service()参数、返回值
如果逻辑需要的话,service()可以添加参数和返回值。例如主程序需要传入数据给插件,可以加入参数,插件需要返回结果给主程序,可以加入返回值。
例如传给插件一些插件需要的配置项。在上面的场景中,各个客户的需求不同。A需要FTP上传,那么需要FTP服务器的地址、端口号、用户名、密码配置项;B需要HTTP请求,那么需要请求地址配置项;C需要发送邮件,那么需要e-mail地址配置项。
这些配置项可以统一配置在XML或数据库中。
XML:
每个插件元素中加入
A客户插件
D:/plugin/a-plugin.jar
com.xxg.aplugin.APlugin
192.168.7.1
21
XXG
123456
B客户插件
D:/plugin/b-plugin.jar
com.xxg.bplugin.BPlugin
http://www.xxg.com/api
C客户插件
D:/plugin/c-plugin.jar
com.xxg.cplugin.CPlugin
[email protected]
数据库:
再加一个插件配置表(plugin_config_info):
id |
int |
主键 |
plugin_id |
int |
外键,关联插件表id |
key |
varchar |
配置项键 |
value |
varchar |
配置项值 |
主程序定义接口,加入一个Map
package com.xxg.main;
import java.util.Map;
public interface PluginService
{
public void service(Map configs);
}
package com.xxg.aplugin;
import java.util.Map;
import com.xxg.main.PluginService;
public class APlugin implements PluginService
{
@Override
public void service(Map configs)
{
String ftpIp = configs.get("FTP_IP");
String ftpPort = configs.get("FTP_PORT");
String ftpUsername = configs.get("FTP_USERNAME");
String ftpPassword = configs.get("FTP_PASSWORD");
// ...
}
}
插件获取的配置项首先应该判断是否为null,防止忘了添加一些配置项。
作者:叉叉哥 转载请注明出处:http://blog.csdn.net/xiao__gui/article/details/9239743