概述
前言
前面 认识类加载器及双亲委派模型/ 中我们认识了类加载器,本文我们来自定义一个类加载器。
为什么需要自定义类加载器?
既然JDK已经有类加载器了,为什么还需要自定义类加载器呢?大概有以下几个原因:
-
隔离加载类
模块隔离,将类加载到不同的应用程序中。比如Tomcat这类web应用服务器,内部定义了好几种类加载器,用于隔离web应用服务器上不同的应用程序。
-
扩展加载源
还可以从数据库、网络或其他终端上加载类
- 防止源码泄露
Java代码容易被编译和篡改,可以进行编译加密,类加载需要自定义还原加密字节码。
类加载器的调用过程
我们需要从ClassLoader的源码入手来看类加载的过程。
ClassLoader中的loadClass方法源码如下:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
双亲委派的核心代码逻辑如下:
而findClass()默认的实现是抛出ClassNotFoundException异常:
还有一个方法我们需要注意的是defineClass(),它的作用是将字节数组转换成Class对象,源码如下:
整个类加载的流程如下图所示:
总结下这几个核心的方法:
loadClass:双亲委派的核心实现逻辑;
findClass:将class文件加载到内存中,是二进制的形式,最后得由defineClass来讲二进制文件转换成class文件。
自定义类加载器
所有用户自定义类加载器都应该继承ClassLoader类。
在自定义ClassLoader的子类是,我们通常有两种做法:
- 重写loadClass方法(是实现双亲委派逻辑的地方,修改他会破坏双亲委派机制,不推荐)
- 重写findClass方法 (推荐)
代码实现
package com.jvm;
import java.io.*;
//自定义类加载器
public class MyClassLoader extends ClassLoader{
//磁盘上类的路径
private String codePath;
public MyClassLoader(ClassLoader parent, String codePath) {
super(parent);
this.codePath = codePath;
}
public MyClassLoader(String codePath) {
this.codePath = codePath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
BufferedInputStream bis=null;
ByteArrayOutputStream baos=null;
//完整的类名
String file = codePath+name+".class";
try {
//初始化输入流
bis = new BufferedInputStream(new FileInputStream(file));
//获取输出流
baos=new ByteArrayOutputStream();
int len;
byte[] data=new byte[1024];
while ((len=bis.read(data))!=-1){
baos.write(data,0,len);
}
//获取内存中的字节数组
byte[] bytes = baos.toByteArray();
//调用defineClass将字节数组转换成class实例
Class<?> clazz = defineClass(null, bytes, 0, bytes.length);
return clazz;
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}
测试
在本地D盘的目录下放置一个class文件:
测试代码如下:
public class MyClassLoaderTest {
public static void main(String[] args) {
MyClassLoader myClassLoader = new MyClassLoader("D:/");
try {
Class<?> clazz = myClassLoader.loadClass("AddressTest");
//打印具体的类加载器,验证是否是由我们自己定义的类加载器加载的
System.out.println("测试字节码是由"+clazz.getClassLoader().getClass().getName()+"加载的。。");
Object o = clazz.newInstance();
System.out.println(o.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
可以看出我们使用自定义的类加载器加载了我们本地的一个class文件。
总结
双亲委派模型的实现核心方法是loadClass()、findClass()和defineClass(),其中loadClass()是核心逻辑,findClass()将文件加载到内存成为,defineClass()是将二进制文件转换为活的class文件,自定义类加载器也是从这三个核心方法入手的,我们通过重写findClass()方法来自定义了一个类加载器,成功加载了本地的一个class文件。
最后
以上就是无聊石头为你收集整理的手把手教你如何自定义一个类加载器的全部内容,希望文章能够帮你解决手把手教你如何自定义一个类加载器所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复