Java面試系列-webapp文件夾和WebContent文件夾的區別?java
程序員面試系列:Spring MVC能響應HTTP請求的緣由?程序員
Java程序員面試系列-什麼是Java Marker Interface(標記接口)web
使用JDK自帶的工具jstack找出形成運行程序死鎖的緣由面試
JavaScript面試系列:JavaScript設計模式之橋接模式和懶加載編程
使用JavaScript ES6的新特性計算Fibonacci(非波拉契數列)json
單例模式在不少Java程序員的眼中,應該是設計模式裏最簡單的一種了。那麼單例模式可能會被攻擊,您據說過麼?設計模式
說到「單例模式被攻擊」這個話題,你們最容易想到的可能就是經過序列化/反序列化來攻擊單例模式,由於一個對象實例序列化再反序列化後,獲得的新的對象雖然各字段內容和原字段一致,然而對象地址和原始對象地址相比已經發生了變化,所以它們是兩個不一樣的對象。app
上面的結論徹底正確,然而除了序列化/反序列化,單例模式還可能遭受另外一種方式的攻擊,即反射攻擊(Reflection attack)。webapp
看一個具體例子:
public class JerrySingleton { private String name; private JerrySingleton(){ name = "Jerry"; } private static class SingletonHolder{ private static final JerrySingleton INSTANCE = new JerrySingleton(); } public static JerrySingleton getInstance() { return SingletonHolder.INSTANCE; } }
上面是一個餓漢式單例。
然而我只須要將這個單例類JerrySingleton的構造函數經過反射設置成能夠訪問Accessible,而後就能經過反射調用該構造函數,進而生成新的對象實例。這樣就破壞了單例模式。
Class<?> classType = JerrySingleton.class; Constructor<?> c = classType.getDeclaredConstructor(null); c.setAccessible(true); JerrySingleton e1 = (JerrySingleton)c.newInstance(); JerrySingleton e2 = JerrySingleton.getInstance(); System.out.println(e1 == e2);
第6行代碼會打印false。
針對這種攻擊,一種可行的防護措施是在單例類的構造函數內定義一個布爾變量,初始化爲false。當構造函數執行後,該變量被置爲true。若是接下來構造函數再次被執行,則人爲拋出異常,避免構造函數重複執行。
public class JerrySingletonImproved { private static boolean flag = false; private JerrySingletonImproved(){ synchronized(JerrySingletonImproved.class) { if(flag == false) { flag = !flag; } else { throw new RuntimeException("Singleton violated"); } } } }
這種防護措施沒法從根本上杜絕Singleton被攻擊,由於攻擊者仍舊能夠經過反射來修改布爾變量flag的值,從而繞過這個檢查。
最理想的不會受到攻擊的單例模式實現是藉助Java裏枚舉類Enumeration的特性:
這種實現類型的單例模式的消費代碼:
System.out.println("Name:" + JerrySingletonAnotherApproach.INSTANCE.getName());
若是攻擊者經過前面介紹的反射代碼對這種實現方式的單例進行攻擊,JDK會拋出NoSuchMethodException異常:
Exception in thread "main" java.lang.NoSuchMethodException: singleton.JerrySingletonAnotherApproach.<init>() at java.lang.Class.getConstructor0(Class.java:3082) at java.lang.Class.getDeclaredConstructor(Class.java:2178) at singleton.SingletonAttack.test3(SingletonAttack.java:31) at singleton.SingletonAttack.main(SingletonAttack.java:43)
究其緣由,是由於如今咱們是經過Java枚舉方式實現的單例,枚舉類沒有傳統意義上的構造函數,所以對這種反射攻擊免疫。
要獲取更多Jerry的原創技術文章,請關注公衆號"汪子熙"或者掃描下面二維碼: