《Effective Java 第三版》——第二章 创建和销毁对象
《Effective Java 第三版》——第三章 所有对象都通用的方法
《Effective Java 第三版》——第四章 类和接口
《Effective Java 第三版》——第六章 枚举和注解
《Effective Java 第三版》——第七章 Lambda 和 Stream
《Effective Java 第三版》——第九章 通用编程
《Effective Java 第三版》——第十二章 序列化
反序列化炸弹
调试程序过程中发现:root 的直接子元素不变,但是变量的赋值过程导致不断往子元素上添加节点,形成 2^100 炸弹
package effectivejava.chapter12.item85; import static effectivejava.chapter12.Util.*; import java.util.HashSet; import java.util.Set; // Deserialization bomb - deserializing this stream takes forever - Page 340 public class DeserializationBomb { public static void main(String[] args) throws Exception { System.out.println(bomb().length); deserialize(bomb()); } static byte[] bomb() { Set<Object> root = new HashSet<>(); Set<Object> s1 = root; Set<Object> s2 = new HashSet<>(); for (int i = 0; i < 100; i++) { Set<Object> t1 = new HashSet<>(); Set<Object> t2 = new HashSet<>(); t1.add("foo"+i); // make it not equal to t2 s1.add(t1); s1.add(t2); s2.add(t1); s2.add(t2); s1 = t1; s2 = t2; } return serialize(root); } }
http://www.qmwxb.com/article/2280136.html
有图示
注意区分:逻辑数据 & 物理表示法
package effectivejava.chapter12.item87; import java.io.*; // StringList with a reasonable custom serialized form - Page 349 public final class StringList implements Serializable { private transient int size = 0; private transient Entry head = null; // No longer Serializable! private static class Entry { String data; Entry next; Entry previous; } // Appends the specified string to the list public final void add(String s) { } /** * Serialize this {@code StringList} instance. * * @serialData The size of the list (the number of strings * it contains) is emitted ({@code int}), followed by all of * its elements (each a {@code String}), in the proper * sequence. */ private void writeObject(ObjectOutputStream s) throws IOException { s.defaultWriteObject(); s.writeInt(size); // Write out all elements in the proper order. for (Entry e = head; e != null; e = e.next) s.writeObject(e.data); } private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject(); int numElements = s.readInt(); // Read in all elements and insert them in list for (int i = 0; i < numElements; i++) add((String) s.readObject()); } // Remainder omitted }
package effectivejava.chapter12.item89.enumsingleton; import java.util.*; // Enum singleton - the preferred approach - Page 311 public enum Elvis { INSTANCE; private String[] favoriteSongs = { "Hound Dog", "Heartbreak Hotel" }; public void printFavorites() { System.out.println(Arrays.toString(favoriteSongs)); } }
enum 的源码逃不过的。。。
package effectivejava.chapter12.item90; // Period class with serialization proxy - Pages 363-364 import java.util.*; import java.io.*; // Immutable class that uses defensive copying public final class Period implements Serializable { private final Date start; private final Date end; /** * @param start the beginning of the period * @param end the end of the period; must not precede start * @throws IllegalArgumentException if start is after end * @throws NullPointerException if start or end is null */ public Period(Date start, Date end) { this.start = new Date(start.getTime()); this.end = new Date(end.getTime()); if (this.start.compareTo(this.end) > 0) throw new IllegalArgumentException( start + " after " + end); } public Date start () { return new Date(start.getTime()); } public Date end () { return new Date(end.getTime()); } public String toString() { return start + " - " + end; } // Serialization proxy for Period class private static class SerializationProxy implements Serializable { private final Date start; private final Date end; SerializationProxy(Period p) { this.start = p.start; this.end = p.end; } private static final long serialVersionUID = 234098243823485285L; // Any number will do (Item 87) } // writeReplace method for the serialization proxy pattern private Object writeReplace() { return new SerializationProxy(this); } // readObject method for the serialization proxy pattern private void readObject(ObjectInputStream stream) throws InvalidObjectException { throw new InvalidObjectException("Proxy required"); } }
package effectivejava.chapter12; import java.io.*; public class Util { public static byte[] serialize(Object o) { ByteArrayOutputStream ba = new ByteArrayOutputStream(); try { new ObjectOutputStream(ba).writeObject(o); } catch (IOException e) { throw new IllegalArgumentException(e); } return ba.toByteArray(); } public static Object deserialize(byte[] bytes) { try { return new ObjectInputStream( new ByteArrayInputStream(bytes)).readObject(); } catch (IOException | ClassNotFoundException e) { throw new IllegalArgumentException(e); } } }
package effectivejava.chapter12.item90; import java.io.*; /** * https://blog.csdn.net/Lirx_Tech/article/details/51303966 * [疯狂Java]I/O:其它自定义序列化的方法(transient、writeReplace、readResolve、Externalizable) */ /** * 1. 序列化Person时, 会调用调用writeReplace()生成一个PersonProxy对象, 然后对此对象进行序列化 (不是对Person类对象进行序列化, * 由序列化文件的内容可以得知, 可以查看序列化生成的文件, 文件中内容为如下图 (代码之后的图) * ��sr1effectivejava.chapter12.item90.Person$PersonProxy_7R&Z��[IageLhobbytLjava/lang/String;Lnameq~xpt足球t张三 * 2. 反序列化时, 会调用PersonProxy的readResolve()方法生成一个Person对象, * 最后返回此对象的拷贝 (通过PersonProxy类的readResolve方法和main方法中的输出可以看出) * 3. 因此, Person类的序列化工作完全交给PersonProxy类, 正如此模式的名称所表达的一样 */ public class Person implements Serializable { private final String name; private final String hobby; private final int age; public Person(String name, String hobby, int age) { System.out.println("Person(String name, String hobby, int age)"); //约束条件 if(age < 0 || age > 200) { throw new IllegalArgumentException("非法年龄"); } this.name = name; this.hobby = hobby; this.age = age; } public String getName() { return name; } public String getHobby() { return hobby; } public int getAge() { return age; } private static class PersonProxy implements Serializable { private final String name; private final String hobby; private final int age; public PersonProxy(Person original) { System.out.println("PersonProxy(Person original)"); this.name = original.getName(); this.hobby = original.getHobby(); this.age = original.getAge(); } //反序列化时将序列化代理转变回为外围类的实例 private Object readResolve() { System.out.println("PersonProxy.readResolve()"); Person person = new Person(name, hobby, age); System.out.println("resolveObject: " + person); return person; } } private Object writeReplace() { System.out.println("Person.writeReplace()"); return new PersonProxy(this); //readObject的时候是调用, PersonProxy的readResolve() } //此方法不会执行,为何? //实现writeReplace就不要实现writeObject了,因为writeReplace的返回值会被自动写入输出流中, // 就相当于自动这样调用:writeObject(writeReplace()); private void writeObject(ObjectOutputStream out) { System.out.println("Person.writeObject()"); } //防止攻击者伪造数据, 企图违反约束条件 (如: 违反年龄约束) private Object readObject(ObjectInputStream in) throws InvalidObjectException { System.out.println("Person.readObject()"); throw new InvalidObjectException("Proxy required"); } public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException { Person person = new Person("张三", "足球" ,25); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("serial.ser")); out.writeObject(person); out.flush(); out.close(); Thread.sleep(1000); ObjectInputStream in = new ObjectInputStream(new FileInputStream("serial.ser")); Person deserPerson = (Person) in.readObject(); System.out.println("main: " + person); in.close(); if(person == deserPerson) { System.out.println("序列化前后是同一个对象"); } else { //程序会走这一段, 反序列化会创建对象, 但是不会执行类的构造方法, 而是使用输入流构造对象 System.out.println("序列化前后不是同一个对象, 哈哈哈"); } } } /** * /Library/Java/JavaVirtualMachines/jdk-13.0.2.jdk/Contents/Home/bin/java -Dfile.encoding=UTF-8 -classpath /Users/didi/git/effective-java-3e-source-code/bin effectivejava.chapter12.item90.Person * Person(String name, String hobby, int age) * Person.writeReplace() * PersonProxy(Person original) * PersonProxy.readResolve() * Person(String name, String hobby, int age) * main: effectivejava.chapter12.item90.Person@3f99bd52 * 序列化前后不是同一个对象, 哈哈哈 * * Process finished with exit code 0 */
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException { Person person = new Person("张三", "足球" ,25); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("serial.ser")); out.writeObject(person); out.flush(); out.close(); Thread.sleep(1000); ObjectInputStream in = new ObjectInputStream(new FileInputStream("serial.ser")); Person deserPerson = (Person) in.readObject(); System.out.println("main: " + person); in.close(); if(person == deserPerson) { System.out.println("序列化前后是同一个对象"); } else { //程序会走这一段, 反序列化会创建对象, 但是不会执行类的构造方法, 而是使用输入流构造对象 System.out.println("序列化前后不是同一个对象, 哈哈哈"); } }
out.writeObject(person); 会进入: private Object writeReplace() { System.out.println("Person.writeReplace()"); return new PersonProxy(this); //readObject的时候是调用, PersonProxy的readResolve() } Person deserPerson = (Person) in.readObject(); 会进入: //反序列化时将序列化代理转变回为外围类的实例 private Object readResolve() { System.out.println("PersonProxy.readResolve()"); Person person = new Person(name, hobby, age); System.out.println("resolveObject: " + person); return person; }