Java虚拟机JVM学习06 自定义类加载器 父委托机制和命名空间的再讨论

Java虚拟机JVM学习06 自定义类加载器 父委托机制和命名空间的再讨论

 

创建用户自定义的类加载器

  要创建用户自定义的类加载器,只需要扩展java.lang.ClassLoader类,然后覆盖它的findClass(String name)方法即可,该方法根据参数指定的类的名字,返回对应的Class对象的引用。

 

自定义类加载器的例子

  代码:

复制代码
package com.mengdd.classloader; 
import java.io.ByteArrayOutputStream;
import
java.io.File;
import
java.io.FileInputStream;
import java.io.InputStream;

public
class MyClassLoader extends ClassLoader {
private String name; // 类加载器的名字
private String path = "d:\\"; // 加载类的路径
private final String fileType = ".class"; // class文件的扩展名

public
MyClassLoader(String name) {
super
(); // 让系统类加载器成为该类加载器的父加载器
this
.name = name;
}

public
MyClassLoader(ClassLoader parent, String name) {
super(parent); // 显式指定该类加载器的父加载器
this
.name = name;
} @Override


public
String toString() {
return
this.name;
}
public String getPath() {
return
path;
}
public void setPath(String path) {
this.path = path;
}
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
// 重写的时候把protected改为public
/
获取字节数组
byte[] data = this.loadClassData(name);
// 将字节数组转换成Class对象返回
return this.defineClass(name, data, 0, data.length);
}
/**
* 得到class文件的二进制字节数组
*
*
@param name
*
@return
*/

private byte[] loadClassData(String name) {
InputStream is
= null;
byte[] data = null;
ByteArrayOutputStream baos
= null;
try {
// 将完整类名中的.转化成\
name = name.replace(".", "\\");
is = new FileInputStream(new File(path + name + fileType));
baos = new ByteArrayOutputStream();
int ch = 0;
while (-1 != (ch = is.read())) {
baos.write(ch);
}
data = baos.toByteArray();
}catch (Exception e) {
e.printStackTrace();
}
finally {
try {
is.close();
baos.close();
}
catch (Exception e2) {

}
}
return data;
}

// main方法用来测试
public static void main(String[] args) throws Exception {
MyClassLoader loader1
= new MyClassLoader("loader1");
// loader1的父加载器是系统类加载器
// 系统类加载器会在classpath指定的目录中加载类
loader1.setPath("d:\\myapp\\serverlib\\");
MyClassLoader loader2
= new MyClassLoader(loader1, "loader2");
// 将loader1作为loader2的父加载器
loader2.setPath("d:\\myapp\\clientlib\\");
MyClassLoader loader3
= new MyClassLoader(null, "loader3");
// loader3的父加载器是根类加载器
loader3.setPath("d:\\myapp\\otherlib\\");
// 测试加载
test(loader2);
test(loader3);
System.out.println(
"test2---------------");
//
测试不同命名空间的类的互相访问
test2(loader3);
}

public static void test(ClassLoader loader) throws Exception {
Class clazz
= loader.loadClass("com.mengdd.classloader.Sample");
Object object
= clazz.newInstance();
}


public
static void test2(ClassLoader loader) throws Exception {
Class clazz
= loader.loadClass("com.mengdd.classloader.Sample");
Sample object
= (Sample) clazz.newInstance();
System.out.println(
"sample v1: " + object.v1);

}
 }
复制代码

 

  其中Sample:

复制代码
package com.mengdd.classloader; 
public class Sample {
public
int v1 = 1;
public Sample() {
System.out.println(
"Sample is loaded by: " + this.getClass().getClassLoader());
// 主动使用Dog类
new
Dog();
}
}
复制代码

 

  Dog类:

复制代码
package com.mengdd.classloader;  
public class Dog {
public
Dog() {
System.out.println(
"Dog is loaded by: " + this.getClass().getClassLoader());
}
}
复制代码

 

 

  例子演示过程略,尝试把class文件放在不同的路径下,看输出或者报错结果。

  主要结论就是验证了父亲委托机制。

  采用loader1的时候由于其父类是系统类加载器(也即应用类加载器),所以如果可以在classpath中找到目标.class文件,则定义类加载器是系统类加载器,输出类似:

  sun.misc.Launcher$AppClassLoader@7448bc3d

 

  每个类加载器都有自己的命名空间,命名空间由该加载器及所有父加载器所加载的类组成。

 

 

  在Sample类中主动使用了Dog类,当执行Sample类的构造方法中的new Dog()语句时,Java虚拟机需要先加载Dog类,到底用哪个类加载器加载呢?

  从打印结果可以看出,Java虚拟机会用Sample类的定义类加载器去加载Dog类,加载过程也同样采用父亲委托机制

  如果Sample类首次主动使用Dog时,Sample类的加载器及它的父加载器都无法加载Dog类,将会抛出找不到文件的异常。

 

不同类加载器的命名空间关系

  同一个命名空间内的类是相互可见的,即可以互相访问。

 

  子加载器的命名空间包含所有父加载器的命名空间。

  因此由子加载器加载的类能看见父加载器加载的类。

  例如系统类加载器加载的类能看见根类加载器加载的类。

 

  由父加载器加载的类不能看见子加载器加载的类。

  可以理解为:由于子加载器中含有父加载器的引用,所以子加载器的范围更大

 

  如果两个加载器之间没有直接或间接的父子关系,那么它们各自加载的类相互不可见。

 

 

  比如这么一种情况:MyClassLoader类由系统类加载器加载,而Sample类由loader3类加载器加载,因此MyClassLoader类看不见Sample类。

  在MyClassLoader类的main()方法中使用Sample类,会导致错误。

  当两个不同命名空间内的类互相不可见时,可采用Java反射机制来访问对方实例的属性和方法,即反射可以突破命名空间的限制。

 

参考资料

  圣思园张龙老师Java SE视频教程。

  ClassLoader类:http://docs.oracle.com/javase/7/docs/api/

  相关博文:

  Java虚拟机JVM学习05 类加载器的父委托机制:http://www.cnblogs.com/mengdd/p/3562540.html

 

 博文源于:

 

posted @ 2017-05-05 18:20  書话  阅读(149)  评论(0编辑  收藏  举报