java类加载器 架构 设计_自定义类加载器的编写,及分析器原理(附源码)

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

8072791_201407280642070092.jpg

8072791_201407280642070389.jpg

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

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

1.自定义的类加载器必须继承classLoader。2.了解ClassLoader中的loadClass方法和findClass方法,以及defineClass方法。

8072791_201407280651390780.jpg

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

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

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

8072791_201407280709310733.jpg

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

8072791_201407280709410983.jpg

defineClass文件:将二进制文件转换成class文件字节码文件。

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

8072791_201407280714400327.jpg

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

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

下面我们一步一步来:

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

8072791_201407290703070670.jpg

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

8072791_201407290703080061.jpg

第三步:为ClassLoaderAttachment.java文件加密,加密后的文件保存到testlib目录下。

8072791_201407290703080452.jpg

整体项目结构如下图:

8072791_201407310723280893.jpg

在项目的根目录下创建了一个文件夹testlib,用来存放加密后的文件。我们在运行这个加密程序。传递两个参数进去。一个是要加密的文件。一个是加密后文件保存的路径

第一个:E:\Workspaces\MyEclipse8.6-tomcat6.0-jdk6.0\classloader\bin\com\ClassLoaderAttachment.class  我们要对ClassLoaderAttachment.class文件进行加密第二个:testlib   加密后的文件放在testlib目录下。这个目录是个相对路径。

8072791_201408031644410206.jpg

运行后我们看到在testlib文件加下多了一个文件,这个文件就是加密后的文件。

8072791_201407310723210065.jpg

下面我们来测试一下这个文件:我们在ClassLoaderTest.java文件中测试一个ClassLoaderAttachment文件,没有加密前的输出效果是:

8072791_201407310731290112.jpg

要想查看 加密后的效果:只需要将加密的class文件,替换AppClassLoader中的未加密文件即可。(普通的class文件都是有AppClassLoader加载器加载的)

8072791_201407310734290815.jpg

再来查看一下运行效果:

8072791_201408031506390347.jpg

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

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

8072791_201408031513300972.jpg

下面来编写我们自己的类加载器。

第一步:继承自ClassLoader类。

8072791_201408031620220128.jpg

第二步重写:定义构造方法,接受传递过来的参数。参数是解密文件的路径

8072791_201408031621120753.jpg

第三步:重写ClassLoader类的findClass()方法。

8072791_201408031621410331.jpg

第四步:在ClassLoaderTest中编写测试方法。

8072791_201408031622290675.jpg

下面我们来看一下效果:首先在父类加载器中,如果没有找到ClassLoaderAttachment.class文件,则去自己定义的类加载器MyClassLoader中查找,如果找到了,则加载父类自己的这个文件。

我们来看看这两种效果:第一种:父类中有这个类。

8072791_201408031627250206.jpg

这是个时候去加载这个类。

8072791_201408031627520347.jpg

报错了,因为在父类中找到这个文件,而这个文件已经被加密。父类加载器没有对其进行解密。

第二种情况,父类加载器中没有这个类,而这个类是在我的指定文件夹中

8072791_201408031632570940.jpg

在testlib文件夹下有这个文件

8072791_201408031632580112.jpg

8072791_201408031632580534.jpg

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

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

有包名的类,不能调用无包名的类。

==================================================================源码:

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;

}

}

项目目录结构:

8072791_201408031829460112.jpg

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值