equinox的jar包本身就有Main-Class,可以直接运行,例如:
java -jar org.eclipse.osgi-3.5.1.R35x_v20090827.jar
但是这种运行方式,需要在configuration/config.ini文件里面配置osgi.bundles参数,要把每个需要启动的bundle名填进去,这么手残的方式显然不科学。
所幸可以从外部启动equinox,只要EclipseStarter.run就可以了。
在这之前,需要配置好参数。可以写段代码,搜索某个指定目录(我用的目录名plugins)下的全部jar包作为bundle,然后构造出 osgi.bundles 这个参数。
例如:
package test;
import java.io.File;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.core.runtime.adaptor.EclipseStarter;
import org.eclipse.osgi.framework.internal.core.FrameworkProperties;
public class Main {
public static void main(String[] args) throws Exception {
String pluginPath = new File("").getCanonicalPath() + File.separator
+ "plugins";
FrameworkProperties.setProperty("osgi.noShutdown", "true");
FrameworkProperties.setProperty("eclipse.ignoreApp", "true");
FrameworkProperties.setProperty("osgi.bundles.defaultStartLevel", "4");
FrameworkProperties.setProperty("osgi.bundles", getPlugins(pluginPath));
FrameworkProperties.setProperty("osgi.syspath", pluginPath);
File config = new File(new File("").getCanonicalPath() + File.separator
+ "configuration");
if (config.exists()) {
deleteDirectory(config.getCanonicalPath());
}
EclipseStarter.run(new String[] { "-configuration", "configuration",
"-console" }, null);
}
private static void deleteDirectory(String sPath) {
// 如果sPath不以文件分隔符结尾,自动添加文件分隔符
if (!sPath.endsWith(File.separator)) {
sPath = sPath + File.separator;
}
File dirFile = new File(sPath);
// 如果dir对应的文件不存在,或者不是一个目录,则退出
if (!dirFile.exists() || !dirFile.isDirectory()) {
return;
}
// 删除文件夹下的所有文件(包括子目录)
File[] files = dirFile.listFiles();
for (int i = 0; i < files.length; i++) {
// 删除子文件
if (files[i].isFile()) {
deleteFile(files[i].getAbsolutePath());
} // 删除子目录
else {
deleteDirectory(files[i].getAbsolutePath());
}
}
dirFile.delete();
}
private static void deleteFile(String sPath) {
File file = new File(sPath);
// 路径为文件且不为空则进行删除
if (file.isFile() && file.exists()) {
file.delete();
}
}
private static String getPlugins(String pluginPath) {
StringBuilder sb = new StringBuilder();
File dir = new File(pluginPath);
Pattern pat = Pattern.compile("-[\\d]+\\.[\\d]+\\.[\\d]+");
for (File file : dir.listFiles()) {
String fileName = file.getName();
if (fileName.endsWith(".jar")) {
Matcher mat = pat.matcher(fileName);
if (mat.find()) {
sb.append(fileName.substring(0, mat.start())).append(
"@start,");
}
}
}
return sb.substring(0, sb.length() - 1);
}
}
在导出jar包的时候,遇到一些问题。以前真没注意。
eclipse导出Runnable jar时,会把全部依赖一起加到这个jar包里面来,让这个jar包不依赖其他任何jar包,就能独立运行。
这显然不是我需要的。
而导出一般的jar包,你还是可以选Main-Class。在MENIFEST.MF中,会产生这么一个属性。它其实也是Runnable的,只是需要依赖。
比如我导出的jar包名字叫start.jar,然后我就用这么一个命令企图让它运行起来:
java -cp org.eclipse.osgi-3.5.1.R35x_v20090827.jar -jar start.jar
失败了。。。。NoClassDefFoundError。。。
查了一会儿才知道,java命令在运行jar包的时候,是忽略掉classpath参数的。这样的逻辑:假如classpath后面跟了两个jar包,都输出了同一个类,而类加载器只要找到第一个类,就认了。那么运行时就可能出现有悖于编程者意图情况。所以干脆不支持classpath大混杂的参数。
要让它运行起来,需要在MENIFEST.MF中,加入Class-Path参数。
比如Class-Path: org.eclipse.osgi-3.5.1.R35x_v20090827.jar
如果有多个依赖,用空格分开。
好了现在可以用命令行轻松启动许多bundle了。
然后看一下类加载器的情况:
package test;
public class Util {
public static void pringClassLoader(Object obj) {
ClassLoader cl = obj.getClass().getClassLoader();
while (cl != null) {
System.out.println(cl);
cl = cl.getParent();
}
}
}
在三个地方打印:
EclipseStarter启动
某自产bundle1
某自产bundle2
得到如下结果:
start sys bundle :
sun.misc.Launcher$AppClassLoader@1dd3812
sun.misc.Launcher$ExtClassLoader@8c436b
start producer bunde :
org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader@8cc09a
org.eclipse.osgi.baseadaptor.BaseAdaptor$ParentClassLoader@928095
start consumer bundle :
org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader@b8db77
org.eclipse.osgi.baseadaptor.BaseAdaptor$ParentClassLoader@928095
随便分析一下,EclipseStarter这个地方,是java程序入口,由AppClassLoader加载毫无疑问。
另外俩自产bundle,类加载器都是DefaultClassLoader,并且实例不同,但是它们的父加载器BaseAdaptor$ParentClassLoader却是相同的实例。
这就为应用程序提供了一个基本的设计思路,工具类、不需要启动、不需要拆装的jar包,可以由统一父类加载,被任意bundle调用。而各个业务模块,为不同加载器,被osgi隔离。