既然类加载器也不过是一个将需要的class文件加载在内存里的普通Java类,(具体参见上一篇博客) 那么我们是否可以通过编写实现自定义Java类加载器呢?答案是肯定的。
我们参照java.lang包下的ClassLoader来编写自定义加载器。
首先定义一个Student类,这里是通过对Student这个类生成的class文件加密(在项目的bin目录下)实现绕过系统的ApplicationClassLoader,而是靠自定义的加载器加载,类里没有定义方法(因为可能自己定义的加载器有问题,类无法加载,这样没办法调用该方法),而是重写toString方法,代码如下:
public class Student {
public String toString(){
return "学习中";
}
}
然后开始新建一个类加载器MyClassLoader 继承自ClassLoader
,这里直接定义main方法进行加密,在
里面定义原来class文件所在路径,和加密后的class文件路径
(我这里是
在项目下新建一个文件夹test_lib,作为自己的class目录)代码如下:
public class MyClassLoder extends ClassLoader {
public static void main(String[] args) {
String srcPath="D:\\workbases\\java\\bin\\com\\huaxin\\classLoader\\Student.class";
String destPath="test_lib\\Studet.class";
//加密
encrypt(srcPath,destPath);
}
}
接着编写加密算法(不是重点,所以写的很简单比较简单,也方便解密)构建文件输入流 ,在读的时候对其加一,
代码如下:
//加密处理 破坏原class,使得ApplicationClassLoader无法加载
public static void encrypt(String srcPath,String destPath){
try {
InputStream in=new FileInputStream(srcPath);
OutputStream out=new FileOutputStream(destPath);
int i;
while((i=in.read())!=-1){
out.write(i+1);
}
in.close();
out.close();
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
然后运行项目,刷新可以看见在新建的
文件夹test_lib
下,有编译后生成的Student.class
文件
,
将这个文件复制拷贝到系统class文件的目录下,(就是上面的srcPath,将原来的Student.class覆盖掉)这时我们在新建一个Test类,让它去引入Student类,发现程序报错,可见系统的ApplicationClassLoader加载器已经没办法加载这个破坏后的class文件,需要使用自己的加载器进行解密,再加载,继续写自定义的加载器(注意,此时不要再动Student.java文件,不然系统很快重新编译,就无法达到使用自己的加载器的目的)
然后继续在类加载器MyClassLoader中定义解密算法
//解密处理
public static void desencrypt(String srcPath,OutputStream out){
try {
InputStream in=new FileInputStream(srcPath);
int i;
while((i=in.read())!=-1){
out.write(i-1); //还原
}
} catch (Exception e) {
// TODO: handle exception
}
}
借鉴ClassLoader的API,我们知道自定义的加载器需要重写findClass方法, 代码如下:
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException{
//检验是否触发 findClass,将name值传入
System.out.println("name="+name);
//拼接传入的路径值
String srcPath="test_lib\\"+name;
//将本地文件读到内存中 向内存中输出的流
ByteArrayOutputStream baos=new ByteArrayOutputStream();
//读到内存后进行还原
desencrypt(srcPath,baos);
//得到Student.class在内存中的字节数组
byte[] buf = baos.toByteArray();
//将字节数组得到对应的Class实例
return defineClass(buf,0,buf.length);
//return super.findClass(name);
}
然后我们在定义的T est文件中再次加载Student类,
public class Test {
public static void main(String[] args) throws Exception {
//创建自定义加载器对象(最终返回类的字节码)
Class c=new MyClassLoder ().loadClass("Studet.class");
//直接实例化对象输出 调用toString方法
System.out.println(c.newInstance().toString());
}
}
后台输出:
至此,使用自己的类加载器加载到了Student类
最后说一个尴尬的地方由于我在第一次加密时将保存路径destPath中的“student.class"错写成"Studet.class",解密的时候一直报类找不到异常,我还在纳闷,看了半天发现自己写错了,这真是瞎呀,为了警戒自己,也没有改,直接留下来了(其实我是懒得再截图什么的,O(∩_∩)O),经常犯这样低级的错误,下次要睁大眼睛了。