类加载器学习笔记
因为毕业设计的一个功能是想要实现学生做完编程题目后提交到服务器进行判卷,所以遇到了整个毕设设计阶段最大的难题:学生新提交的字节码文件是我的系统编译运行之后的部分,只用简单的Class.forName并不能如愿得到该.class文件,所以有了这篇笔记O(∩_∩)o
类加载器
1.平时我们所说的加载只是类加载过程的一个步骤,一共分为7个阶段:加载、验证、准备、解析、初期花、使用、回收。而在加载阶段,虚拟机会使用类加载器来加载class文件。
2.类加载器分为三种:
启动类加载器(Bootstrap ClassLoader)、扩展类加载器(Extension ClassLoader)、应用程序类加载器(Application ClassLoader)
其中启动类加载器由C++编写而成,是虚拟机自身的一块;
扩展类加载器负责加载当时咱们配的<JAVA_HOME>\lib\ext目录下的类;
应用程序类加载器也称系统类加载器,负责加载ClassPath指定的类库。
ExtClassLoader与AppClassLoader的父类都是URLClassLoader,但AppClassLoader的父加载器ExtClassLoader,ExtClassLoader的父加载器为null。这一个怪异的地方是由于源码中的这几句:
首先看一下java的入口源代码,具体解释在四段代码的后面,注意源代码中加注释的部分
public Launcher() {
...
Launcher.ExtClassLoader var1;
try {
// 在这里创建ExtClassLoader 实例。
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
// 创建AppClassLoader实例。将ExtClassLoader 实例var1作为参数传入
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
这个是ExtClassLoader构造方法中的重要句子:
static class ExtClassLoader extends URLClassLoader {
...
public ExtClassLoader(File[] var1) throws IOException {
//这里发现父类构造第二个参数传了一个null
super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
}
...
这个是AppClassLoader构造方法中的重要句子:
static class AppClassLoader extends URLClassLoader {
public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
final String var1 = System.getProperty("java.class.path");
final File[] var2 = var1 == null?new File[0]:Launcher.getClassPath(var1);
return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction() {
public Launcher.AppClassLoader run() {
URL[] var1x = var1 == null?new URL[0]:Launcher.pathToURLs(var2);
// 这里创建AppClassLoader时,这里的var0是LaunCher创建时传来的ExtClassLader的对象,然后进到下面的构造方法
return new Launcher.AppClassLoader(var1x, var0);
}
});
}
AppClassLoader(URL[] var1, ClassLoader var2) {
//这里发现将ExtClassLoader的实例var0改名var2 并传进列第二个参数
super(var1, var2, Launcher.factory);
this.ucp.initLookupCache(this);
}
...
再看看他们super调用的父类构造就能看出端倪
public URLClassLoader(URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) {
super(parent);
...
首先第一段代码可以得知Launcher获取了ExtClassLoader与AppClassLoader,但获取AppClassLoader时将ExtClassLoader传了进去。之后,ExtClassLoader往父类构造第二个参数中传了null,与此同时,AppClassLoader在得到ExtClassLoader后将实例var0传入自己的构造var2,并传入了父类构造的第二个参数。 最后一段代码可以看到他们的父类构造第二个参数叫做parent。 这下明白了,虽然他们的父类都是URLClassLoader,但父加载器却是null与Ext加载器。
双亲委派模式
双亲委派模式的工作过程是:任何层次的一个类加载器收到了类加载的请求时,并不会自己立马加载这个类,而是把这个请求委派给父类加载器去完成,因此所有的加载请求最终都会传送到顶层的启动类加载器中,只有当父加载器发现自己的搜索范围没有时,子加载器才会去自己的营业范围内寻找,依次向下。
自顶向下为:启动类加载器(BootStrapxxx)->扩展类加载器(Extensionxxx)->应用程序类加载器(Applicationxxx)->用户自定义加载器 xxx= ClassLoader
比较值得注意的是,尽管ExtClassLoader的parent为Null,但还认为BootStrapClassLoader是他的父加载器,因为ExtClassLoader的源码中有这么一句:
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
如果父类为空,去找BootStrap去解决。这算是干爹?
现在,进行实践,看看如何通过自定义的类加载器来完成对于新下载的.class文件如何进行获取
首先,我将一个java文件编译后的.class文件Question001通过jdbc存进了数据库。
...
Blob blob = rs.getBlob("q_script");
// 获取字节流
InputStream in = blob.getBinaryStream();
// 在本地创建考生文件夹
File dir = new File("C:\\EOSTestFolder");
if (!dir.exists()) {
dir.mkdirs();
}
File file = new File("C:\\EOSTestFolder\\" + rs.getString("q_name") + "." + fileBehind);
if (!file.exists()) {
file.createNewFile();
}
OutputStream out = new BufferedOutputStream(new FileOutputStream(file));
// 定义一个缓冲区
byte[] buff = new byte[1024];
for (int i = 0; (i = in.read(buff)) > 0;) {
// 写数据
out.write(buff, 0, i);
}
...
之后学生将测试用的题目下载到了本地的C:\EOSTestFolder文件夹,并保存其文件名以便于一会加载它。
现在我们自定义类加载器:
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
//获取文件名
String fileName = getFileName(name);
File file = new File(mLibPath, fileName);
try {
FileInputStream is = new FileInputStream(file);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int len = 0;
try {
while ((len = is.read()) != -1) {
bos.write(len);
}
} catch (IOException e) {
e.printStackTrace();
}
byte[] data = bos.toByteArray();
is.close();
bos.close();
//返回需要的类
return defineClass(name, data, 0, data.length);
} catch (IOException e) {
e.printStackTrace();
}
return super.findClass(name);
这个加载器的作用就是通过delineClass()将指定路径下的字节码文件读入并转换为Class对象。
最后通过反射,看看这个不存在于web工程中的文件经过存取数据库之后,能否正常的读出信息:
// 创建类加载器
CustomClassLoader customClassLoader = new CustomClassLoader("C:\\EOSTestFolder");
Class<?> clazz = null;
try {
clazz = customClassLoader.findClass("EOSTestFolder.Question001");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println(clazz.toString());
Method[] methods = clazz.getDeclaredMethods();
for (Method m : methods) {
System.out.println(m.toString());
}
结果让人欣慰,只要能得到class就可以继续攻克接下的难题啦:
class EOSTestFolder.Question001
public java.lang.String EOSTestFolder.Question001.getName()
public void EOSTestFolder.Question001.setName(java.lang.String)
public void EOSTestFolder.Question001.show()
`