单例模式:基于反射和反序列化破解单例模式的漏洞及其解决方法
單例模式使得在創(chuàng)建類對象的時候只創(chuàng)建一個對象實例。上一節(jié)講解了五種實現(xiàn)單例模式的方式。
分別為:餓漢模式、懶漢模式、double check、靜態(tài)內(nèi)部類、枚舉
但是基于反射和反序列化可以破解單例模式的單一實例,在使用反射時可以通過調(diào)用setAccesible()直接調(diào)用私有構造器,創(chuàng)建新的實例;在反序列化的時候會直接創(chuàng)建新的對象實例。但是以上漏洞只針對前四種方式,枚舉由于是基于JVM底層實現(xiàn)機制,是天然的單例模式。
假設我們使用餓漢的實現(xiàn)方式創(chuàng)建了一個單例類:
package com.test.test1.danlimoshi;public class EHanShi {private static EHanShi eHanShi = new EHanShi();private EHanShi(){}public static EHanShi getInstance(){return eHanShi;} }接下來基于反射實現(xiàn)創(chuàng)建兩個不同的實例。
package com.test.test1.danlimoshi;import java.lang.reflect.Constructor;public class Client2 {public static void main(String[] args) throws Exception {EHanShi eHanShi1 = EHanShi.getInstance();EHanShi eHanShi2 = EHanShi.getInstance();System.out.println(eHanShi1);System.out.println(eHanShi2);Class<EHanShi> clazz = (Class<EHanShi>) Class.forName("com.test.test1.danlimoshi.EHanShi");Constructor<EHanShi> constructor = clazz.getDeclaredConstructor(null);constructor.setAccessible(true); //跳過檢查機制,直接調(diào)用私有構造器EHanShi eHanShi3 = constructor.newInstance();System.out.println(eHanShi3);} }打印結(jié)果:顯然創(chuàng)新了新的實例。
com.test.test1.danlimoshi.EHanShi@1b6d3586
com.test.test1.danlimoshi.EHanShi@1b6d3586
com.test.test1.danlimoshi.EHanShi@4554617c
如何解決?
在私有構造器通過拋出異常處理。即當創(chuàng)建第二個實例的時候就刨出異常。
package com.test.test1.danlimoshi;public class EHanShi {private static EHanShi eHanShi = new EHanShi();private EHanShi(){if(eHanShi != null){throw new RuntimeException();}}public static EHanShi getInstance(){return eHanShi;} }接下來基于序列化創(chuàng)建新的實例。
package com.test.test1.danlimoshi;import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Constructor;public class Client2 {public static void main(String[] args) throws Exception {EHanShi eHanShi1 = EHanShi.getInstance();EHanShi eHanShi2 = EHanShi.getInstance();System.out.println(eHanShi1);System.out.println(eHanShi2);// Class<EHanShi> clazz = (Class<EHanShi>) Class.forName("com.test.test1.danlimoshi.EHanShi"); // Constructor<EHanShi> constructor = clazz.getDeclaredConstructor(null); // constructor.setAccessible(true); // EHanShi eHanShi3 = constructor.newInstance(); // System.out.println(eHanShi3);try(FileOutputStream fos = new FileOutputStream("D:/a.txt")){ObjectOutputStream oos = new ObjectOutputStream(fos);oos.writeObject(eHanShi1);}catch (Exception e){e.printStackTrace();}FileInputStream fis = new FileInputStream("D:/a.txt");ObjectInputStream ois = new ObjectInputStream(fis);EHanShi eHanShi3 = (EHanShi)ois.readObject();System.out.println(eHanShi3);} }打印結(jié)果:顯然創(chuàng)建了新的實例。
com.test.test1.danlimoshi.EHanShi@1b6d3586
com.test.test1.danlimoshi.EHanShi@1b6d3586
com.test.test1.danlimoshi.EHanShi@6d03e736
如何解決?
在單例類中創(chuàng)建一個方法readResolve(),基于回調(diào)機制,在反序列化的時候直接會調(diào)用這個方法。返回當前實例。
package com.test.test1.danlimoshi;import java.io.Serializable;public class EHanShi implements Serializable {private static EHanShi eHanShi = new EHanShi();private EHanShi(){if(eHanShi != null){throw new RuntimeException();}}public static EHanShi getInstance(){return eHanShi;}public Object readResolve(){return eHanShi;} }那么就不會創(chuàng)建新的實例了。
測試五種實現(xiàn)方式的耗時:
package com.test.test1.danlimoshi;import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit;//測試多線程環(huán)境下實現(xiàn)這幾種方式的耗時 public class Client3 {public static void main(String[] args) throws Exception {long startTime = System.currentTimeMillis();int maxCount = 10;CountDownLatch countDownLatch = new CountDownLatch(maxCount);for(int i =0;i<maxCount;i++) {new Thread(new Runnable() {@Overridepublic void run() {for (int j = 0; j < 100000; j++) {//Object o = EHanShi.getInstance();//Object o =LanHanShi.getInstance();Object o =JingTaiLeiJiaZai.getInstance();}countDownLatch.countDown();}}).start();}countDownLatch.await();//當10個線程執(zhí)行完成,也即是計數(shù)器值為0,main線程繼續(xù)往下執(zhí)行long endTime = System.currentTimeMillis();System.out.println("總耗時:"+(endTime-startTime));}} 《新程序員》:云原生和全面數(shù)字化實踐50位技術專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的单例模式:基于反射和反序列化破解单例模式的漏洞及其解决方法的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 设计模式:纵观
- 下一篇: 创建型模式:工厂模式(简单工厂+工厂方法