我们已经学习完了类加载器的委托机制,类加载器工作的原理。那么,现在我们来编写一个 自己的类加载器。 我们要写一个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; } } 项目目录结构: |
|
来自: I_T_馆 > 《张孝祥java高新技术》