自定义类加载器是从实际场景出发,解决一些应用上的问题,比如:
SpringBoot-devtools和Arthas等工具,其实现原理就用到了类加载机制JVM,保证核心代码不被反编译泄漏SDK功能,自定义类加载器可以解决某个同名的Class想要加载不同的版本的场景,实现同名Class多版本共存,相互隔离从而达到解决版本冲突的目的。如Java模块化规范 OSGi、蚂蚁金服的类隔离框架SOFAArk想要自定义类加载器,一定需要了解双亲委派模型
双亲委派模型加载class的步骤可为如下几步:
BootStrapClassLoader。如果父类加载器为null则代表使用BootStrapClassLoader进行加载。BootStrapClassLoader->ExtClassLoader->AppClassLoader->自定义类加载器的顺序依次尝试加载。其加载class的核心方法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 loade
// 捕获父类加载器无法加载的请求
}
if (c == null) {
// If still not found, then invoke findClass in orde
// to find the class.
long t1 = System.nanoTime();
// 如果父类加载器不能加载,就尝试采用子类加载器加载
c = findClass(name);
// this is the defining class loader; record the stats
PerfCounter.getParentDelegationTime().addTime(t1 - t0);
PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
// 链接指定类
resolveClass(c);
}
return c;
}
}下图更好的展示了双亲委派的过程:

自定义类加载器只需要继承ClassLoader,同时覆盖findClass方法(而不是loadClass方法)即可
::: tip
Subclasses of ClassLoader are encouraged to override findClass(String), rather than this method.
Unless overridden, this method synchronizes on the result of getClassLoadingLock method during the entire class loading process. 官方推荐
:::
准备两个类,一个类作为实体,一个类作为Service
TestUser.java
public class TestUser {
private String name;
public TestUser() {
}
public TestUser(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "========这是User测试文件1号========";
}
}TestService.java
public class TestService {
public String testPrint(String name) {
String result = name + "调用当前方法";
System.out.println(name + "调用当前方法");
return result;
}
}自定义类加载器CustomClassLoader.java
package com.test.custom;
import java.io.FileInputStream;
public class CustomClassLoader extends ClassLoader {
private String classPath;
public CustomClassLoader() {
}
public CustomClassLoader(String classPath) {
this.classPath = classPath;
}
private byte[] loadByte(String name) throws Exception {
name = name.replaceAll("\\.", "/");
String a = classPath + "/" + name + ".class";
FileInputStream fileInputStream = new FileInputStream(classPath + "/" + name + ".class");
int len = fileInputStream.available();
byte[] data = new byte[len];
fileInputStream.read(data);
fileInputStream.close();
return data;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
//defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节数组。
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
}
return super.findClass(name);
}
}自定义的函数很简单,将全限定名的class文件加载为字节码数组,然后传入defineClass方法进行加载,defineClass的作用是将一个字节数组转化为Class对象
需要注意的是**要加载的Class必须在全限定名一样的目录**下进行javac编译,以为package必须一致,比如com.test.custom.pojo.TestUser,TestUser必须在com.test.custom.pojo目录下编译生成字节码
package com.test.custom;
public class LoadCustomPoJoTest {
public static void main(String[] args) throws Exception {
// 初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定义类加载器的父类加载器设置为应用程序类加载器AppClassLoade
CustomClassLoader classLoader = new CustomClassLoader(
"E:/ideaProject/custom-classloader/src");
// 从磁盘中创建一个目录,将要加载的类的class放入目录
// Class.forName效果和classLoader.loadClass一致
Class<?> clazz = Class.forName("com.test.custom.pojo.TestUser", true, classLoader);
Object obj = clazz.newInstance();
System.out.println(obj.toString());
System.out.println(clazz.getClassLoader());
System.out.println();
CustomClassLoader classLoader2 = new CustomClassLoader(
"E:/ideaProject/custom-classloader/src");
Class clazz1 = Class.forName("com.test.custom.pojo.repeat.TestUser", true, classLoader2);
Object obj1 = clazz1.newInstance();
System.out.println(obj1.toString());
System.out.println(clazz1.getClassLoader());
}
}我们测试两个同名的Class对象共存的情况,两个TestUser只在toString方法打印的**数据**和**包名**不一样
必须采用不同的类加载器加载同名对象,否则同一个类加载器会以第一次加载的对象为准
com.test.custom.pojo.TestUse
package com.test.custom.pojo;
public class TestUser {
// ......
public String toString() {
return "========这是User测试文件1号========";
}
}package com.test.custom.pojo.repeat;
public class TestUser {
// ......
public String toString() {
return "========这是User测试文件2号========";
}
}========这是User测试文件1号========
com.test.custom.CustomClassLoader@2133c8f8
========这是User测试文件2号========
com.test.custom.CustomClassLoader@30c7da1e可以看到,两个类加载器是不一样的,且两个类的方法都已经打印了,说明此时同名类不同版本共存。
构建一个带参数的Service方法,加载到方法之后,利用反射进行调用
package com.test.custom;
import java.lang.reflect.Method;
public class LoadCustomMethodTest {
public static void main(String[] args) throws Exception {
// 初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定义类加载器的父类加载器设置为应用程序类加载器AppClassLoade
CustomClassLoader classLoader = new CustomClassLoader(
"E:/ideaProject/custom-classloader/src");
// 从磁盘中创建一个目录,将要加载的类的class放入目录
Class clazz = classLoader.loadClass("com.test.custom.service.TestService");
Object obj = clazz.newInstance();
Method method = clazz.getDeclaredMethod("testPrint", String.class);
method.invoke(obj, "李四");
System.out.println(clazz.getClassLoader());
}
}李四调用当前方法
com.test.custom.CustomClassLoader@49e4cb85https://www.cnblogs.com/twoheads/p/10143038.html
https://blog.csdn.net/liubenlong007/article/details/103593443
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。