`
cloverprince
  • 浏览: 127392 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

插件问题回答第3题

阅读更多
问题原贴: 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.1 什么是计算机控制系统?计算机控制系统较模拟系统有何优点?举例说明。解答: 由计算机参与并作为核心环节的自动控制系统,

    习题5参考解答1

    通信原理习题 第六章 数字信号的调制传输共3页,第2页2、已知解调器输入端的峰值信噪比为 8 dB,分别计算 2ASK 和 2PSK 相干解调的误比特率,并进行

    习题4参考解答1

    通信原理习题 第五章 数字信号的基带传输共 4页,第 2页2、已知某数字代码序列对应的 CMI 码基带信号波形如图所示,画出对应的 HDB3 码基带信号的波形,

    c语言题目,小学算术题目,实现50以内加减,要是想更大就解除限制,代码中有限制50的部分,同时要想加减乘除就直接在算法部分补即可

    (4)对于每道题,学生第一次输入正确答案得10分,第二次输入正确答案得7分,第三次输入正确答案得5分,否则不得分; (5)总成绩90以上显示SMART”80-90显示“GOOD”,70-80显示OK60-70显示“PASS”,60以下“TRYAGAIN

    2023年上半年 软件设计师答案解析

    3.每个空格对应一个序号,有A、B、C、D四个选项,请选择一个最恰当的选项作为解答,在答题卡相应序号下填涂该选项。 4.解答前务必阅读例题和答题卡上的例题填涂样式及填涂注意事项。解答时用正规2B铅笔正确填涂选项...

    微机原理与接口技术(钱晓捷版)课后习题答案1

    第1章 微型计算机系统〔习题1.1〕简答题〔解答〕① 处理器每个单位时间可以处理的二进制数据位数称计算机字长。② 总线信号分成三组,分别是数据总线、地址总线和控

    数值计算方法 MATLAB实验 实验报告 02 最小二乘拟合 含全部源代码.pdf

    一、回答下面的问题 1、什么是超定方程,其对应的珍贵方程组是什么,系数特点是什么? 2、最小二乘拟合方法和插值方法的相同点和不同点,各自用在什么情况下? 3、生成超定方程时,Matlab是行优先,还是列优先? 4、...

    Go语言学习学习demo

    这个Go项目是一个非常全面的Go语言学习和实践资源,涵盖了Go语言的基础知识、性能优化、设计模式、面试题、第三方工具集成、Web开发工具、区块链、内网穿透、爬虫和LeetCode算法题等多个方面。对于Go语言爱好者和...

    【开心农场V1.5.0】功能模块+解密开源版+养鸡场+邀请好友+鸡蛋交易市场+客户一键设置功能

    第三:商家广告费 解释:游残里面的很多地方可以给商家冠名,商家可以在线自己设置广告,但是投放广告需要消费鸡蛋,商家可收购别人的鸡蛋用来投放广告,收购的钱会直接充值到平台,成为平台利润。 第四:卖游戏 ...

    基于工作流的Python脚本引擎,从此写代码就像做填空题 +源代码+文档说明

    因为girlfriend自带了很多插件,依赖的第三方包就比较多,所以如果带宽不够大,安装速度就会比较慢一些,请耐心等待。另外,建议大家最好先通过[virtualenv](https://virtualenv.readthedocs.org/en/latest/)来安装...

    KesionEshop v9.5.140605 免费正式版 (gbk).rar

    改进问答系统问题详情页面如果是专家回答的问题,增加可以显示专家认分类及专家姓名等信息,以区分普通会员及专家回答。评论增加数据表,当默认的评论数据表数据量很大量,会严重的影响页面的加载速度。这时我们可以...

    KesionEshop v9.5.140605 免费正式版 (utf-8).rar

    改进问答系统问题详情页面如果是专家回答的问题,增加可以显示专家认分类及专家姓名等信息,以区分普通会员及专家回答。评论增加数据表,当默认的评论数据表数据量很大量,会严重的影响页面的加载速度。这时我们可以...

    GPT到底是什么?它能干什么?

    它还会像个真人一样跟人对话,它会翻译,会回答问题,会完形填空,会做阅读理解题,而且会做 SAT 考试题。 GPT-3 这个水平,到底有高级呢?它的原理是什么?它有多大实用价值?它对世界意味着什么呢? GPT-3 跟当前...

    易语言-自动化考试系统(易语言2006年大赛三等奖)

    自动化考试系统(易语言2006年大赛三等奖) 《高中数学自动化测试系统》紧扣高中数学新课程标准,适合高中学生在家自行练习使用。其题库总题量达到3000多题。 《高中数学自动化测试系统》的优势在于 1、紧扣新课程标准...

    《MyEclipse 6 Java 开发中文教程》前10章

    第三章 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 v9.5.140605 免费正式版本(utf-8).rar

    KesionCMS打破CMS系统瓶颈,系统自带功能强大的BBS,不再为了整合第三方论坛平台而烦恼,一站式用户登录,系统还集成腾讯QQ,新浪微博及支付宝快捷登录,只需绑定下帐户,以后可以直接用QQ号或支付宝帐户登录。...

    KesionCMS v9.5.140605 免费正式版(gbk).rar

    KesionCMS打破CMS系统瓶颈,系统自带功能强大的BBS,不再为了整合第三方论坛平台而烦恼,一站式用户登录,系统还集成腾讯QQ,新浪微博及支付宝快捷登录,只需绑定下帐户,以后可以直接用QQ号或支付宝帐户登录。...

    为我们的世界建模(GIS必备)-ESRI出品

    主要回答了5个方面的问题:如何设计一个合适的地理数据库,如何无需编写代码定制数据库,如何管理复杂工程中的工作流,如何建模河流、道路、电力线路等各种线性系统,如何集成卫星影像用于地理分析和表达,如何利用...

Global site tag (gtag.js) - Google Analytics