快好了,快好了,可是又冒出一个问题,花去了半天的时间,是我太浮躁,还是我太愚钝,或者两个都有,或者这些都已经不重要了……
1. 环境变量的设置
Plug-In终于调出了一个相对稳定的Version,轻松的exprot出来,然后copy到eclispse目录的plugins子目录下,再次启动Eclipse,我的宝贝插件居然告诉我“chosen operation is not currently available”。
(1)不用想,这肯定是在plug-in初始化的时候出错了。定点输出,果然Activator的构造函数没有调用。
(2)在通过一个简单的例子测试一下自己的导出过程有没有错,结果很失望,导出没错。
(3)接下来就自然而然地想到了那个曾经这么我很久的jni的本地库,结果就是这里的问题。
因为以前一直觉得环境变量设置麻烦,所以就简单地把这个jni库放在项目的根目录下,每次运行调试都不会有问题。但是当把plugin导出后,由于java library path中没有这个库,自然就是失败了。
怎么改呢?
(1)最直接的方法就是在java里利用 System.out.println(System.getProperty("java.library.path"));把java library path全部输出,再把握的plugin copy过去一份,不考虑客户的情绪,这样做无疑最简单。然而可惜,这是服务器,一共输出九个path,全都是只读的,没办法,google!
(2)以前都很少懂得网上资源是多么丰富。找到了高手的代码:
static {
try {
String libpath = System.getProperty("java.library.path");
if ( libpath==null || libpath.length() == 0 ) {
throw new RuntimeException("java.library.path is null");
}
String path = null;
StringTokenizer st = new StringTokenizer(libpath, System.getProperty("path.separator"));
if ( st.hasMoreElements() ) {
path = st.nextToken();
} else {
throw new RuntimeException("can not split library path:" + libpath);
}
InputStream inputStream = DbgServerBridge.class.getResource(FULL_NAME).openStream();
final File dllFile = new File(new File(path), FULL_NAME);
if (!dllFile.exists()) {
FileOutputStream outputStream = new FileOutputStream(dllFile);
byte[] array = new byte[8192];
for (int i = inputStream.read(array); i != -1; i = inputStream.read(array)){
outputStream.write(array, 0, i);
}
outputStream.close();
}
//dllFile.deleteOnExit();
Runtime.getRuntime().addShutdownHook(new Thread(){
public void run(){
if ( dllFile.exists() ) {
boolean delete = dllFile.delete();
System.out.println("delete : " + delete);
}
}
});
} catch (Throwable e) {
throw new RuntimeException("load library error!", e);
}
System.loadLibrary(LIBRARY_PATH);
}
把本大库放在classpath中,用Class.getResource(str).openStream()读取这个库,然后写到temp目录中,最后用System.load(path)来动态加载。
关于这段程序还有两个为什么:
第一:既然已经知道lib的url,为什么不直接加载?这是因为System.loadLibrary(path)需要的是lib的绝对路径,而且不能解析jar协议。
URL url = Foo.class.getResource("/java/lang/String.class");
System.out.println(url.toExternalForm());
System.out.println(url.getFile());
输出:
jar:file:/D:/jdk1.5.0_06/jre/lib/rt.jar!/java/lang/String.class
file:/D:/jdk1.5.0_06/jre/lib/rt.jar!/java/lang/String.class
这两个前缀System.loadLibrary是不认识的。
第二:如果把运行setProperty("java.library.path", XXX),之后再load不是更快么?
类似于这样:
URL url = Foo.class.getResource("Foo.class");
String path = (new File(url.getPath())).getParent();
System.setProperty("java.library.path", path);
这样也是不行的,这要看ClassLoader的源代码:
// The paths searched for libraries
static private String usr_paths[];
static private String sys_paths[];
...
if (sys_paths == null) {
usr_paths = initializePath("java.library.path");
sys_paths = initializePath("sun.boot.library.path");
}
因为ClassLoader把java library path的搜索结果保存在一个静态变量中,VM在启动时装载,并且以后都不在修改,再调用setProperty是没有用的。但是就有人要修改这个静态变量,这段代码在第三个解决方案中提起。
到这里,觉得这个第二种方法真的很大胆,居然在运行时先复制在加载,再于退出时删除。可是我的担心经过验证之后逼着我放弃这个方案,还是因为我的机器是服务器,那些java library path都是只读,不能创建temp file。
(3)这个方案不错,就是想方设法改变读进来的path
static {
try{
setLibrary(LIBRARY_PATH);
System.out.println(System.getProperty("java.library.path"));
System.loadLibrary(LIBRART_NAME);
}catch(Exception e){
MsgLog.WriteError(e);
}
}
private static void setLibrary(String libPath) throws IOException{
if(libPath == null){
return;
}
try {
Field field = ClassLoader.class.getDeclaredField("usr_paths"); //保存java library paths
field.setAccessible(true);
String[] paths = (String[])field.get(null);
for (int i = 0; i < paths.length; i++) {
if (libPath.equals(paths[i])) {
break;
}
}
String[] tmp = new String[paths.length+1];
System.arraycopy(paths,0,tmp,0,paths.length);
tmp[paths.length] = libPath;
field.set(null,tmp);
} catch (IllegalAccessException e) {
throw new IOException("Failed to get permissions to set library path");
} catch (NoSuchFieldException e) {
throw new IOException("Failed to get field handle to set library path");
}
}
这个方案通过反射,改变静态变量usr_paths的值,有点曲线救国的意思,不过的确是个好方法。不足之处在与usr_paths会增加程序对平台的依赖性,因为换一个VM,可能usr_paths就不是这个名字,程序就不能用了。不过权衡比较,这是觉得不错的方法,也是最终采用的方法。
(4)修改java library path
java library path主要包括LD_LIBRARY_PATH和JAVA_HOME下的一些路径
linux 下LD_LIBRARY_PATH设置方法有以下三种:
a) 临时修改,只对当前终端有效
在terminal中执行:export LD_LIBRARY_PATH=XXPATH
b) 让当前帐号以后都优先加载当前目录的动态库
修改~/.bashrc,在文件末尾加上两行: export LD_LIBRARY_PATH=XXPATH,重新登录
c) 让所有帐号从此都优先加载当前目录的动态库
修改/etc/profile在文件末尾加上两行: export LD_LIBRARY_PATH=XXPATH,重新登录
注意:$HOME 表示用户根目录 / 表示系统根目录 ./ 当前目录 ../上一级目录
分隔符 :
echo $LD_LIBRARY_PATH 回显
哎,马马虎虎总结了这么多,其中多数是在网上搜集的,少许是自己的一点心得。
发现自己还是对代码的兴趣更浓一些,总觉得设置这些繁琐无味,可是自己的编码水平有这么样呢?自己的开发效率又如何呢?