自己编写类加载器的意义
- 当class文件不在ClassPath目录下时,默认的系统类加载器无法找到该class文件,这时候需要自定义一个ClassLoader来加载特定路径下的class对象
- 当一个class文件需要通过网络传输,甚至可能会进行相应的解密操作时,这时候需要编写自定义的ClassLoader来实现相应逻辑
- 当要实现热部署时(一个class文件通过不同的类加载器产生不同的class对象而实现热部署功能),需要实现自定义的ClassLoader的逻辑
目标
本目录针对以上三个意义,将分类实战完成三类自定义ClassLoader的自定义实现,以便更好的掌握。
- 自定义文件类加载器
- 自定义网络类加载器
- 热部署类加载器
自定义File类加载器
package classloader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* 从特定路径的文件获取类的File类加载器
* @author XZP
*
*/
public class FileClassLoader extends ClassLoader {
private String rootDir;
public FileClassLoader(String rootDir) {
this.rootDir = rootDir;
}
/**
* 重写父类的findClass方法
* @param name 文件名
* @return 找到类的字节码就用defineClass返回对应的对象
* @throws ClassNotFoundException
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// 获取类的class文件字节数组
byte[] classData = getClassDataFromFile(name);
if (classData == null) {
throw new ClassNotFoundException();
} else {
// 直接生成class对象
return defineClass(name, classData, 0, classData.length);
}
}
/**
* 获取class文件并转换为字节码流的逻辑
* @param name 文件名
* @return
*/
private byte[] getClassDataFromFile(String name) {
// 读取类文件的字节
String path = name2Path(name);
System.out.println("path:" + path);
try {
InputStream ins = new FileInputStream(path);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bufferSize = 4096;
byte[] buffer = new byte[bufferSize];
int bytesNumRead = 0;
// 读取文件字节码
while ((bytesNumRead = ins.read(buffer)) != -1) {
baos.write(buffer, 0, bytesNumRead);
}
return baos.toByteArray();
} catch(IOException io) {
io.printStackTrace();
}
return null;
}
/**
* 通过文件名获取class文件的完全路径
* @param className 文件名
* @return
*/
private String name2Path(String className) {
return rootDir + File.separatorChar + className.replace('.', File.separatorChar) + ".class";
}
}
自定义网络类加载器
package classloader;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.URL;
public class NetClassLoader extends ClassLoader {
private String url; // class文件所在的URL
public NetClassLoader(String url) {
this.url = url;
}
/**
* 重写父类的findClass方法
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classData = getClassDataFromNet(name);
if (classData == null) {
throw new ClassNotFoundException();
} else {
return defineClass(name,classData, 0, classData.length);
}
}
/**
* 从网络获取class文件,不包含解密
* @param name
* @return
*/
private byte[] getClassDataFromNet(String name) {
String path = name2Path(name);
try {
URL url = new URL(path);
InputStream ins = url.openStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bufferSize = 4096;
byte[] buffer = new byte[bufferSize];
int hasRead = 0;
while ((hasRead = ins.read(buffer)) != -1) {
baos.write(buffer, 0, hasRead);
}
// 可能还涉及解密,在此省略,但是是非常重要的一个内容
return baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 根据文件名得到文件URL
* @param name
* @return
*/
private String name2Path(String name) {
// 得到class文件的URL
return url + "/" + name.replace('.', '/') + ".class"; // 注意网络路径表示和File路径表示还是有区别的
}
}
热部署类加载器
package classloader;
/**
* 热部署的测试类
* 作如下特殊说明:
* 热部署就是利用同一个class文件不同的类加载器在内存创建出两个不同的class对象.
* 由于JVM在加载类之前会检测请求的类是否已加载过(即在loadClass()方法中调用findLoadedClass()方法),
* 如果被加载过,则直接从缓存获取,不会重新加载。注意同一个类加载器的实例和同一个class文件只能被加载器一次,多次加载将报错.
* 因此我们实现的热部署必须让同一个class文件可以根据不同的类加载器重复加载,以实现热部署:也即为同一个class文件写两个不同的类加载器
* @author XZP
*
*/
public class HotDeployTest {
public static void main(String[] args) {
String rootDir = "F:\\java_workspace\\jvm\\src\\classloader";
// 创建自定义类的加载器:两个不同的对象
FileClassLoader loader1 = new FileClassLoader(rootDir);
FileClassLoader loader2 = new FileClassLoader(rootDir);
try {
// 通过调用loadClass()加载指定的class文件
Class<?> object1 = loader1.loadClass("classloader.FileResObj");
Class<?> object2 = loader2.loadClass("classloader.FileResObj");
System.out.println("obj1 by loadClass:" + object1.hashCode()); // 输出的两者hashCode相等,说明是同一对象
System.out.println("obj2 by loadClass:" + object2.hashCode());
// 通过调用findClass()方法绕过检测,创建不同的class对象
Class<?> object3 = loader1.findClass("FileResObj"); // 输出的两者hashCode不相等说明是不同对象
Class<?> object4 = loader2.findClass("FileResObj");
System.out.println("obj3 by findClass:" + object3.hashCode());
System.out.println("obj4 by findClass:" + object4.hashCode());
} catch (Exception e) {
e.printStackTrace();
}
}
}