前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java 自定义类加载器教程[通俗易懂]

Java 自定义类加载器教程[通俗易懂]

作者头像
全栈程序员站长
发布2022-10-04 12:11:03
4300
发布2022-10-04 12:11:03
举报

大家好,又见面了,我是你们的朋友全栈君。

文章目录


Java 自定义类加载器教程

除了在面试中遇到类的加载器的概率会高外,在实际的工作中很少接触。但是一个程序员想要成长为大牛就必须对一些 JVM 的底层设计有些了解。在此基础上我们阅读一些源码和框架会显得更轻松。

好了废话不多说,我们接着前面的文章,乘热打铁。来实现一个 Java 自定义类加载器吧。

要实现 Java 自定义的类加载器,我们需要继承 ClassLoader 。并且需要了解Java的双亲委派模型。

loadClass

loadClass默认实现如下:

代码语言:javascript
复制
public Class<?> loadClass(String name) throws ClassNotFoundException { 
   
    return loadClass(name, false);
}

再看看loadClass(String name, boolean resolve)函数:

代码语言:javascript
复制
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
}
// 业余草:www.xttblog.com
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;
}
}

从上面代码可以明显看出,loadClass(String, boolean)函数即实现了双亲委派模型!整个大致过程如下:

  1. 首先,检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接返回。
  2. 如果此类没有加载过,那么,再判断一下是否有父加载器;如果有父加载器,则由父加载器加载(即调用parent.loadClass(name, false);).或者是调用bootstrap类加载器来加载。
  3. 如果父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前类加载器的findClass方法来完成类加载。 话句话说,如果自定义类加载器,就必须重写findClass方法!

findClass

findClass的默认实现如下:

代码语言:javascript
复制
protected Class<?> findClass(String name) throws ClassNotFoundException { 

throw new ClassNotFoundException(name);
}

可以看出,抽象类ClassLoader的findClass函数默认是抛出异常的。而前面我们知道,loadClass在父加载器无法加载类的时候,就会调用我们自定义的类加载器中的findeClass函数,因此我们必须要在loadClass这个函数里面实现将一个指定类名称转换为Class对象.

如果是是读取一个指定的名称的类为字节数组的话,这很好办。但是如何将字节数组转为Class对象呢?很简单,Java提供了defineClass方法,通过这个方法,就可以把一个字节数组转为Class对象。

defineClass

defineClass主要的功能是:将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节数组。如,假设class文件是加密过的,则需要解密后作为形参传入defineClass函数。

defineClass默认实现如下:

代码语言:javascript
复制
protected final Class<?> defineClass(String name, byte[] b, int off, int len)
throws ClassFormatError  { 

return defineClass(name, b, off, len, null);
}

函数调用过程

函数调用过程,如下图所示:

在这里插入图片描述
在这里插入图片描述

通常情况下,我们都是直接使用系统类加载器。但是,有的时候,我们也需要自定义类加载器。比如应用是通过网络来传输 Java类的字节码,为保证安全性,这些字节码经过了加密处理,这时系统类加载器就无法对其进行加载,这样则需要自定义类加载器来实现。自定义类加载器一般都是继承自ClassLoader类,从上面对loadClass方法来分析来看,我们只需要重写 findClass 方法即可。下面我们通过一个示例来演示自定义类加载器的流程:

代码语言:javascript
复制
package com.xttblog.classloader;
import java.io.*;
public class MyClassLoader extends ClassLoader { 

private String root;
// 业余草:www.xttblog.com
protected Class<?> findClass(String name) throws ClassNotFoundException { 

byte[] classData = loadClassData(name);
if (classData == null) { 

throw new ClassNotFoundException();
} else { 

return defineClass(name, classData, 0, classData.length);
}
}
private byte[] loadClassData(String className) { 

String fileName = root + File.separatorChar
+ className.replace('.', File.separatorChar) + ".class";
try { 

InputStream ins = new FileInputStream(fileName);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bufferSize = 1024;
byte[] buffer = new byte[bufferSize];
int length = 0;
while ((length = ins.read(buffer)) != -1) { 

baos.write(buffer, 0, length);
}
return baos.toByteArray();
} catch (IOException e) { 

e.printStackTrace();
}
return null;
}
public String getRoot() { 

return root;
}
public void setRoot(String root) { 

this.root = root;
}
public static void main(String[] args)  { 

MyClassLoader classLoader = new MyClassLoader();
classLoader.setRoot("E:\\temp");
Class<?> testClass = null;
try { 

testClass = classLoader.loadClass("com.xttblog.classloader.Test2");
Object object = testClass.newInstance();
System.out.println(object.getClass().getClassLoader());
} catch (ClassNotFoundException e) { 

e.printStackTrace();
} catch (InstantiationException e) { 

e.printStackTrace();
} catch (IllegalAccessException e) { 

e.printStackTrace();
}
}
}

自定义类加载器的核心在于对字节码文件的获取,如果是加密的字节码则需要在该类中对文件进行解密。由于这里只是演示,我并未对class文件进行加密,因此没有解密的过程。这里有几点需要注意:

  1. 这里传递的文件名需要是类的全限定性名称,即com.paddx.test.classloading.Test格式的,因为 defineClass 方法是按这种格式进行处理的。
  2. 最好不要重写loadClass方法,因为这样容易破坏双亲委托模式。
  3. 这类Test 类本身可以被AppClassLoader类加载,因此我们不能把com/paddx/test/classloading/Test.class放在类路径下。否则,由于双亲委托机制的存在,会直接导致该类由AppClassLoader加载,而不会通过我们自定义类加载器来加载。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022年9月6日 上,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章目录
  • Java 自定义类加载器教程
  • loadClass
  • findClass
  • defineClass
  • 函数调用过程
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档