问题原贴:
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包中,所有类的路径和名称不能相同的说法是不正确的。
分享到:
相关推荐
在电子设计大赛中,开发抽题小插件是一项常见的任务,用于随机选择参赛者需要解答的问题。本项目名为"C#抽题小插件",它采用C#编程语言实现,主要用于打开并处理PDF文件,这涉及到C#与PDF文档交互的技术。下面将详细...
用户可以轻松地向前或向后滑动来切换题目,这种滑动效果通常由第三方库Swiper实现。Swiper是一款强大的触摸滑动插件,特别适合构建滑动轮播、网格布局和全屏滚动等效果。它支持多种动画效果,同时兼容大部分现代...
本篇将深入探讨一份针对前端开发者设置的自学和自测练习题,特别是其中的第三题代码。这份练习题集旨在帮助学习者巩固JavaScript基础,并逐步迈向更高阶的应用。 首先,我们来理解JavaScript的重要性。JavaScript是...
在“黑马程序员Python视频中代码、课后习题等第三章内容”这个资源包中,我们聚焦于Python编程语言的学习,特别是围绕第三章的主题展开。Python作为一种高级编程语言,以其简洁明了的语法和强大的功能深受程序员喜爱...
4. BIM设计师斑马的背景:斑马拥有浙江大学建筑系的硕士学历和在国企设计院及上市公司的BIM工作经验,代表作品包括参加第16届威尼斯建筑双年展的华腾猪舍里展厅以及入选2014年Rhino大会的沈阳科技馆幕墙项目。...
这份文档详尽解答了教材《计算机操作系统》第三版中的课后习题,旨在帮助学习者深入理解并巩固操作系统的核心概念、原理及应用。 操作系统是计算机科学中的基石,它管理着计算机的硬件资源,为用户和应用程序提供...
综上所述,Vue.js面试题会覆盖这些核心知识点,通过回答这些问题,可以判断面试者是否具备Vue.js开发的基本技能和理解能力。在回答问题时,不仅需要了解概念,还应该结合实际项目经验进行详细阐述。
2. **可扩展性**:设计为模块化、插件化,允许添加自定义功能和集成第三方服务,以满足不同场景的需求。 3. **自动化**:Kubernetes能够自动部署、重启、复制和扩展容器,确保服务的高可用性和稳定性。 Kubernetes...
本教程通过详细解答课后习题,帮助读者深入理解JavaWeb编程的核心概念和技术。以下是相关知识点的详细说明: 1. **JavaWeb基础**:JavaWeb是基于Java技术的Web应用开发平台,它包括Servlet、JSP(JavaServer Pages...
1~31 题目 + 完整解答代码 1.分秒转换 2.字符串转换为整数 3.最大最小数字的差值 4.列表最后一个元素 5.比较字符串长度 6.字符串首尾连接 7.检查复数单词 8.列表唯一的数字 9.range转为列表 10.素数判断 11.元音...
15. **源码阅读**:面试中可能会要求解读部分Android系统或第三方库的关键源码。 以上是根据标题和标签推测的可能知识点,实际内容需要解压并查阅"百度Android工程师面试题.pdf"才能得到更准确的详情。对于准备面试...
### 第十五届蓝桥杯大赛软件赛省赛第二场 C/C++ 大学B组试题解析 #### 题目背景及要求概述 蓝桥杯大赛是中国一项知名的计算机类竞赛,旨在选拔和培养优秀的计算机人才。本次比赛为第十五届蓝桥杯大赛软件赛省赛第...
在第9届全国大学生GIS技能大赛中,下午的试题可能涵盖了GIS技术的多个核心领域,包括空间数据处理、地图制图、空间分析、数据库管理以及GIS应用开发等。 1. **空间数据处理**:这部分可能涉及到矢量数据的编辑和...
- 第三题考察UML(统一建模语言),包括9种UML图的使用,如类图、对象图、用例图等。 - 第四、五题是C语言程序填空,测试考生的结构化编程能力,数据结构和算法运用。 - 第六、七题为C++和Java程序填空,考生需...
本文主要讨论了两个编程相关的课堂练习题,涵盖了16位汇编语言的程序设计和子程序调用。首先,我们来详细分析第一个题目。 题目一的目标是编写一个汇编程序段,将BX寄存器中的内容以十六进制形式显示在屏幕上。这个...
6. **插件**:可能有预装或可选的第三方插件,增强小程序的功能。 7. **其他资源**:如音频、视频等多媒体文件,用于教学辅助。 综上所述,这个基于微信平台的研知识题库小程序提供了丰富的教育资源和测试功能,...
通过解答《Thinking in Java》第四版的练习题,我们可以系统地巩固和提高Java编程技能,为成为一名出色的Java开发者打下坚实基础。这本书的习题涵盖了从基础到高级的各个方面,是每个Java学习者不可多得的实践资源。
3. **函数与递归**:函数是组织代码的基本单元,递归则是解决某些问题的有效手段。码图问题可能需要设计递归算法来解决问题,如深度优先搜索(DFS)或回溯法。 4. **指针与引用**:C++的指针和引用允许直接操作内存,...
### 2022年软件评测师上午试题分析与解答 #### 试题(1) **题目**: 在计算机体系结构中,CPU内部涉及程序计数器PC、存储器数据寄存器MDR、指令寄存器IR和存储器地址寄存器MAR等。若CPU要执行的指令为:MOV R0, #...
通信原理习题 第六章 数字信号的调制传输共3页,第2页2、已知解调器输入端的峰值信噪比为 8 dB,分别计算 2ASK 和 2PSK 相干解调的误比特率,并进行