为何要花时间实现自己的ClassLoader
尽管人生的乐趣非常大一部分来自于将时间花在有意思可是无意义的事情上,可是这件事绝对是有意思并且有意义的,有下面几个情景是值得我们花费时间实现自己的classLoader的:
- 我们须要的类不一定存放在已经设置好的classPath下(有系统类载入器AppClassLoader载入的路径),对于自己定义路径中的class类文件的载入,我们须要自己的ClassLoader
- 有时我们不一定是从类文件里读取类,可能是从网络的输入流中读取类,这就须要做一些加密和解密操作,这就须要自己实现载入类的逻辑,当然其它的特殊处理也相同适用。
- 能够定义类的实现机制。实现类的热部署,如OSGi中的bundle模块就是通过实现自己的ClassLoader实现的。
理解ClassLoader类的结构
载入class文件
ClassLoader的loadClass採用双亲托付型实现。由于我们实现的ClassLoader都继承于java.lang.ClassLoader类,父载入器都是AppClassLoader。所以在上层逻辑中依然要保证该模型,所以一般不覆盖loadClass函数
protected synchronized Class<?> loadClass ( String name , boolean resolve ) throws ClassNotFoundException{
//检查指定类是否被当前类载入器载入过
Class c = findLoadedClass(name);
if( c == null ){//假设没被载入过。委派给父载入器载入
try{
if( parent != null )
c = parent.loadClass(name,resolve);
else
c = findBootstrapClassOrNull(name);
}catch ( ClassNotFoundException e ){
//假设父载入器无法载入
}
if( c == null ){//父类不能载入,由当前的类载入器载入
c = findClass(name);
}
}
if( resolve ){//假设要求马上链接,那么载入完类直接链接
resolveClass();
}
//将载入过这个类对象直接返回
return c;
}
从上面的代码中。我们能够看到在父载入器不能完毕载入任务时,会调用findClass(name)函数,这个就是我们自己实现的ClassLoader的查找类文件的规则。所以在继承后。我们仅仅须要覆盖findClass()这个函数,实现我们在本载入器中的查找逻辑,并且还不会破坏双亲托付模型
载入资源文件(URL)
我们有时会用Class.getResource():URL来获取对应的资源文件。假设仅仅使用上面的ClassLoader是找不到这个资源的,对应的返回值为null。
下面我们来看Class.getResource()的源代码:
public java.net.URL getResource(String name) {
name = resolveName(name);//解析资源
ClassLoader cl = getClassLoader();//获取到当前类的classLoader
if (cl==null) {//假设为空,那么利用系统类载入器载入
// A system class.
return ClassLoader.getSystemResource(name);
}
//假设获取到classLoader,利用指定的classLoader载入资源
return cl.getResource(name);
}
我们发现Class.getResource()是通过托付给ClassLoader的getResource()实现的,所以我们来看classLoader对于资源文件的获取的详细实现例如以下:
public URL getResource(String name) {
URL url;
if (parent != null) {
url = parent.getResource(name);
} else {
url = getBootstrapResource(name);
}
if (url == null) {
url = findResource(name);//这里
}
return url;
}
通过代码我们easy发现。也是双亲委派模型的实现,在不破坏模型的前提下,我们发现我们须要覆写的仅仅是findResource(name)函数
综上
我们在创建自己的ClassLoader时仅仅须要覆写findClass(name)和findResource()就可以
例讲ClassLoader的实现
下面的实现均基于对于ClassLoader抽象类的继承(仅仅给出对于findClass的覆写。由于常理上处理逻辑基本一致)
载入自己定义路径下的class文件
package com.company;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.ByteBuffer;
/**
* Created by liulin on 16-4-20.
*/
public class MyClassLoader extends ClassLoader {
private String classpath;
public MyClassLoader( String classpath){
this.classpath = classpath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String fileName = getClassFile( name );
byte[] classByte=null;
try {
classByte = getClassBytes(fileName);
}catch( IOException e ){
e.printStackTrace();
}
//利用自身的载入器载入类
Class retClass = defineClass( null,classByte , 0 , classByte.length);
if( retClass != null ) {
System.out.println("由我载入");
return retClass;
}
//System.out.println("非我载入");
//在classPath中找不到类文件,托付给父载入器载入,父类会返回null,由于可载入的话在
//委派的过程中就已经被载入了
return super.findClass(name);
}
/***
* 获取指定类文件的字节数组
* @param name
* @return 类文件的字节数组
* @throws IOException
*/
private byte [] getClassBytes ( String name ) throws IOException{
FileInputStream fileInput = new FileInputStream(name);
FileChannel channel = fileInput.getChannel();
ByteArrayOutputStream output = new ByteArrayOutputStream();
WritableByteChannel byteChannel = Channels.newChannel(output);
ByteBuffer buffer = ByteBuffer.allocate(1024);
try {
int flag;
while ((flag = channel.read(buffer)) != -1) {
if (flag == 0) break;
//将buffer写入byteChannel
buffer.flip();
byteChannel.write(buffer);
buffer.clear();
}
}catch ( IOException e ){
System.out.println("can't read!");
throw e;
}
fileInput.close();
channel.close();
byteChannel.close();
return output.toByteArray();
}
/***
* 获取当前操作系统下的类文件合法路径
* @param name
* @return 合法的路径文件名称
*/
private String getClassFile ( String name ){
//利用StringBuilder将包形式的类名转化为Unix形式的路径
StringBuilder sb = new StringBuilder(classpath);
sb.append("/")
.append ( name.replace('.','/'))
.append(".class");
return sb.toString();
}
public static void main ( String [] args ) throws ClassNotFoundException {
MyClassLoader myClassLoader = new MyClassLoader("/home/liulin/byj");
try {
myClassLoader.loadClass("java.io.InputStream");
myClassLoader.loadClass("TestServer");
myClassLoader.loadClass("noClass");
}catch ( ClassNotFoundException e ){
e.printStackTrace();
}
}
}
结果例如以下:
从结果我们看,由于我们载入的类的父载入器是系统载入器,所以调用双亲托付的loadClass,会直接载入掉java.io.InputStream类。仅仅有在载入双亲中没有的TestServer类,才会用到我们自己的findClass载入逻辑载入指定路径下的类文件,满足双亲委派模型详细前面已经讲述过。不再赘述
热部署和加密解密的ClassLoader实现,大同小异。
仅仅是findClass的逻辑发生改变而已