插件问题回答第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包中,所有类的路径和名称不能相同的说法是不正确的。


你可能感兴趣的:(java,工作,.net,Access,D语言)