自定义ClassLoader时,一般来说,只需要重写findClass、findResource两个方法即可。findClass方法就是根据class名找到其class文件的字节数据,调用defineClass来生成一个class对象;而findResource就是根据资源名,返回资源所在的URL即可。做到这些真的就可以了吗?
最近遇到了一个问题,将一个Jar包解压后,在命令行使用java命令来加载启动类,可以正常运行;而换成自定义的ClassLoader时即只运行了一部分就退出了,没有错误信息,只有一个程序异常退出的提示。而此自定义ClassLoader在一个比较复杂的项目上已经使用一段时间了,没有发现什么问题,而运行另一个项目时,却出现的莫名的错误。
没有错误信息只能进行断点调试,跟踪到底是什么地方发生了异常而退出,遇到断点到一个日志的赋值语句。赋值语句看起来是没什么问题的,但查看TypeDescription.class.getPackage()值却是空。点进去分析发现,最终是通过ClassLoader来获取的package信息,看来是ClassLoader的问题。
private static final Logger log = Logger.getLogger(TypeDescription.class.getPackage().getName());
Yaml yaml = new Yaml(new Constructor(AppProperties.class));
//
public Constructor(Class<? extends Object> theRoot) {
this(new TypeDescription(checkRoot(theRoot)));
}
private static Class<? extends Object> checkRoot(Class<? extends Object> theRoot) {
if (theRoot == null) {
throw new NullPointerException("Root class must be provided.");
} else {
return theRoot;
}
}
public class TypeDescription {
private static final Logger log = Logger.getLogger(TypeDescription.class.getPackage().getName());
查看ClassLoader的getPackage是有实现的,自定义ClassLoader应该不用再重写此方法,根据packages变量找到了definePackage方法,此方法在ClassLoader里也已实现。添加日志发现所有的getPackage返回值都为null,而definePackage方法没有被调用。如此看来,definePackage是需要手动调用的,查看URLClassLoader内部实现,发现其也手动调用了definePackage方法。
在findClass方法里添加对definePackage的调用后,程序运行正常。
总结: 自定义ClassLoader要实现
- findClass方法
- findResource方法
findClass要调用
- definePackage方法
- defineClass方法