插件问题回答第3题

问题原贴: [url]http://cloverprince.iteye.com/admin/blogs/481307[/url]

[quote]3. 现有一个主程序用Java语言写成。现在要允许第三方开发人员编写扩展的类,约定第三方开发的类必须包含一个实现了某个已知接口(如interface IFooPlugin)的类,名称不限。如果要求第三方的类必须与主程序的bytecode分开发布,把.class放在classpath相应位置,或把jar丢在某个文件夹内即可被动态装载使用,应如何实现?
[/quote]

回答:

使用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属性是我自己编的。

问题出现:[b]为什么要在Manifest里面放置这个类的路径呢?[/b]

回答:因为Java的所有的包/类的组织结构,是一个公共的大树。不同的类一定拥有不同的路径。因此,不同的插件中的类,路径、类名一定不同。根据插件的定义,主程序不应该知道插件的实现细节。也就是说,主程序在编译之前,不可能知道插件的类的路径和名称。但是,要让插件工作,主程序又知道所有的插件的共同特征,想一想,如果主程序不知道插件的“任何”细节,又怎么知道jar里面哪个类才是实现了那个已知接口的类呢?

对于这个实现来说:如果将插件组织到不同的jar包中,那么一个良好的存储这一信息的地方,就是它的Manifest。读取一个jar包时,先看看它的Manifest中的这个Plugin-Class属性,就知道应该装载哪个类了。

这时,这个jar包里只有两个文件:
[quote]
META-INF/MANIFEST.MF
com/javaeye/cloverprince/HelloWorldPlugin.class
[/quote]


另一个插件也类似的制作。
一个类 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包,包含两个文件:
[quote]
META-INF/MANIFEST.MF
com/somecompanyelse/GoodbyeWorld.class
[/quote]


现在,两个插件已经有了,只差一个主程序来读取这两个插件了。
主程序:

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个文件):
[quote]
.
│ Main.class

├─com
│ └─javaeye
│ └─cloverprince
│ └─interfaces
│ PluginInterface.class

└─plugins
goodbye-3.14159265.jar
helloworld-1.0.jar
[/quote]

执行: java Main

[quote]
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
[/quote]


总结:
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包中,所有类的路径和名称不能相同的说法是不正确的。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值