最近接手实习生写的一个项目A,主要是往HDFS里写文件,因为项目比较急所以代码写的很乱,我就顺便新建个项目B重构一下。
其中把对HDFS的操作封装成了一个类:
public class HDFS {
private static final String HDFS_ADDR = Config.HDFS_PATH;
private static FileSystem fs;
static {
Configuration configuration = new Configuration();
configuration.set("dfs.replication", Config.REPLICATION);
configuration.set("fs.defaultFS", HDFS_ADDR);
try {
fs = FileSystem.get(URI.create(HDFS_ADDR), configuration);
} catch (IOException e) {
e.printStackTrace();
}
}
private Path path;
private FSDataOutputStream out;
public HDFS(String file) {
if (file != null && file.length() > 0) {
init(file);
}
}
.....
}
供其他模块调用,我在重构时候基本就照搬了这一块,后来发现我的程序每次执行
new HDFS(string);
的时候就停止了,也没报错。
Debug的话在构造函数第一行打印语句都不执行,那么说明不是构造函数本身的问题,回想起之前看的类的生命周期,在构造函数之前那就是初始化阶段了,也就是类加载时候自动执行的。
于是我就把目光集中在静态初始化阶段,依旧是靠打印语句,在
Configuration configuration = new Configuration();
之前是可以打印的,后面的就不行,可这么简单一句能有什么问题呢?而且明明他的程序是可以运行的,也就是客户端代码应该是没问题。
为了验证这一点,我又新建了一个空项目C,然后把静态初始化这段代码放到main里面执行,然后马上就抛出ClassNotFound异常了:
显示是在new Configuration的时候抛出的,我看了一眼依赖的库,确实是没有的,但是之前我的工程B也没有抛出异常啊,这两个工程BC导入的库是一样的。
唯一一种可能就是:原来的工程B本应该报错却没报,这也太不正常了,找不到类是Java常见的异常之一啊,怎么可能忽视呢?
这时候我想起了之前看effective系列的时候一条经验,说是不要在构造函数里面做太多事,而是单独写一个init()来做初始化的工作,因为构造函数中不能抛异常。
莫非是因为这个问题,连构造函数都不能抛异常,更别说有更高优先级的静态初始化了,于是我把静态初始化这段改成了这样:
static {
try {
Configuration configuration = new Configuration();
configuration.set("dfs.replication", Config.REPLICATION);
configuration.set("fs.defaultFS", HDFS_ADDR);
fs = FileSystem.get(URI.create(HDFS_ADDR), configuration);
} catch (IOException e) {
e.printStackTrace();
}
}
马上就抛出异常了,至此证明确实是之前就有错。
然后说一下为什么新建的B工程缺少类而A工程不缺,新建工程的过程中有一个步骤就是导入依赖的jar包,之前的项目A是吧hadoop/share目录下所有的jar全添加进来了,我为了图省事,只加了几个common和core的jar:
所以自然就报错了。