java学习笔记

Java 动态代理

Java反射提供了一种类动态代理机制,可以通过代理接口实现类来完成程序无侵入式扩展。

Java动态代理主要使用场景:

统计方法执行所耗时间。

在方法执行前后添加日志。

检测方法的参数或返回值。

方法访问权限控制。

方法Mock测试。

1
2
3
4
什么是 Mock 测试?
Mock 测试(模拟测试)是一种测试方法,通过创建一个“假对象”(Mock 对象)来模拟真实对象的行为,从而在测试中替代依赖的外部组件。简单来说:

当你测试一个类时,如果它依赖其他类(比如数据库、服务),你可以用 Mock 对象假装这些依赖的行为,而不用真的去调用它们。

动态代理API

创建动态代理类会使用到java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口。java.lang.reflect.Proxy主要用于生成动态代理类Class、创建代理类实例,该类实现了java.io.Serializable接口。

分析java.lang.reflect.Proxy的源码:

从这次开始学习一下怎么进行源码审计吧(

1
public class Proxy implements java.io.Serializable

这个implments 是 Java 中的一个关键字,用于声明一个类实现了某个接口(interface)

java.lang.reflect.InvocationHandler接口用于调用Proxy类生成的代理类方法,该类只有一个invoke方法。

使用java.lang.reflect.Proxy动态创建类对象

ClassLoader和Unsafe都有一个叫做defineClassXXX的native方法,我们可以通过调用这个native方法动态的向JVM创建一个类对象,而java.lang.reflect.Proxy类恰好也有这么一个native方法,所以我们也将可以通过调用java.lang.reflect.Proxy类defineClass0方法实现动态创建类对象。

ProxyDefineClassTest.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
{
private static final String TEST_CLASS_NAME = "org.example.HelloWorld";
private static final byte[] TEST_CLASS_BYTES;

static {
try {
TEST_CLASS_BYTES = Files.readAllBytes(Paths.get("/HelloWorld.class"));
} catch (Exception e) {
throw new RuntimeException("Failed to load bytecode", e);
}
}

public static void main(String[] args) {
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
try {
Method method = Proxy.class.getDeclaredMethod("defineClass0", new Class[]{
ClassLoader.class, String.class, byte[].class, int.class, int.class
});
method.setAccessible(true);
Class<?> helloWorldClass = (Class<?>) method.invoke(null, new Object[]{
classLoader, TEST_CLASS_NAME, TEST_CLASS_BYTES, 0, TEST_CLASS_BYTES.length
});
System.out.println(helloWorldClass);
} catch (Exception e) {
e.printStackTrace();
}
}
}

运行结果:

值得注意的是,原文并没有初始化其中的变量,并且由于版本不同,我这里会出现奇怪的报错,切换成jdk8就可以了。
这段代码实现了动态加载字节码并注册为类

通常java类都是通过编译后自动由jvm加载,使用的是标准的ClassLoader 加载 .class 文件或 JAR 包。

但是在这个代码里绕开了传统的 ClassLoader 加载路径,而是:
1. 手动读取一个 .class 文件的字节数组

2.	通过反射调用 defineClass0 这个底层 API

3.	把这个字节数组“注入”进 JVM,生成 Class 对象

Java 序列化和反序列化

很多语言都提供了对象反序列化支持,java在jdk1.1就内置了对象反序列化支持,java对象序列化是指将一个java类实例序列化成字节数组,用于储存对象实例化信息:类成员变量和属性值,java反序列化可以将序列化后的二进制数组转换为对应的java类示例。

java序列化对象可以因其可以方便的将对象转换成字节数组,又可以方便快速的将字节数组反序列化成java对象而非常频繁的被用于socket传输,在rmi(Java远程方法调用-Java Remote Method Invocation)和jmx(Java Management Extensions)服务中对象反序列化机制被强制使用,在http请求中也时常用到反序列化机制,如:直接接收序列化请求的后端服务、使用Base编码序列化字节字符串的方式传递等。

Java 序列化/反序列化

在java中实现对象反序列化非常简单,,实现java.io.Serializable(内部序列化)或java.io.Externalizable(外部序列化)接口即可被序列化,其中java.io.Externalizable接口只是实现了java.io.Serializable接口。

反序列化类对象时有如下限制:
1、被反序列化的类必须存在。
2、serialVersionUID值必须一致。

除此之外,反序列化类对象是不会调用该类构造方法的,因为在反序列化创建类实例时使用了un.reflect.ReflectionFactory.newConstructorForSerialization创建了以恶搞反序列化专用的Constructor(反射构造方法对象),使用这个特殊Constructor可以绕过构造方法创建类实例(前面章节讲sun.misc.Unsafe 的时候我们提到了使用allocateInstance方法也可以实现绕过构造方法创建类实例)。

使用反序列化方式创建类实例代码:

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

import sun.reflect.ReflectionFactory;
import java.lang.reflect.Constructor;
public class ReflectionFactoryTest {

public static void main(String[] args) {
try {
ReflectionFactory factory = ReflectionFactory.getReflectionFactory();
Constructor constructor = factory.newConstructorForSerialization(
DeserializationTest.class, Object.class.getConstructor()
);
System.out.println(constructor.newInstance());
} catch (Exception e) {
e.printStackTrace();
}
}

}
1
ReflectionFactory factory = ReflectionFactory.getReflectionFactory();

通过 ReflectionFactory.getReflectionFactory() 方法获取一个 ReflectionFactory 实例。

1
2
3
Constructor constructor = factory.newConstructorForSerialization(
DeserializationTest.class, Object.class.getConstructor()
);

newConstructorForSerialization 方法是用来获取一个反序列化构造器的,这个构造器可以用来创建对象实例,即使该类没有一个公开的构造器。在这里,DeserializationTest.class 是创建实例的目标类,Object.class.getConstructor() 获取 Object 类的无参构造器。

1
System.out.println(constructor.newInstance());

使用 constructor.newInstance() 来通过反射创建一个 DeserializationTest 类的实例.
运行结果:

ObjectInputStream、ObjectOutputStream

java.io.ObjectOutputStream类最核心的方法是writeObject方法,即序列化类对象。

java.io.ObjectInputStream类最核心的功能是readObject方法,即反序列化类对象。

所以,只需借助ObjectInputStream和ObjectOutputStream类我们就可以实现类的序列化和反序列化功能了

java.io.Serializable

java.io.Serializable是一个空的接口,我们不需要实现java.io.Serializable的任何方法。

一个空接口有什么的意义?其实现java.io.Serializable接口仅仅只用于表示这个类可序列化。实现了java.io.Serializable接口的类原则上都需要生产一个serialVersionUID常量,反序列化是如果双方的serialVersionUID不一致会导致InvalidClassException异常。如果可序列化类未显示声明serialVersionUID,则序列化运行时将基于该类的各个方面计算该类默认的serialVersionUID值。

DeserializationTest.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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package org.example;

import java.io.*;
import java.util.Arrays;

public class DeserializationTest implements Serializable { // 实现java.io.Serializable接口
private String username;
private String email;

// 无参构造器和setter方法
public void setUsername(String username) {
this.username = username;
}

public void setEmail(String email){
this.email = email;
}

// 无参数的getter方法
public String getUsername(){
return username;
}

public String getEmail(){
return email;
}

public static void main(String[] args) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
DeserializationTest t = new DeserializationTest();
t.setUsername("shallot");
t.setEmail("admin@admin.com");

// 序列化
ObjectOutputStream out = new ObjectOutputStream(baos);
out.writeObject(t);
out.flush();
out.close();

System.out.println("DeserializationTest类序列化后的字节数组:" + Arrays.toString(baos.toByteArray()));

// 反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream in = new ObjectInputStream(bais);
DeserializationTest test = (DeserializationTest) in.readObject();
System.out.println("用户名:" + test.getUsername() + " 邮箱:" + test.getEmail());
in.close();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}

核心逻辑其实就是使用ObjectOutputStream类的writeObject方法序列化DeserializationTest类,使用ObjectInputStream类的readObject方法反序列化DeserializationTest类而已。

1
2
3
4
5
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(baos);
out.writeObject(t);
out.flush();
out.close();

• ByteArrayOutputStream:这是一个字节流输出流,它将数据写入到内存中的字节数组,而不是文件。这里用它来存储序列化后的字节数据。

• ObjectOutputStream:这是一个专门用于对象序列化的流。它将 Java 对象转换为字节流。

• out.writeObject(t):将 t 对象序列化并写入到 baos(字节数组输出流)中。这个方法会将 DeserializationTest 对象 t 序列化为字节流。

• out.flush():确保将所有的缓冲区数据写入目标输出流。

• out.close():关闭流,释放资源。

baos.toByteArray() 返回的是一个字节数组,它包含了DeserializationTest 对象 t 的序列化内容。

1
2
3
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream in = new ObjectInputStream(bais);
DeserializationTest test = (DeserializationTest) in.readObject();

• ByteArrayInputStream:这是一个字节流输入流,它从字节数组中读取数据。bais 从 baos.toByteArray() 返回的字节数组中读取数据。

• ObjectInputStream:这是一个专门用于反序列化的流。它将字节流转换回 Java 对象。

• in.readObject():反序列化操作。它将字节流转换为原始的 DeserializationTest 对象,这里返回的 test 就是从字节流恢复的对象。

Arrays.toString(baos.toByteArray()):将字节数组转换为字符串格式,方便打印出来查看。

ObjectOutputStream序列化类对象的主要流程是首先判断序列化的类是否重写了writeObject方法,如果重写了就调用序列化对象自身的writeObject方法序列化,序列化时会先写入类名信息,其次是写入成员变量信息(通过反射获取所有不包含被transient修饰的变量和值)。

java.io.Externalizable

java.io.Externalizable和java.io.Serializable几乎一样,只是java.io.Externalizable接口定义了writeExternal和readExternal方法需要序列化和反序列化的类实现,其余的和java.io.Serializable并无差别。

自定义序列化(writeObject)和反序列化(readObject)

实现了java.io.Serializable接口的类,还可以定义如下方法(反序列化魔术方法),这些方法将会在类序列化或反序列化过程中调用:

private void writeObject(ObjectOutputStream oos),自定义序列化。

private void readObject(ObjectInputStream ois),自定义反序列化。

private void readObjectNoData()。

protected Object writeReplace(),写入时替换对象。

protected Object readResolve()。

当我们对DeserializationTest类进行序列化操作时,会自动调用(反射调用)该类的writeObject(ObjectOutputStream oos)方法,对其进行反序列化操作时也会自动调用该类的readObject(ObjectInputStream)方法,也就是说我们可以通过在待序列化或反序列化的类中定义readObject和writeObject方法,来实现自定义的序列化和反序列化操作,当然前提是,被序列化的类必须有此方法,并且方法的修饰符必须是private。

Apache Commons Collections反序列化漏洞

Apache Commons 是Apache开源的java通用类项目在java的项目中被广泛的使用,Apache commons当中有一个组件叫做Apache Commons Collections,主要封装了java的Collection(集合)相关类对象

了解完前置知识开始学习java反序列化的各种链子((

要努力一个一个调试哇!!!!