分享

自定义类加载器的编写,及分析器原理(附源码)

 I_T_馆 2014-08-03
我们已经学习完了类加载器的委托机制,类加载器工作的原理。那么,现在我们来编写一个 自己的类加载器。

 
 
 我们要写一个MyClassLoader,把MyClassLoader放到AppClassLoader的下面去。我们要用MyClassLoader去加载一个特定目录下的类。我们把我们自己洗的类放在一个特定的目录下,这样,其他的类加载器就加载不了了,只能被我们的类加载器加载,并且,我们的这些类还要加密。让被人将我们的程序拷回家了也运行不起来,只能用我们的类加载器才能运行起来。因为我们的类加载器可以对这个文件的内容解密。


在编写这个类加载器之前,我们必须知道的基础知识。

1. 自定义的类加载器必须继承classLoader。
2. 了解ClassLoader中的loadClass方法和findClass方法,以及defineClass方法。
 
 为loadClass方法传递一个二进制类名称,么类加载器会试图查找或生成构成类定义的数据。那么我们要编写自己的类加载器是否要重写loadClass呢?不用。因为这个类加载器内部会去找他的爸爸。当爸爸返回来以后,接着会调用findClass。我们只要重写findClass就可以了。这样,这个类加载器还有委托机制,还会去找他的爸爸,找完爸爸不行,再回来找自己的findClass。如果你覆盖了loadClass,就是完全自己干了,不去找爸爸了。或者,你去找爸爸,还要写上找爸爸的代码。findClass的作用就是自己干,你所需要做的事情就是重写findClass。这就是模板方法设计模式

总结:我们要覆盖的是findClass,而不是loadClass。如果复写了loadClass,那找父类的方法就被我们干掉了。我们只是局部细节需要自己干。

findClass:用指定的二进制名称,查找类,也就是class文件。

 
 当我们得到了class文件里面的二进制数据,那么怎样把二进制数据转换成字节码,这是接下来要做的事。这个事情,我们可以通过复写defineClass来完成。
 
 
defineClass文件:将二进制文件转换成class文件字节码文件。

API文档提供了一个简单的例子,我们来看一下。
 
 

以上就是类加载器的原理。

我们这次做的要稍微复杂一点,要对class文件进行加密。所以我们要先写一个加密程序:

下面我们一步一步来:

第一步:编写一个要加密的类ClassLoaderAttachment.java。这个磊很简单,如下:
 

第二步:创建一个 MyClassLoader.java文件。编写一个  加密/解密的方法。具体内容如下:

 第三步:为ClassLoaderAttachment.java文件加密,加密后的文件保存到testlib目录下。
 整体项目结构如下图:
 
 
 
 

在项目的根目录下创建了一个文件夹testlib,用来存放加密后的文件。我们在运行这个加密程序。传递两个参数进去。一个是要加密的文件。一个是加密后文件保存的路径
第一个:E:\Workspaces\MyEclipse8.6-tomcat6.0-jdk6.0\classloader\bin\com\ClassLoaderAttachment.class  我们要对ClassLoaderAttachment.class文件进行加密
第二个:testlib   加密后的文件放在testlib目录下。这个目录是个相对路径。
 
 
运行后我们看到在testlib文件加下多了一个文件,这个文件就是加密后的文件。
 
 
 
 
下面我们来测试一下这个文件:
我们在ClassLoaderTest.java文件中测试一个ClassLoaderAttachment文件,没有加密前的输出效果是: 
要想查看 加密后的效果:只需要将加密的class文件,替换AppClassLoader中的未加密文件即可。(普通的class文件都是有AppClassLoader加载器加载的)
 
 

再来查看一下运行效果:
 
 
  报出文件编译异常。
那么,如果要想正确解析我们加密后的文件。该怎么办呢?就不能使用系统自带的类加载器解析了。使用我们自己的类加载器加载,并在加载的时候对文件进行解密。

如何定义我们自己的类加载器呢?上面已经提到过了,定义一个自己的类加载器,需要继承自ClassLoader类,并重写findClass方法。然后返回defineClass。写法参考API:
 
 
下面来编写我们自己的类加载器。
第一步:继承自ClassLoader类。
 

第二步重写:定义构造方法,接受传递过来的参数。参数是解密文件的路径
 
 第三步:重写ClassLoader类的findClass()方法。
 
第四步:在ClassLoaderTest中编写测试方法。
下面我们来看一下效果:首先在父类加载器中,如果没有找到ClassLoaderAttachment.class文件,则去自己定义的类加载器MyClassLoader中查找,如果找到了,则加载父类自己的这个文件。 

我们来看看这两种效果:
第一种:父类中有这个类。
 
 这是个时候去加载这个类。
  
 
报错了,因为在父类中找到这个文件,而这个文件已经被加密。父类加载器没有对其进行解密。
第二种情况,父类加载器中没有这个类,而这个类是在我的指定文件夹中
 
 在testlib文件夹下有这个文件

 
 正确解析了这个文件,因为这个类是通过我自己的类加载器加载的,我自己定义的类加载对这个类进行了解密。

==================================================================
注意:

有包名的类,不能调用无包名的类。
==================================================================
源码:

ClassLoaderAttachment:
-----------------------------------
package com;

import java.util.Date;

/**
 * 这个文件是要加密的文件
 * 
 * 这个文件只是复写了一个Date的toString方法
 * 
 * 这里要继承Date,原因是:在使用我们自己定义的类加载器加载到类,实例化这个类以后.我们需要用这个父类来
 * 接受实例化的类,不能出现ClassLoaderAttachment,因为已经被加密,编译器不能识别
 */
