再来看一下自定义的MyClassLoader.java:
- package com.loadclass.demo;
-
- import java.io.ByteArrayOutputStream;
- import java.io.FileInputStream;
- import java.io.FileOutputStream;
- import java.io.InputStream;
- import java.io.OutputStream;
-
- /**
- * 自定义的类加载器
- * @author Administrator
- */
- public class MyClassLoader extends ClassLoader{
-
- //需要加载类.class文件的目录
- private String classDir;
-
- //无参的构造方法,用于class.newInstance()构造对象使用
- public MyClassLoader(){
- }
-
- public MyClassLoader(String classDir){
- this.classDir = classDir;
- }
-
- @SuppressWarnings("deprecation")
- @Override
- protected Class<?> findClass(String name) throws ClassNotFoundException {
- //class文件的路径
- String classPathFile = classDir + "/" + name + ".class";
- try {
- //将class文件进行解密
- FileInputStream fis = new FileInputStream(classPathFile);
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- encodeAndDecode(fis,bos);
- byte[] classByte = bos.toByteArray();
- //将字节流变成一个class
- return defineClass(classByte,0,classByte.length);
- } catch (Exception e) {
- e.printStackTrace();
- }
- return super.findClass(name);
- }
-
- //测试,先将ClassLoaderAttachment.class文件加密写到工程的class_temp目录下
- public static void main(String[] args) throws Exception{
- //配置运行参数
- String srcPath = args[0];//ClassLoaderAttachment.class原路径
- String desPath = args[1];//ClassLoaderAttachment.class输出的路径
- String desFileName = srcPath.substring(srcPath.lastIndexOf("\\")+1);
- String desPathFile = desPath + "/" + desFileName;
- FileInputStream fis = new FileInputStream(srcPath);
- FileOutputStream fos = new FileOutputStream(desPathFile);
- //将class进行加密
- encodeAndDecode(fis,fos);
- fis.close();
- fos.close();
- }
-
- /**
- * 加密和解密算法
- * @param is
- * @param os
- * @throws Exception
- */
- private static void encodeAndDecode(InputStream is,OutputStream os) throws Exception{
- int bytes = -1;
- while((bytes = is.read())!= -1){
- bytes = bytes ^ 0xff;//和0xff进行异或处理
- os.write(bytes);
- }
- }
-
- }
这个类中定义了一个加密和解密的算法,很简单的,就是将字节和oxff异或一下即可,而且这个算法是加密和解密的都可以用,很是神奇呀!
当然我们还要先做一个操作就是,将ClassLoaderAttachment.class加密后的文件存起来,也就是在main方法中执行的,这里我是在项目中新建一个class_temp文件夹用来皴法加密后的class文件:
同时采用的是参数的形式来进行赋值的,所以在运行的MyClassLoader的时候要进行输入参数的配置:右击MyClassLoader->run as -> run configurations
第一个参数是ClassLoaderAttachment.class文件的源路径,第二个参数是加密后存放的目录,运行MyClassLoader之后,刷新class_temp文件夹,出现了ClassLoaderAttachment.class,这个是加密后的class文件。
下面来看一下测试类:
- package com.loadclass.demo;
-
- import java.util.Date;
- import java.util.List;
-
- /**
- * 测试类
- * @author Administrator
- */
- public class ClassLoaderTest {
-
- @SuppressWarnings("rawtypes")
- public static void main(String[] args){
- //输出ClassLoaderText的类加载器名称
- System.out.println("ClassLoaderText类的加载器的名称:"+ClassLoaderTest.class.getClassLoader().getClass().getName());
- System.out.println("System类的加载器的名称:"+System.class.getClassLoader());
- System.out.println("List类的加载器的名称:"+List.class.getClassLoader());
-
- System.out.println("默认的类加载器:"+ClassLoaderTest.class.getClassLoader().getSystemClassLoader());
-
- ClassLoader cl = ClassLoaderTest.class.getClassLoader();
- while(cl != null){
- System.out.print(cl.getClass().getName()+"->");
- cl = cl.getParent();
- }
- System.out.println(cl);
-
- try {
- Class classDate = new MyClassLoader("class_temp").loadClass("ClassLoaderAttachment");
- Date date = (Date) classDate.newInstance();
- //输出ClassLoaderAttachment类的加载器名称
- System.out.println("ClassLoader:"+date.getClass().getClassLoader().getClass().getName());
- System.out.println(date);
- } catch (Exception e1) {
- e1.printStackTrace();
- }
- }
-
-
-
-
- }
运行ClassLoaderTest类,运行结果如下:
ClassLoaderAttachment类的加载器是我们自己定义的类加载器MyClassLoader,同时也输出了Hello ClassLoader字段
到此不要以为结束了,这里还有很多的问题呀,看一下问题的结果是没有问题,但是这里面还有很多的东西需要去理解的,首先来看一下,按照我们之前说的类加载机制,应该是先交给父级的类加载器,AppClassLoader->ExtClassLoader->BootStrap,ExtClassLoader和BootStrap没有找到ClassLoaderAttach.class,但是AppClassLoader类加载器应该能找到呀,可以为什么也没有找到呢?这时因为loadClass方法在使用系统类加载器的时候需要传递全称(包括包名),我们传递ClassLoaderAttachment的话,AppClassLoader也是没有找到这个ClassLoaderAttachment,所以还是MyClassLoader处理了,不信的话可以试一下:
现在将
Class classDate = new MyClassLoader("class_temp").loadClass("ClassLoaderAttachment");
改成:
Class classDate = new MyClassLoader("class_temp").loadClass("com.loadclass.demo.ClassLoaderAttachment");
结果运行:
这时候的加载器是AppClassLoader了,所以要注意loadClass方法传递的参数
到这里我们貌似还没有测试到我们加密后的class文件,我们现在将工程目录中的ClassLoaderAttachment.class删除,将class_temp中加密的ClassLoaderAttachment.class拷贝过去,然后再运行:
这时候就会报错了,不合适的魔数错误(class文件的前六个字节是个魔数用来标识class文件的),这时候就对了,因为ClassLoaderAttachment.class使我们加密后的class文件,AppClassLoader是不认识的,所以报这个错误了,只有用我们自己定义的类加载器来进行解密才可以正确的访问的。到这里总算是说完了,搞了一上午,头都写大了,很是麻烦呀!
注意的两个问题:
1.可能在测试的过程中有这样的情况就是ClassLoaderTest类并没有执行,这个是因为在第一个测试的时候,将ClassLoaderTest类打成.jar放到jre目录中了,所以你后续修改ClassLoaderTest类的话,运行没有效果,因为它加载的类还是jre中的jar中的ClassLoaderTest类,所以你应该将jre中的jar删除即可。
2.就是ClassLoaderAttachment只要保存就会编译成.class文件,所以你在拷贝ClassLoaderAttachment.class文件的时候要注意了。