2005年12月30日星期五

Class.forName和ClassLoader.loadClass的区别

Class的装载分了三个阶段,loading,linking和initializing,分别定义在The Java Language Specification的12.2,12.3和12.4。
Class.forName(className)实际上是调用Class.forName(className, true, this.getClass().getClassLoader())。注意第二个参数,是指Class被loading后是不是必须被初始化。
ClassLoader.loadClass(className)实际上调用的是ClassLoader.loadClass(name, false),第二个参数指出Class是否被link。区别就出来了。Class.forName(className)装载的class已经被初始化,而ClassLoader.loadClass(className)装载的class还没有被link。
一般情况下,这两个方法效果一样,都能装载Class。但如果程序依赖于Class是否被初始化,就必须用Class.forName(name)了。
例如,在JDBC编程中,常看到这样的用法,Class.forName("com.mysql.jdbc.Driver"),如果换成了getClass().getClassLoader().loadClass("com.mysql.jdbc.Driver"),就不行。
为什么呢?打开com.mysql.jdbc.Driver的源代码看看,
//
// Register ourselves with the DriverManager
//
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
原来,Driver在static块中会注册自己到java.sql.DriverManager。而static块就是在Class的初始化中被执行。所以这个地方就只能用Class.forName(className)。

eclipse常用快捷键

Atl + / 代码提示
Ctrl + / 注释/取消注释
Ctrl + D 删除光标所在行
Ctrl + Shift + F 代码格式化
Ctrl + Shift + O 优化import
Ctrl + Shift + M 导入import
Ctrl + 鼠标单击 跟踪方法和类的源代码
Alt + <- -> 跳转
双击括号 选择括号内的所有内容
Ctrl + 1 快速重构

2005年12月28日星期三

整型常量前加0,该常量被视为八进制

整型常量前加0,该常量被视为八进制。《C陷阱与缺陷》中看到的,Java里也是这样。原来真的不知道,比较弱。 int octal = 0100;
int num = 100;
System.out.println(octal);
System.out.println(num); 运行结果:
64
100

2005年12月27日星期二

vim使用小技巧

