问题:为什么我自定义ClassLoader来加载一个.class文件。可以显示用的自定义classLoader加载的。但是加载项目中的.java文件。就提示用AppClassLoaser加载的。
我猜测哈,是编译的时候,IDE就给实现了类加载,因此对于.java文件。会用AppClassLoader进行加载。而对于我们自己写的.class文件。因为是动态加载的,所以可以使用自定义的类加载器了。
-
1.系统ClassLoader,只能加载.java文件。加载时候,传递的是类的全路径名称,没有.class后缀。不能加载.class文件。如果是自定义的ClassLoader,正好相反,只能加载.class文件,并且是类文件所在的正确路径。
-
2.所谓类加载器,就是真正执行类加载的加载器。如果用自定义的ClassLoader进行加载,那么就自己通过IO的方式,来进行类的加载。
-
3.系统的加载器,只能加载已经编译期有的类;换句话说,动态加载服务端或者是本地文件系统的类,必须是.class格式的,同时必须是自定义类加载器了。
public static void main(String[] args) throws Exception{
//动态加载类,获取当前类的Class对象
Class student = Class.forName("com.justalk.javademo.loader.Student");
//获取Student类名称为outName地方法
Method name = student.getMethod("outName");
//调用outName方法
name.invoke(student.newInstance()); //通过实例化的对象,调用无参数的方法
//获取类中名称为Student的方法,String,class是参数类型(调用含有参数的方法,参数的类型必须为包装类)
Method age = student.getMethod("outAge", Integer.class);//注意参数不是String
//调用Student方法,其中27是方法传入的参数值
age.invoke(student.newInstance(),26);//通过对象,调用有参数的方法
System.out.println("=======================Class.forName加载类完成=======================");
//这种方式不会进行类初始化
Class student1 = ClassLoader.getSystemClassLoader().loadClass("com.justalk.javademo.loader.Student");
Method name1 = student1.getMethod("outName");
//调用outName方法
name1.invoke(student1.newInstance()); //通过实例化的对象,调用无参数的方法
System.out.println("=======================ClassLoader加载类完成=======================");
}
下面的几张图是我通过UT进行类加载器实验。了解类加载器和文件后缀,文件路劲的关系。
对于自定义ClassLoader,通过IO流读取.class文件,反序列化后,生成Class对象的操作,需要重写findClass()方法。 具体调用流程是,loadClass()-》findClass()-》自己写的反序列化的方法。
实现自定义类加载器的三步:
- 1.继承ClassLoader
- 2.重写findClass()方法
- 3.调用defineClass()方法
一个基本的自定义类加载器代码如下:
package com.mobile.waikuai;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
public class MyClassLoader extends ClassLoader {
private String path = "src/main/java/"; //默认加载路径
private String name; //类加载器名称
private final String filetype = ".class"; //文件类型
public MyClassLoader(String name) {
super();
this.name = name;
}
public MyClassLoader(ClassLoader parent, String name) {
super(parent);
this.name = name;
}
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
// TODO Auto-generated method stub
byte[] b = loadClassData(name);
return defineClass(name, b, 0, b.length);
}
/**
* 在该方法中,有双亲委托机制的运行。但是子类的执行,还是要走到子类重写的findClass方法
* @param name
* @param resolve
* @return
* @throws ClassNotFoundException
*/
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
return super.loadClass(name, resolve);
}
private byte[] loadClassData(String name) {
byte[] data = null;
InputStream in = null;
name = name.replace('.', '/');
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
in = new FileInputStream(new File(path + name + filetype));
int len = 0;
while (-1 != (len = in.read())) {
out.write(len);
}
data = out.toByteArray();
} catch (FileNotFoundException e) {
System.out.println("FileNotFoundException !!!");
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
System.out.println("IOException !!!");
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
in.close();
out.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return data;
}
public void setPath(String path) {
this.path = path;
}
@Override
public String toString() {
// TODO Auto-generated method stub
return this.name;
}
}
或者这套代码
public class CustomClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] result = getClassFromCustomPath(name);
if (result == null) {
throw new FileNotFoundException(name);
} else { //defineClass方法将字节码转化为类
return defineClass(name, result, 0, result.length);
}
} catch (Exception e) {
e.printStackTrace();
}
throw new ClassNotFoundException(name);
}
private byte[] getClassFromCustomPath(String name) { // 从自定义路径中加载指定类,返回类的字节码文件
InputStream in = null;
ByteArrayOutputStream out = null;
String path = "/Users/yeyonghao/" + name + ".class";
try {
in = new FileInputStream(path);
out = new ByteArrayOutputStream();
byte[] buffer = new byte[2048];
int len = 0;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
return out.toByteArray();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
in.close();
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
public static void main(String[] args) {
CustomClassLoader customClassLoader = new CustomClassLoader();
try {
Class<?> clazz = Class.forName("One", true, customClassLoader);
Object obj = clazz.newInstance(); // cn.xpleaf.coding.c4.CustomClassLoader@610455d6
System.out.println(obj.getClass().getClassLoader());
} catch (Exception e) {
e.printStackTrace();
}
}
}
类加载的双亲委托机制的原理如下:
先判断当前加载器是否已加载目标类,如已加载直接返回,如未加载则委托父加载器加载;
父类加载器拿到目标类后先判断自己是否已加载目标类,如已加载则返回该类,如未加载再委托自己的父 加载器加载。如此递归循环,直到当前类加载器没有父加载器(即当前加载器为引导类加载器),这时交由引导类加载器去加载;
引导类加载器加载目标类时现在自己的类路径下找寻目标类文件,如找到了
则直接加载该类,如未找到则向下委托子类加载器去加载。直到找到该类文件且成功加载为止
对应的理论如下:
最后,如果要跳出双亲委托机制,则直接调用自定义类加载器的findClass()方法。从对应的android内部或者外部存储路径来进行 .class文件的加载。 如果一个目录既有某个要加载文件的.java文件,又有.class文件。那么通过loaderClass()方法,将会通过AppClassLoader来加载。通过直接调用findClass()方法,将会直接通过自定义的ClassLoader进行类加载了。从而跳出了双亲委托机制。
总结就是:
- 1.如果想正常加载已有的Class文件,则可以使用系统的类加载器;
- 2.如果想加载外部的文件,使用自定义ClassLoader,然后调用loadClass()方法,会遵循双亲委托机制;
- 3.如果想跳过双亲委托机制,使用自定义ClassLoader,直接重写并调用findClass()方法。