java 动态代理
java学习笔记
Java 动态代理
Java反射提供了一种类动态代理机制,可以通过代理接口实现类来完成程序无侵入式扩展。
Java动态代理主要使用场景:
统计方法执行所耗时间。
在方法执行前后添加日志。
检测方法的参数或返回值。
方法访问权限控制。
方法Mock测试。
1 | 什么是 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 | { |
运行结果:
值得注意的是,原文并没有初始化其中的变量,并且由于版本不同,我这里会出现奇怪的报错,切换成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 | package org.example; |
1 | ReflectionFactory factory = ReflectionFactory.getReflectionFactory(); |
通过 ReflectionFactory.getReflectionFactory() 方法获取一个 ReflectionFactory 实例。
1 | Constructor constructor = factory.newConstructorForSerialization( |
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 | package org.example; |
核心逻辑其实就是使用ObjectOutputStream类的writeObject方法序列化DeserializationTest类,使用ObjectInputStream类的readObject方法反序列化DeserializationTest类而已。
1 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
• ByteArrayOutputStream:这是一个字节流输出流,它将数据写入到内存中的字节数组,而不是文件。这里用它来存储序列化后的字节数据。
• ObjectOutputStream:这是一个专门用于对象序列化的流。它将 Java 对象转换为字节流。
• out.writeObject(t):将 t 对象序列化并写入到 baos(字节数组输出流)中。这个方法会将 DeserializationTest 对象 t 序列化为字节流。
• out.flush():确保将所有的缓冲区数据写入目标输出流。
• out.close():关闭流,释放资源。
baos.toByteArray() 返回的是一个字节数组,它包含了DeserializationTest 对象 t 的序列化内容。
1 | ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); |
• 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反序列化的各种链子((
要努力一个一个调试哇!!!!