Java 提供了一种对象序列化的机制,该机制中,一个对象可以被表示为一个字节序列,该字节序列包括该对象的数据、有关对象的类型的信息和存储在对象中数据的类型。把字节序列恢复为对象的过程称为对象的反序列化。
序列化的条件一个类是否可以序列化的条件
- 该类需要实现java.io.Serializable接口
序列化和反序列化的用途主要是用于客户端和服务端交互数据,假如此时一台主机A和一台主机B。如果
例子首先定义一个Person类,实现了Serializable接口。
package com.darkerbox.deserialization;
import java.io.IOException;
import java.io.Serializable;
public class Person implements Serializable {
String name;
String age;
public Person() {
}
public Person(String name, String age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException, IOException {
//执行默认的readObject()方法
in.defaultReadObject();
//执行命令
Runtime.getRuntime().exec("calc.exe");
}
}
再写一个序列化的类
package com.darkerbox.deserialization;
import java.io.*;
public class Ser {
public static void main(String[] args) throws IOException {
Person person = new Person();
person.name = "Vicl1fe";
person.age = "30";
// 实例化一个对象输出流。
ObjectOutputStream obj = new ObjectOutputStream(new FileOutputStream(new File("ser")));
// 序列化 person这个对象
obj.writeObject(person);
obj.close();
}
}
再写一个反序列化的类
package com.darkerbox.deserialization;
import java.io.*;
public class Des {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 实例化一个对象输入流。
ObjectInputStream obj = new ObjectInputStream(new FileInputStream(new File("ser")));
// 反序列化对象,然后强转为Person对象。
Person person = (Person)obj.readObject();
System.out.println(person.getName());
System.out.println(person.getAge());
obj.close();
}
}
可以看到在序列化的时候,实例化了一个Person对象。并且设置Name为Vicl1fe,Age为30。然后序列化这个对象到ser文件中。
使用WinHex打开这个文件,如果文件头是 AC ED 00 05,那么基本上该文件就是序列化后的文件。
然后在反序列化类中定义ObjectInputStream对象,然后使用readObject方法反序列化为Object对象,然后强转为Person对象,就可以得到正确的对象。
简单的反序列化漏洞我们给Person类添加一个readObject方法。最终如下
package com.darkerbox.deserialization;
import java.io.IOException;
import java.io.Serializable;
public class Person implements Serializable {
String name;
String age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException, IOException {
//执行默认的readObject()方法
in.defaultReadObject();
//执行命令
Runtime.getRuntime().exec("calc.exe");
}
}
这里,我们不需要重新生成序列化文件,可以直接运行反序列化代码。成功弹出计算器。
为什么呢?因为如果实现Serializable的类重写了readObject方法,当反序列化调用readObject方法的时候就会调用重写后的readObject方法。
注意in.defaultReadObject();这里调用的默认的defaultReadObject方法。必须调用这个方法,才可以正确的进行反序列化获取对象的属性值。
反射 什么是反射Java语言中 一种 动态(运行时)访问、检测 & 修改它本身的能力
Java 类的成员包括以下三类:
- 属性字段
- 构造函数
- 方法
通过反射我们可以实例化任意类的对象。调用任意方法。(个人认为)
例子正常的实例化对象的方式
// 正常的实例化对象的方式,直接new一个对象。
Person person = new Person();
person.setName("Vicl1fe");
person.setAge("30");
反射获取对象的方式
// 反射获取对象
// 反射获取对象
// 获取Class对象clz
Class clz = Class.forName("com.darkerbox.deserialization.Person");
// 调用getMethod方法可以获取一个'方法对象',参数1是方法名,参数2是该方法的参数的类型。
Method method = clz.getMethod("setName", String.class);
// 调用getConstructor方法来获取'构造函数对象'
Constructor constructor = clz.getConstructor();
// 构造函数对象调用netInstance来实例化一个真正的对象。
Object object = constructor.newInstance();
// '方法对象'调用invoke方法可以执行该方法。参数需要传入一个对象,后面是参数值
method.invoke(object,"30");
// 获取getName方法对象。
Method method1 = clz.getMethod("getName");
// 调用getName方法
System.out.println(method1.invoke(object));
如果我们一开始不知道我们应该实例化什么对象,那么我们就不能使用正常实例化对象的方式来获取对象。
但是可以通过反射的方式来获取对象。只要知道类的全路径名就可以获取该类的所有信息,并且可以实例化该类的对象。
获取反射中的Class对象在反射中,要获取一个类或调用一个类的方法,我们首先需要获取到该类的 Class 对象。
1.使用Class.forName方法获取,此方法需要知道类的全路径名
Class clz = Class.forName("java.lang.String");
2.使用.class方法
Class clz = String.class;
3.使用类对象的 getClass() 方法。
String str = new String("Hello");
Class clz = str.getClass();
通过反射创建类对象
通过反射创建类对象主要有两种方式:通过 Class 对象的 newInstance() 方法、通过 Constructor 对象的 newInstance() 方法。
1.通过 Class 对象的 newInstance() 方法。
Class clz2 = Person.class; Person apple = (Person)clz2.newInstance();
2.通过 Constructor 对象的 newInstance() 方法
Class clz3 = Person.class; Constructor constructor1 = clz.getConstructor(); Person person1 = (Person)constructor1.newInstance();
通过 Constructor 对象创建类对象可以选择特定构造方法,而通过 Class 对象则只能使用默认的无参数构造方法。
调用有参构造函数方法如下
Class clz4 = Person.class;
Constructor constructor4 = clz.getConstructor(String.class, String.class);
Person person4 = (Person)constructor4.newInstance("Vicl1fe", "20");
通过反射获取类属性、方法、构造器
我们通过 Class 对象的 getFields() 方法可以获取 Class 类的属性,但无法获取私有属性。
Class clz6 = Person.class;
Field[] fields = clz6.getFields();
for (Field field : fields) {
System.out.println(field.getName());
}
而如果使用 Class 对象的 getDeclaredFields() 方法则可以获取包括私有属性在内的所有属性:
Class clz6 = Person.class;
Field[] fields = clz6.getDeclaredFields();
for (Field field : fields) {
System.out.println(field.getName());
}
与获取类属性一样,当我们去获取类方法、类构造器时,则是getMethod、getConstructor,
如果要获取私有方法或私有构造器,则必须使用有 declared 关键字的方法。
https://www.cnblogs.com/chanshuyi/p/head_first_of_reflection.html
代理 静态代理这种代理方式需要代理对象和目标对象实现一样的接口。
优点:
- 可以在不修改目标对象的前提下扩展目标对象的功能。
缺点:
- 冗余。由于代理对象要实现与目标对象一致的接口,会产生过多的代理类。
- 不易维护。一旦接口增加方法,目标对象与代理对象都要进行修改。
举一个例子
首先定义一个接口:
public interface Rental {
// "卖"的操作
public void sale();
}
定义一个委托类,这个类主要是为了实现Rental接口,添加具体的对象。
public class Entrust implements Rental {
// 实现接口"卖"的操作,添加具体的操作对象,最终实现"卖房子"。
public void sale() {
System.out.println("卖房子");
}
}
定义一个代理类
public class AgentRental implements Rental {
private Rental target; // 被代理对象
public AgentRental(Rental target) {
this.target = target;
}
public void sale() {
System.out.println("房子卖500w"); // 添加新的操作
target.sale(); // 调用Entrust委托类的sale方法
}
}
最后定义一个测试类
public class main {
// 静态代理使用示例
public static void consumer(Rental subject) {
subject.sale();
}
public static void main(String[] args) {
Rental test = new Entrust();
System.out.println("---使用代理之前---");
consumer(test);
System.out.println("---使用代理之后---");
consumer(new AgentRental(test));
}
}
运行结果如下
---使用代理之前--- 卖房子 ---使用代理之后--- 房子卖500w 卖房子
仔细想一下静态代理和没有静态代理的区别。
通过上面的代码,可以看出静态代理的优点,假如有一天你要便宜点,卖400w。不需要修改Entrust类,只需要修改代理类AgentRental即可。不需要修改最底层的代码。
但这个是我们通过代理类进行实现更改的方法,如果当我们需要过多的代理类对委托类进行修改的情况下,则可能出现下图情况:
由此可以我们得知此静态代理的缺点:
当我们的接口类需要增加和删除功能的时候,委托类和代理类都需要更改,不容易维护。
同时如果需要代理多个类的时候,每个委托类都要编写一个代理类,会导致代理类繁多,不好管理。
因为java静态代理是对类进行操作的,我们需要一个个代理类去实现对委托类的更改操作,针对这个情况,我们可以利用动态代理来解决,通过程序运行时自动生成代理类。
------------摘自https://xz.aliyun.com/t/9197#toc-2
相当于Rental每增加一个方法,就会增加至少一个代理类。
动态代理Java动态代理位于Java.lang.reflect包下,我们一般就仅涉及Java.lang.reflect.Proxy类与InvocationHandler接口,使用其配合反射,完成实现动态代理的操作。
静态代理与动态代理的区别主要在:
- 静态代理在编译时就已经实现,编译完成后代理类是一个实际的class文件
- 动态代理是在运行时动态生成的,即编译完成后没有实际的class文件,而是在运行时动态生成类字节码,并加载到JVM中
动态代理对象不需要实现接口,但是要求目标对象必须实现接口,否则不能使用动态代理。
首先看看InvocationHandler这个接口。
这个接口只有一个invoke方法。其他全是注释。这个接口负责提供调用代理操作。
Proxy类:负责动态构建代理类
接口和委托类就用上面静态代理的例子,这里可以直接写动态代理的代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class DynamicAgent implements InvocationHandler {
// target变量为委托类对象
private Object target;
public DynamicAgent(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 添加自定义的委托逻辑
System.out.println("房子卖500w");
// 调用委托类的方法
Object result = method.invoke(target,args);
return result;
}
// 实现 java.lang.reflect.InvocationHandler.invoke()方法
}
测试类。
import com.darkerbox.proxy.Entrust;
import com.darkerbox.proxy.Rental;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class main {
public static void main(String[] args) {
// 获取委托类的实例对象
Entrust testEntrust = new Entrust();
// 获取CLassLoader
ClassLoader classLoader = testEntrust .getClass().getClassLoader();
// 获取所有接口
Class[] interfaces = testEntrust .getClass().getInterfaces();
// 获取一个调用处理器
InvocationHandler invocationHandler = new DynamicAgent(testEntrust);
// 查看生成的代理类
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
// 创建代理对象
Rental proxy = (Rental) Proxy.newProxyInstance(classLoader,interfaces,invocationHandler);
// 调用代理对象的sayHello()方法
proxy.sale();
}
}
运行结果
房子卖500w 卖房子
一步步分析。
首先在测试类的main方法。实例化了一个委托类的对象。
// 获取委托类的实例对象 Entrust testEntrust = new Entrust();
然后又获取类的CLassLoader和所有接口。
// 获取CLassLoader ClassLoader classLoader = testEntrust .getClass().getClassLoader(); // 获取所有接口 Class[] interfaces = testEntrust .getClass().getInterfaces();
创建一个调用处理器。里面实现了invoke方法。
// 获取一个调用处理器 InvocationHandler invocationHandler = new DynamicAgent(testEntrust);
设置为true,会在项目目录下生成com.sun.proxy目录及代理类的class文件,
// 查看生成的代理类,
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
如下图
根据classLoader和interfaces和上面创建的调用处理器这三个而实例化一个Proxy代理对象。
// 创建代理对象 Rental proxy = (Rental) Proxy.newProxyInstance(classLoader,interfaces,invocationHandler);
当调用代理对象的任何方法时,都会先调用处理器的invoke方法。
// 调用代理对象的sale方法 proxy.sale();参考
https://segmentfault.com/a/1190000011291179
https://xz.aliyun.com/t/9197#toc-2
等之后单独一篇文章写吧
javassitJava 字节码以二进制的形式存储在 .class 文件中,每一个 .class 文件包含一个 Java 类或接口。Javaassist 就是一个用来 处理 Java 字节码的类库。它可以在一个已经编译好的类中添加新的方法,或者是修改已有的方法,并且不需要对字节码方面有深入的了解。同时也可以去生成一个新的类对象,通过完全手动的方式。
引入包
org.javassist javassist 3.25.0-GA
编写创建一个Person类
package com.darkerbox.javassit;
import javassist.*;
public class ssit_test {
public static void createPseson() throws Exception {
ClassPool pool = ClassPool.getDefault();
// 1. 创建一个空类
CtClass cc = pool.makeClass("Person");
// 2. 新增一个字段 private String name;
// 字段名为name
CtField param = new CtField(pool.get("java.lang.String"), "name", cc);
// 访问级别是 private
param.setModifiers(Modifier.PRIVATE);
// 初始值是 "xiaoming"
cc.addField(param, CtField.Initializer.constant("xiaoming"));
// 3. 生成 getter、setter 方法
cc.addMethod(CtNewMethod.setter("setName", param));
cc.addMethod(CtNewMethod.getter("getName", param));
// 4. 添加无参的构造函数
CtConstructor cons = new CtConstructor(new CtClass[]{}, cc);
cons.setBody("{name = "xiaohei";}");
cc.addConstructor(cons);
// 5. 添加有参的构造函数
cons = new CtConstructor(new CtClass[]{pool.get("java.lang.String")}, cc);
// $0=this / $1,$2,$3... 代表方法参数
cons.setBody("{$0.name = $1;}");
cc.addConstructor(cons);
// 6. 创建一个名为printName方法,无参数,无返回值,输出name值
CtMethod ctMethod = new CtMethod(CtClass.voidType, "printName", new CtClass[]{}, cc);
ctMethod.setModifiers(Modifier.PUBLIC);
ctMethod.setBody("{System.out.println(name);}");
cc.addMethod(ctMethod);
//这里会将这个创建的类对象编译为.class文件
cc.writeFile("./");
}
public static void main(String[] args) {
try {
createPseson();
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行之后会在项目目录下生成一个class文件。我们来看一下这个文件内容。
public class Person {
private String name = "xiaoming";
public void setName(String var1) {
this.name = var1;
}
public String getName() {
return this.name;
}
public Person() {
this.name = "xiaohei";
}
public Person(String var1) {
this.name = var1;
}
public void printName() {
System.out.println(this.name);
}
}
将两个代码进行对比,就理解了。
调用生成的类对象1.通过反射方式调用
Object person = cc.toClass().newInstance();
Method setnamme = person.getClass().getMethod("setName",String.class);
setnamme.invoke(person,"小马");
Method print = person.getClass().getMethod("printName");
print.invoke(person);
参考
https://www.cnblogs.com/rickiyang/p/11336268.html



