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

Java 自定义类加载器教程[通俗易懂]Java自定义类加载器教程除了在面试中遇到类的加载器的概率会高外,在实际的工作中很少接触。但是一个程序员想要成长为大牛就必须对一些JVM的底层设计有些了解。在此基础上我们阅读一些源码和框架会显得更轻松。好了废话不多说,我们接着前面的文章,乘热打铁。来实现一个Java自定义类加载器吧。要实现Java自定义的类加载器,我们需要继承ClassLoader。并且需要了解Java的双亲委派模型。loadClassloadClass默认实现如下:publicClass<?>

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺


Java 自定义类加载器教程

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

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

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

loadClass

loadClass默认实现如下:

public Class<?> loadClass(String name) throws ClassNotFoundException { 
   
    return loadClass(name, false);
}

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

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的默认实现如下:

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默认实现如下:

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 方法即可。下面我们通过一个示例来演示自定义类加载器的流程:

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加载,而不会通过我们自定义类加载器来加载。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/196748.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号