1. 使用/pattern搜索,可以再使用*继续查找整个word
2. % 匹配 {} 和 ()
3. [{ 跳到 { start of the current code block
4. gd 跳到变量的定义
5. * find整个word
6. n next find next
7. . (dot) 重复上一次的change
8. ctrl _ N 自动完成
9 缩进 << >>

首先设置
:set shiftwidth=4
:set cindent

2005年12月17日星期六

使用JNI时,装载本地库的小技巧

刚到新公司,就接到一个棘手的任务。想了很多办法,最后想使用Java COM桥来解决。JACOB是一个较成熟的开源项目,可以很方便的调用COM组件。搞过JNI的都知道,本地库要放到系统path中,这样,Java进程在运行中才能找到本地库并动态加载。我们可以通过环境变量System.getProperty("java.library.path")来查看当前JVM搜索本地库的路径。 这时,就会遇到一个问题,部署应用的时候要记住将本地库拷贝到环境变量path指定的路径中。一般在windows平台上直接copy到C:\WINDOWS\System32目录下了事。但要换一台机器部署怎么办?除了要把Java程序拿过去,还要记的把本地库也copy到正确的目录,真麻烦。于是想看看有什么好办法来解决这个问题。 首先,最容易想到的是,把本地库和class文件放在一起,利用Class.getResource(str)找到路径,然后加到环境java.library.path中:
java代码: URL url = Foo.class.getResource("Foo.class");
String path = (new File(url.getPath())).getParent();
System.setProperty("java.library.path", path);
看上去很好,但却不能工作。查了一下ClassLoader的源代码,原来它把搜索路径定义为静态变量并只初始化一次,后面再设置java.library.path就没有用了。ClassLoader代码片断:
java代码: // 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");
}
正在一筹莫展是,翻看JACOB的源代码,忽然有了惊喜的发现。
java代码: try
{
//Finds a stream to the dll. Change path/class if necessary
InputStream inputStream = getClass().getResource("/jacob.dll").openStream();
//Change name if necessary
File temporaryDll = File.createTempFile("jacob", ".dll");
FileOutputStream outputStream = new FileOutputStream(temporaryDll);
byte[] array = new byte[8192];
for (int i = inputStream.read(array); i != -1; i = inputStream.read(array)) {
outputStream.write(array, 0, i);
}
outputStream.close();
temporaryDll.deleteOnExit();
System.load(temporaryDll.getPath());
return true;
}
catch(Throwable e)
{
e.printStackTrace();
return false;
}
高,真是好办法。和我一样,把dll放在classpath中,用Class.getResource(str).openStream()读取这个dll,然后写到temp目录中,最后用System.load(path)来动态加载。多说一句,为什么得到了jacob.dll的URL不直接去加载呢?想想看,如果把dll和class一起打成Jar包,ClassLoader还是不能加载本地库,因为System.load(path)需要的是dll的完整路径,但并不支持jar协议。还不明白,看看下面的代码:
java代码: URL url = Foo.class.getResource("/java/lang/String.class");
System.out.println(url.toExternalForm());
System.out.println(url.getFile());
在我的机器上的运行结果是:
java代码: 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
ClassLoader中用new File(name),当然会找不到文件。同时,看看我的第一种方法,就算能设置成功环境java.library.path,如果dll是在jar包中,还是加载不了。 到现在,你是不是觉得问题已经解决了?还没呢!jacob的很多源文件中已经写了下面的代码:
java代码: static {
System.loadLibrary("jacob");
}
除非我去掉这一句重新编译jacob的源代码,否则系统还是会报错。不过,既然有了上面的想法,稍微变通一下,就可以巧妙的解决。首先找到环境java.library.path,然后把dll拷贝到其中一个路径中就行了。
java代码: 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 = Foo.class.getResource("jacob.dll").openStream();
final File dllFile = new File(new File(path), "jacob.dll");
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 jacob.dll error!", e);
}
}
唯一的美中不足,在系统关闭的时候删除dll总是不能成功,试了两种办法都不行。想想也对,dll正被程序使用,当然不能删除。翻了一下API,Java好像没用提供unload本地库的功能,只好做罢。
解决了这么个小问题,罗罗嗦嗦一大篇,罪过罪过。后来这个项目又没有使用jacob,真对不起各位观众。

2005年12月10日星期六

今天去购书中心的收获

呵呵,一本书都没买,一直站着看书,腿都酸了:)
1.《Java多线程设计模式》,日本人写的,内容浅显实用,总结的不错。Future模式,搞明白了我前一阵的疑问。准备用Asynchronous IO写一个简单的HttpServer,对比一下几个IO库的性能。
IBM aio4j
apache MINA
Coconut AIO

2. 《Java测试与设计——从单元测试到自动Web测试》发现了一个很好的自动测试工具TestMaker,而且是开源的,有时间一定要研究一下。

3. 《More Java Pitfalls》学习了其中一章关于logging API使用的陷阱。看下面的这张图,有种豁然开朗的感觉。一般,只在root logger上注册Handler,并把Handler的level设为ALL,这样设置root logger的level就可以控制最终输出的级别。而且,继承自root的logger可以进一步设置level控制日志的输出。系统应该按照功能来设计logger的继承结构。
From blog

4. 准备去新的公司了,要学的东西太多了。
计划看的书:《精通Hibernate》《精通Struts》还要熟悉Websphere和Eclipse。

2005年12月8日星期四

jdk环境设置

set ant_home=D:\javaapp\apache-ant-1.6.2
set java_home=D:\jdk1.5.0
rem set java_home=D:\jdk1.4.2
set path=%java_home%\bin;%ant_home%\bin
这样,可以指定JDK的版本,并使用ant进行编译,满足大部分的要求。