本篇博客主要讲述如何实现一个自己的java类加载器(当然功能时很简单的),Java类加载器的作用就是在运行时加载类。
Java类加载器基于三个机制:委托、可见性和单一性。
委托机制是指将加载一个类的请求交给父类加载器,如果这个父类加载器不能够找到或者加载这个类,那么再有此加载器加载它。
可见性的原理是子类的加载器可以看见所有的父类加载器加载的类,而父类加载器看不到子类加载器加载的类。
单一性原理是指仅加载一个类一次,这是由委托机制确保子类加载器不会再次加载父类加载器加载过的类。正确理解类加载器能够帮你解决 NoClassDefFoundError和java.lang.ClassNotFoundException,因为它们和类的加载相关。类加载器通常 也是比较高级的Java面试中的重要考题,Java类加载器和工作原理以及classpath如何运作的经常被问到。Java面试题中也经常出现“一个类 是否能被两个不同类加载器加载”这样的问题(看完本篇的博客相信你就可以回答此类问题了)。
Java类加载器,也就是JVM类加载器,主要分为三种加载器,这三种加载器已经在源码中实现好了!!
下面我们来一个一个来看:
启动(Bootstrap)类加载器:引导类装入器是用本地代码实现的类装入器,它负责将/lib下面的核心类库或-Xbootclasspath选项指定的jar包加载到内存中(看到boot这个单词你应该明白,这玩意究竟有多重要了吧)。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。
扩展(Extension)类加载器:扩展类加载器是由Sun的ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的。它负责将/lib/ext或者由系统变量-Djava.ext.dir指定位置中的类库加载到内存中。
系统(System)类加载器:系统类加载器是由 Sun的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。它负责将系统类路径java -classpath或-Djava.class.path变量所指的目录下的类库加载到内存中。开发者可以直接使用系统类加载器。
下面来看一下具体的代码实现:
自定义加载器代码MyClassLoader.java
package com.zyq.classloader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class MyClassLoader extends ClassLoader{
private String mLoaderName;
private String mBasePath = "";
public MyClassLoader(){
super();
this.mLoaderName = "";
}
public MyClassLoader(String name) {
super();
this.mLoaderName = name;
}
public MyClassLoader(ClassLoader parent,String name) {
super(parent);
this.mLoaderName = name;
}
public void setBasePath(String path){
mBasePath = path;
}
public String getLoaderName(){
return mLoaderName;
}
@SuppressWarnings("deprecation")
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] data = loadClassFile(name);
System.out.println("data size = "+data.length);
return this.defineClass(data, 0, data.length);
}
private byte[] loadClassFile(String fileName){
ByteArrayOutputStream outStream = null;
byte[] result = null;
FileInputStream in = null;
outStream = new ByteArrayOutputStream();
this.mBasePath = this.mBasePath.replace(".", "/");
try {
in = new FileInputStream(new File(this.mBasePath+fileName+".class"));
System.out.println("file path + file name = "+this.mBasePath+fileName+".class");
int length = 0;
while(-1 != (length=in.read())){
outStream.write(length);
// System.out.println("outStream size = "+outStream.size());
}
result = outStream.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
in.close();
outStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
}
}
RunClass.java文件:
package com.zyq.classloader;
public class RunClass {
@SuppressWarnings("unchecked")
public static void main(String[] args){
System.out.println("run on main method "+(ClassLoader.getSystemClassLoader().toString()));
// MyClassLoader loader1 = new MyClassLoader(ClassLoader.getSystemClassLoader(),"loader1");
MyClassLoader loader1 = new MyClassLoader("loader1");
loader1.setBasePath("F:/workspace/web/ClassLoaderDemo/bin/com/zyq/classloader/");
MyClassLoader loader2 = new MyClassLoader(loader1, "loader2");
loader2.setBasePath("F:/workspace/web/ClassLoaderDemo/bin/com/zyq/classloader/");
MyClassLoader loader3 = new MyClassLoader(null, "loader3");
loader3.setBasePath("F:/workspace/web/ClassLoaderDemo/bin/com/zyq/classloader/");
try {
Class<?> clazz = loader2.loadClass("SampleA");
clazz.newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
}
class SampleC{
public SampleC() {
if(this.getClass().getClassLoader() instanceof MyClassLoader){
System.out.println("SampleC is loaded by "+((MyClassLoader)(this.getClass().getClassLoader())).getLoaderName());
}else{
System.out.println("SampleC is loaded by "+this.getClass().getClassLoader());
}
}
}
测试类:SampleA和SampleB文件:
package com.zyq.classloader;
public class SampleA {
public SampleA() {
if(this.getClass().getClassLoader() instanceof MyClassLoader){
System.out.println("SampleA is loaded by "+((MyClassLoader)(this.getClass().getClassLoader())).getLoaderName());
}else{
System.out.println("SampleA is loaded by "+this.getClass().getClassLoader());
}
new SampleB();
new SampleAInner();
}
public class SampleAInner{
public SampleAInner(){
if(this.getClass().getClassLoader() instanceof MyClassLoader){
System.out.println("SampleAInner is loaded by "+((MyClassLoader)(this.getClass().getClassLoader())).getLoaderName());
}else{
System.out.println("SampleAInner is loaded by "+this.getClass().getClassLoader());
}
}
}
}
package com.zyq.classloader;
public class SampleB {
public SampleB(){
System.out.println("SampleB is loaded by "+this.getClass().getClassLoader().toString());
}
}
运行结果:
run on main method sun.misc.Launcher$AppClassLoader@73d16e93
file path + file name = F:/workspace/web/ClassLoaderDemo/bin/com/zyq/classloader/SampleA.class
data size = 1165
SampleA is loaded by loader1
SampleB is loaded by sun.misc.Launcher$AppClassLoader@73d16e93
SampleAInner is loaded by sun.misc.Launcher$AppClassLoader@73d16e93
通过运行结果很明显可以看出SampleA使用的类加载器是我们自定义的类加载器,而其他的对象的类的加载都是使用系统加载器进行加载的。
自定义类加载器主要需要我们重写findClass方法,主要在这里面将.class文件转换成一个byte数组,然后通过使用defineClass获取一个Class对象,我们可以通过这个Class调用newInstance方法获取一个对应的实例化对象。
在findClass方法中我们使用父加载器先行加载类,如果找不到或者无法加载对应的类,在通过自定义的加载器加载我们传入的类。
在使用自定义的加载器时需要详细指明带加载类的详细位置,不然会报出类找不到的错误,详细的可以看一下上述的代码!!!
好了,关于类加载器我还有很多不清楚的地方,比如说这个类加载器如法加载内部类,我现在没有什么思路,通过打印可以看出,通过Class获取的构造方法的名称为父类$内部类名称,不知道是不是以为找不到构造方法的缘故,希望有知道的朋友可以告诉我一下!!!
关于自定义类加载器就说这么到,有兴趣的朋友可以以关注我,遇到问题大家一起讨论一下!!
这是我的微信公众号,如果可以的话,希望您可以帮忙关注一下,这将是对我最大的鼓励了,谢谢!!