public class ClassLoaderAttachment extends Date{

private static final long serialVersionUID = 1L;

public String toString(){
return "Hello, itcast";
}
}
==================================================================

ClassLoaderTest:
-----------------------------------
package com;

import java.util.Date;

public class ClassLoaderTest {

public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
/**
* 获得一个classpath下类的类加载器
*/
/*
*  获取这个类的字节码,在获取字节码的类加载器,字节码也是类呀,
*  也有class字节码,这就获得了类加载器的类型,然后再getName,获取类加载器的类名
*/
//System.out.println(ClassLoaderTest.class.getClassLoader().getClass().getName());
/**
* 看看另外一个类加载器是由谁加载的呢?
*/
//System.out.println(System.class.getClassLoader());
/**
* 打印ClassLoaderTest的类加载器,父类加载器
*/
/*ClassLoader loader = ClassLoaderTest.class.getClassLoader();
while(loader != null){
System.out.println(loader.getClass());
//获取当前类加载器的父类
loader=loader.getParent();
}
//到根节点了,就是BootStrap,他的loader=null,直接打印出来
System.out.println(loader);
*/
testClassLoaderCpher();
}
/**
* 测试ClassLoaderAttachment.class文件
* 父类加载器中没有这个文件,则从自己的类加载器中加载
*/
public static void testClassLoaderCpher() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
//获取了要加载的类ClassLoaderAttachment.
Class clazz = new MyClassLoader("testlib").loadClass("com.ClassLoaderAttachment");
/*
* 初始化这个类,调用他的toString方法
* 我们知道这个类是ClassLoaderAttachment.所以实例化以后返回的类应该是什么?
* 对,是ClassLoaderAttachment.但我们可以这么写么?
* ClassLoaderAttachment cla = (ClassLoaderAttachment)clazz.newInstance();
* 答案是不可以,因为ClassLoaderAttachment类被加密了,编译器不能识别他.如果这么写会报
* 不能编译该文件错误.那应该如何写呢?他不是继承了一个父类么?使用父类来接受
*/
Date d1 = (Date)clazz.newInstance();
System.out.println(d1);
}
}
==================================================================

MyClassLoader
-----------------------------------
package com;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * 定义一个类加载器,同时这里还有对类的加密解密的算法。
 * @author luoxl
 *
 */

public class MyClassLoader extends ClassLoader{

/**
* 将ClassLoaderAttachment文件加密,加密后的文件放到根目录下的testlib文件夹下.
*/
public static void main(String[] args) throws IOException {
/**
* 现在要调用加密的类,对某个class文件进行加密.
* 那么要传递一个要加密文件及路径,和要保存加密文件及路径
*/
//源文件的路径
String srcPath = args[0];
//目标文件的目录
String descDir = args[1];
//目标文件名称
String descFileName = srcPath.substring(srcPath.lastIndexOf("\\"));
//目标文件的路径(目录+文件名称)
String descPath = descDir + descFileName;
FileInputStream fis = new FileInputStream(srcPath);
FileOutputStream fos = new FileOutputStream(descPath);
//调用加密方法
cypher(fis,fos);
//关闭文件流
fis.close();
fos.close();
}
/**
* 这是一个加密/解密算法类
* 这里ips输入流,ops输出流定义的都是父类,是为了更好的兼容.
*/
private static void cypher(InputStream ips,OutputStream ops) throws IOException{
/**
* 加密,无非就是读取文件流。
*/
int b = -1;//定义一个字节码变量
//读取到字节码
while((b=ips.read())!=-1){//当文件读完最后一个字符,返回-1.不等于-1,就表示读到了文件流
/*
* 将读出来的字节码加密后,写入ops中
* 加密的算法是,对这个字节码文件进行"异或"运算.
* 异或运算的意思是:原来是0,异或以后就是1;原来是1,异或以后就是0.
* 那么这个算法方法就既可以是加密算法,又可以是解密算法了.
*/
ops.write(b ^ 0xff);
}
/**
* 加密算法就完成了,是不是很简单呢
*/
}
/**
* 根据传递过来的class名称,查找类。
* 使用自己的类加载器解密class文件,返回class二进制字节码类
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
/*
* 获取文件名(带路径),此文件是已经被加密的文件
* name.substring(name.lastIndexOf(".")+1):是只获取文件名.
* 例如:com.ClassLoaderAttachment--->ClassLoaderAttachment
*/
String classFileName = pathDir + "\\" + name.substring(name.lastIndexOf(".")+1) + ".class";
System.out.println(classFileName);
try {
//读取文件流
FileInputStream fis = new FileInputStream(classFileName);
//定义字节数组流
ByteArrayOutputStream bos = new ByteArrayOutputStream();
//对文件进行解密,传递一个输入流fis,输出到bos中
cypher(fis, bos);
fis.close();
//得到获得的输出流byte数组
byte[] bytes = bos.toByteArray();
//如果找到了
return defineClass(null, bytes, 0, bytes.length);
} catch (Exception e) {
e.printStackTrace();
}
//如果有异常则到父类中找
return super.findClass(name);
}
private String pathDir;
public MyClassLoader(){}
/**
* 定义一个构造方法,传递文件路径
*/
public MyClassLoader(String pathDir){
this.pathDir = pathDir;
}
}

项目目录结构:
 
 


    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多