在前面的知识基础上对自定义类加载器进行优化
优化内容:
①不对ClassLoader进行任何重写,完全自定义
②批量加载类文件
一.新类加载器的创建
1.实现原理:
①手动指定加载器加载文件的根路径
②手动指定需要被加载的类文件数组
③自定义类加载器命名为CustomClassLoader并继承ClassLoader
④定义私有成员属性
1>String rootdir用于记录根路径
2>Map<String,Class>用于记录所有加载后的类文件对象
⑤定义带参构造函数,构造函数内完成的内容如下:
1>完成成员属性的初始化
2>完成对所有指定的类的加载
2.实现代码如下:
package Myloader02;
import java.io.*;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
/**
* @author :weihuanwen
* @date :Created in 2019/5/23 00:14
* @description :自定义类加载器(多批次加载)
* @version: 1.0
*/
public class CustomClassLoader extends ClassLoader {
//该类加载器直接加载类文件的根路径
private String rootdir;
//用于存储加载的类文件对象
private Map<String,Class> clazzs;
//提供外界获取接口
public Map<String, Class> getClazzs() {
return clazzs;
}
/**
* 类加载器构造函数
* @param basedir 指定类加载器加载根目录
* @param classNames 需要被加载的类的文件名数组
*/
public CustomClassLoader(String basedir,String[] classNames) {
//此加载器的父加载器被定义为null
super(null);
//初始化类加载器加载路径根目录
this.rootdir = basedir;
//初始化存储加载后的类文件的集合
clazzs = new HashMap<>();
//获取形参列表中所有类的加载文件对象
getClazzs(classNames);
}
/**
* 将形参列表中指定的类名全部加载为对应的类文件对象
* @param classNames 类文件名数组
*/
private void getClazzs(String[] classNames) {
for (int i = 0; i < classNames.length; i++) {
//当前的文件名
String className = classNames[i];
//获取文件对象
File classFile = loadClassFile(className);
try {
//将类文件加载为字节数组
byte[] classData = loadClassData(classFile);
if (classData == null){
throw new ClassNotFoundException();
}else{
//将字节数组转换为类文件对象
Class clazz = defineClass(className, classData, 0, classData.length);
//将加载出的类文件对象存储于map集合中
clazzs.put(className,clazz);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 根据文件名获取对应的文件对象
* @param className
* @return
*/
private File loadClassFile(String className){
//加载目录根路径
StringBuilder sb = new StringBuilder(rootdir);
//获取当前类对应的class文件名
String classFullName = className.replace('.', File.separatorChar) + ".class";
sb.append(File.separatorChar + classFullName);
//获取类对应的class文件
return new File(sb.toString());
}
/**
* 将类文件转换为字节数组
* @param classFile
* @return
*/
private byte[] loadClassData(File classFile){
try {
//获取class文件对应的字节流
InputStream is = new FileInputStream(classFile);
//该字节流可以将数据写入字节数组,数据写入缓冲区时缓冲区自动增长
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] bytes = new byte[4096];
int length = 0;
//读取类文件并写入字节数组中
while ((length = is.read(bytes))!= -1){
baos.write(bytes,0,length);
}
//返回类文件的字节数组
return baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
二.测试类加载器:
1.定义被加载类SayHi.java
/**
* @author :weihuanwen
* @date :Created in 2019/5/23 00:14
* @description :
* @version: 1.0
*/
public class SayHi {
@Override
public String toString() {
return "Hi!";
}
}
2.定义被加载类SayGoodBye.java
/**
* @author :weihuanwen
* @date :Created in 2019/5/23 00:14
* @description :
* @version: 1.0
*/
public class SayGoogBye {
@Override
public String toString() {
return "GoogBye!";
}
}
3.将java文件放置在D:\study下并执行编译,编译后效果如下:
4.定义测试类test.java
package Myloader02;
import java.util.Map;
/**
* @author :weihuanwen
* @date :Created in 2019/5/23 00:14
* @description :
* @version: 1.0
*/
public class test {
public static void main(String[] args) throws Exception{
CustomClassLoader ccl = new CustomClassLoader("D:\\study",
new String[]{"SayHi","SayGoogBye"});
Map<String, Class> clazzs = ccl.getClazzs();
for (String className : clazzs.keySet()) {
String msg = clazzs.get(className).newInstance().toString();
System.out.println(msg);
}
}
}
5.测试结果输出如下:
Hi!
GoogBye!
三.总结
自定义类加载器时不一定要重写ClassLoader的findClass(String name)方法,自定义的大体套路都是此,先确定加载路径,再确定需要被加载的类文件名,定义字节流读取class文件后转为字节数组.通过字节数组便可以获取该类的class文件对象.通过class文件对象,通过反射就可以操控类的内容(类的方法,类的属性)了.