问题原贴:
http://cloverprince.iteye.com/admin/blogs/481307
引用
3. 现有一个主程序用Java语言写成。现在要允许第三方开发人员编写扩展的类,约定第三方开发的类必须包含一个实现了某个已知接口(如interface IFooPlugin)的类,名称不限。如果要求第三方的类必须与主程序的bytecode分开发布,把.class放在classpath相应位置,或把jar丢在某个文件夹内即可被动态装载使用,应如何实现?
回答:
使用jar打包每个插件。里面包含一个实现已知接口的类,在jar的MANIFEST.MF中定义该类的全路径(像com.example.blah.MyPluginClass这样)。使用java.util.jar中的JarFile和Manifest类解析jar包和Manifest文件,用URLClassLoader装载该jar包。获得插件类后,用Class.newInstance()方法创造实例。
适用范围:
在Java-1.6中测试通过。
实现:
先指定一个接口,比如叫com.javaeye.cloverprince.plugins.PluginInterface。插件必须包含一个类,实现这个接口。
package com.javaeye.cloverprince.interfaces;
public interface PluginInterface {
void setName(String name); // 设定名字
void greet(); // 打招呼
}
创建一个插件。插件包含一个类,实现这个接口。
这个类叫com.javaeye.cloverprince.HelloWorldPlugin。
package com.javaeye.cloverprince;
import com.javaeye.cloverprince.interfaces.*;
public class HelloWorldPlugin implements PluginInterface{
private String name;
@Override
public void greet() {
System.out.format("Hello, %s\n",name);
}
@Override
public void setName(String name) {
this.name = name;
}
}
这个包将被打入一个jar包。我们还需要一个MANIFEST.MF文件
Manifest-Version: 1.0
Plugin-Class: com.javaeye.cloverprince.HelloWorldPlugin
注意第二行,这个Plugin-Class属性是我自己编的。
问题出现:
为什么要在Manifest里面放置这个类的路径呢?
回答:因为Java的所有的包/类的组织结构,是一个公共的大树。不同的类一定拥有不同的路径。因此,不同的插件中的类,路径、类名一定不同。根据插件的定义,主程序不应该知道插件的实现细节。也就是说,主程序在编译之前,不可能知道插件的类的路径和名称。但是,要让插件工作,主程序又知道所有的插件的共同特征,想一想,如果主程序不知道插件的“任何”细节,又怎么知道jar里面哪个类才是实现了那个已知接口的类呢?
对于这个实现来说:如果将插件组织到不同的jar包中,那么一个良好的存储这一信息的地方,就是它的Manifest。读取一个jar包时,先看看它的Manifest中的这个Plugin-Class属性,就知道应该装载哪个类了。
这时,这个jar包里只有两个文件:
引用
META-INF/MANIFEST.MF
com/javaeye/cloverprince/HelloWorldPlugin.class
另一个插件也类似的制作。
一个类 com.somecompanyelse.GoodbyeWorld:
package com.somecompanyelse;
import com.javaeye.cloverprince.interfaces.PluginInterface;
public class GoodbyeWorld implements PluginInterface {
private String name;
@Override
public void greet() {
System.out.println("Goodbye, "+name);
}
@Override
public void setName(String name) {
this.name = name;
}
}
一个Manifest文件MANIFEST.MF:
Manifest-Version: 1.0
Plugin-Class: com.somecompanyelse.GoodbyeWorld
打成另一个jar包,包含两个文件:
引用
META-INF/MANIFEST.MF
com/somecompanyelse/GoodbyeWorld.class
现在,两个插件已经有了,只差一个主程序来读取这两个插件了。
主程序:
import java.io.*;
import java.util.jar.*;
import java.util.*;
import java.net.*;
import com.javaeye.cloverprince.interfaces.PluginInterface;
public class Main {
public static final String PLUGINS_PATH = "plugins";
// 用一个ArrayList储存每个插件中的类的实例。
private static ArrayList<PluginInterface> plugins =
new ArrayList<PluginInterface>();
public static void main(String[] args) {
File pluginsDir = new File(PLUGINS_PATH);
if(!pluginsDir.isDirectory()) {
System.err.format("%s isn't directory!\n",pluginsDir.getName());
return;
}
// Load plugins
for(File pluginFile : pluginsDir.listFiles()) {
if(pluginFile.getName().endsWith(".jar")) {
System.out.format("Loading File: %s ...\n", pluginFile.getAbsolutePath());
loadFile(pluginFile);
}
}
// Test plugins
for(PluginInterface plugin : plugins) {
plugin.setName("cloverprince");
plugin.greet();
}
}
private static void loadFile(File pluginFile) {
JarFile jf;
Manifest mf;
// 打开jar包
try {
jf = new JarFile(pluginFile);
mf = jf.getManifest();
} catch (IOException e) {
System.err.format("Error reading jar file.\n", pluginFile.getName());
e.printStackTrace();
return;
}
// 从jar的Manifest中读取Plugin-Class属性
String pluginClassPath = mf.getMainAttributes().getValue("Plugin-Class");
if(pluginClassPath==null) {
System.err.format("Cannot find attribute Plugin-Class in manifest file.\n", pluginFile.getName());
return;
}
// 创造ClassLoader
URLClassLoader cl;
try {
cl = new URLClassLoader(new URL[]{
pluginFile.toURI().toURL()
});
} catch (MalformedURLException e) {
System.err.format("This should not throw.\n");
e.printStackTrace();
return;
}
// 装载这个插件jar中的类
Class pluginClass;
try {
pluginClass = cl.loadClass(pluginClassPath);
} catch (ClassNotFoundException e) {
System.err.println("Cannot load class");
e.printStackTrace();
return;
}
// 实例化这个类
PluginInterface pluginInstance;
try {
pluginInstance = (PluginInterface) pluginClass.newInstance();
} catch (InstantiationException e) {
System.err.println("Cannot instantiate class.");
e.printStackTrace();
return;
} catch (IllegalAccessException e) {
System.err.println("Illegal Access.");
e.printStackTrace();
return;
}
// 把这个类放入数组中,等待以后使用
plugins.add(pluginInstance);
System.out.format("Class %s loaded.\n",pluginInstance.getClass().getCanonicalName());
}
}
精简版(无异常处理):
import java.io.*;
import java.util.jar.*;
import java.util.*;
import java.net.*;
import com.javaeye.cloverprince.interfaces.PluginInterface;
public class Main {
public static final String PLUGINS_PATH = "plugins";
// 用一个ArrayList储存每个插件中的类的实例。
private static ArrayList<PluginInterface> plugins =
new ArrayList<PluginInterface>();
public static void main(String[] args) throws Exception {
File pluginsDir = new File(PLUGINS_PATH);
// Load plugins
for(File pluginFile : pluginsDir.listFiles()) {
if(pluginFile.getName().endsWith(".jar")) {
loadFile(pluginFile);
}
}
// Test plugins
for(PluginInterface plugin : plugins) {
plugin.setName("cloverprince");
plugin.greet();
}
}
private static void loadFile(File pluginFile) throws Exception {
JarFile jf = new JarFile(pluginFile);
Manifest mf = jf.getManifest();
String pluginClassPath = mf.getMainAttributes().getValue("Plugin-Class");
URLClassLoader cl= new URLClassLoader(new URL[]{
pluginFile.toURI().toURL()
});
Class pluginClass = cl.loadClass(pluginClassPath);
PluginInterface pluginInstance = (PluginInterface) pluginClass.newInstance();
plugins.add(pluginInstance);
}
}
以上,所有需要的文件齐备。
编译:
如上所述,每个插件打一个jar包,主程序随意。注意路径。
执行:
执行该程序需要的最小文件集(4个文件):
引用
.
│ Main.class
│
├─com
│ └─javaeye
│ └─cloverprince
│ └─interfaces
│ PluginInterface.class
│
└─plugins
goodbye-3.14159265.jar
helloworld-1.0.jar
执行: java Main
引用
Loading File: D:\wks\workspace\PluginTest\plugins\goodbye-3.14159265.jar ...
Class com.somecompanyelse.GoodbyeWorld loaded.
Loading File: D:\wks\workspace\PluginTest\plugins\helloworld-1.0.jar ...
Class com.javaeye.cloverprince.HelloWorldPlugin loaded.
Goodbye, cloverprince
Hello, cloverprince
总结:
1. 主程序并不了解plugins目录中有多少插件。在运行时列举目录。
2. 主程序对每个plugins文件(比如叫helloworld-1.0.jar)的了解只有:
- helloworld-1.0的META-INF/MANIFEST.MF中有一个Plugin-Class属性,指定了该插件类的路径。
- 这个插件类拥有一个不带参数的构造方法。
- 这个插件类实现了com.javaeye.cloverprince.PluginInterface接口。
后记:
复制第一个插件hello world,将greeting中的字符串修改,其余文件均不变,打成另一个jar包,放在插件目录中,可以和第一个插件共存,分别装载并工作。
这就是说,不同的jar包中,所有类的路径和名称不能相同的说法是不正确的。
分享到:
相关推荐
第一章计算机控制系统概述习题与思考题1.1 什么是计算机控制系统?计算机控制系统较模拟系统有何优点?举例说明。解答: 由计算机参与并作为核心环节的自动控制系统,
通信原理习题 第六章 数字信号的调制传输共3页,第2页2、已知解调器输入端的峰值信噪比为 8 dB,分别计算 2ASK 和 2PSK 相干解调的误比特率,并进行
通信原理习题 第五章 数字信号的基带传输共 4页,第 2页2、已知某数字代码序列对应的 CMI 码基带信号波形如图所示,画出对应的 HDB3 码基带信号的波形,
(4)对于每道题,学生第一次输入正确答案得10分,第二次输入正确答案得7分,第三次输入正确答案得5分,否则不得分; (5)总成绩90以上显示SMART”80-90显示“GOOD”,70-80显示OK60-70显示“PASS”,60以下“TRYAGAIN
3.每个空格对应一个序号,有A、B、C、D四个选项,请选择一个最恰当的选项作为解答,在答题卡相应序号下填涂该选项。 4.解答前务必阅读例题和答题卡上的例题填涂样式及填涂注意事项。解答时用正规2B铅笔正确填涂选项...
第1章 微型计算机系统〔习题1.1〕简答题〔解答〕① 处理器每个单位时间可以处理的二进制数据位数称计算机字长。② 总线信号分成三组,分别是数据总线、地址总线和控
一、回答下面的问题 1、什么是超定方程,其对应的珍贵方程组是什么,系数特点是什么? 2、最小二乘拟合方法和插值方法的相同点和不同点,各自用在什么情况下? 3、生成超定方程时,Matlab是行优先,还是列优先? 4、...
这个Go项目是一个非常全面的Go语言学习和实践资源,涵盖了Go语言的基础知识、性能优化、设计模式、面试题、第三方工具集成、Web开发工具、区块链、内网穿透、爬虫和LeetCode算法题等多个方面。对于Go语言爱好者和...
第三:商家广告费 解释:游残里面的很多地方可以给商家冠名,商家可以在线自己设置广告,但是投放广告需要消费鸡蛋,商家可收购别人的鸡蛋用来投放广告,收购的钱会直接充值到平台,成为平台利润。 第四:卖游戏 ...
因为girlfriend自带了很多插件,依赖的第三方包就比较多,所以如果带宽不够大,安装速度就会比较慢一些,请耐心等待。另外,建议大家最好先通过[virtualenv](https://virtualenv.readthedocs.org/en/latest/)来安装...
改进问答系统问题详情页面如果是专家回答的问题,增加可以显示专家认分类及专家姓名等信息,以区分普通会员及专家回答。评论增加数据表,当默认的评论数据表数据量很大量,会严重的影响页面的加载速度。这时我们可以...
改进问答系统问题详情页面如果是专家回答的问题,增加可以显示专家认分类及专家姓名等信息,以区分普通会员及专家回答。评论增加数据表,当默认的评论数据表数据量很大量,会严重的影响页面的加载速度。这时我们可以...
它还会像个真人一样跟人对话,它会翻译,会回答问题,会完形填空,会做阅读理解题,而且会做 SAT 考试题。 GPT-3 这个水平,到底有高级呢?它的原理是什么?它有多大实用价值?它对世界意味着什么呢? GPT-3 跟当前...
自动化考试系统(易语言2006年大赛三等奖) 《高中数学自动化测试系统》紧扣高中数学新课程标准,适合高中学生在家自行练习使用。其题库总题量达到3000多题。 《高中数学自动化测试系统》的优势在于 1、紧扣新课程标准...
第三章 Eclipse 的基础概念,配置和使用 51 3.1界面布局 51 3.1.1菜单 51 3.1.2 工具栏 51 3.1.3 透视图(Perspective)切换器 52 3.1.4 视图(View) 53 3.1.5 上下文菜单(Context Menu) 55 3.1.6 状态栏(Status...
KesionCMS打破CMS系统瓶颈,系统自带功能强大的BBS,不再为了整合第三方论坛平台而烦恼,一站式用户登录,系统还集成腾讯QQ,新浪微博及支付宝快捷登录,只需绑定下帐户,以后可以直接用QQ号或支付宝帐户登录。...
KesionCMS打破CMS系统瓶颈,系统自带功能强大的BBS,不再为了整合第三方论坛平台而烦恼,一站式用户登录,系统还集成腾讯QQ,新浪微博及支付宝快捷登录,只需绑定下帐户,以后可以直接用QQ号或支付宝帐户登录。...
主要回答了5个方面的问题:如何设计一个合适的地理数据库,如何无需编写代码定制数据库,如何管理复杂工程中的工作流,如何建模河流、道路、电力线路等各种线性系统,如何集成卫星影像用于地理分析和表达,如何利用...