Java设计模式——为什么要用枚举实现单例模式(避免反射、序列化问题)
1、序言
??相信如果能看到我這篇博客的小伙伴,肯定都看過(guò)Joshua Bloch大神說(shuō)過(guò)的這句話:“單元素的枚舉類型已經(jīng)成為實(shí)現(xiàn)Singleton的最佳方法”。其實(shí),第一次讀到這句話,我連其中說(shuō)的單元素指什么都不知道,尷尬。后來(lái),網(wǎng)上看了搜索了好幾篇文章,發(fā)現(xiàn)基本上都是轉(zhuǎn)載自相同的一篇文章,而我的困惑是“為什么要用枚舉類型實(shí)現(xiàn)單例模式呢?”,文章中都說(shuō)的很籠統(tǒng),于是決定自己結(jié)合Joshua Bloch的《effective java》寫一篇總結(jié)下,給后來(lái)的同學(xué)做個(gè)參考。
2、什么是單例模式
??關(guān)于什么是單例模式的定義,我之前的一篇文章《Java設(shè)計(jì)模式——單例模式的七種寫法》中有寫過(guò),主要是講餓漢懶漢、線程安全方面得問(wèn)題,我就不再重復(fù)了,只是做下單例模式的總結(jié)。之前文章中實(shí)現(xiàn)單例模式三個(gè)主要特點(diǎn):1、構(gòu)造方法私有化;2、實(shí)例化的變量引用私有化;3、獲取實(shí)例的方法共有。
??如果不使用枚舉,大家采用的一般都是“雙重檢查加鎖”這種方式,如下,對(duì)單例模式還不了解的同學(xué)希望先大致看下這種思路,接下來(lái)的3.1和3.2都是針對(duì)這種實(shí)現(xiàn)方式進(jìn)行探討,了解過(guò)單例模式的同學(xué)可以跳過(guò)直接看3.1的內(nèi)容:
public class Singleton {private volatile static Singleton uniqueInstance;private Singleton() {}public static Singleton getInstance() {if (uniqueInstance == null) {synchronized (Singleton.class){if(uniqueInstance == null){//進(jìn)入?yún)^(qū)域后,再檢查一次,如果仍是null,才創(chuàng)建實(shí)例uniqueInstance = new Singleton();}}}return uniqueInstance;} }3、為什么要用枚舉單例
3.1私有化構(gòu)造器并不保險(xiǎn)
??《effective java》中只簡(jiǎn)單的提了幾句話:“享有特權(quán)的客戶端可以借助AccessibleObject.setAccessible方法,通過(guò)反射機(jī)制調(diào)用私有構(gòu)造器。如果需要低于這種攻擊,可以修改構(gòu)造器,讓它在被要求創(chuàng)建第二個(gè)實(shí)例的時(shí)候拋出異常。”下面我以代碼來(lái)演示一下,大家就能明白:
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {Singleton s=Singleton.getInstance();Singleton sUsual=Singleton.getInstance();Constructor<Singleton> constructor=Singleton.class.getDeclaredConstructor();constructor.setAccessible(true);Singleton sReflection=constructor.newInstance();System.out.println(s+"\n"+sUsual+"\n"+sReflection);System.out.println("正常情況下,實(shí)例化兩個(gè)實(shí)例是否相同:"+(s==sUsual));System.out.println("通過(guò)反射攻擊單例模式情況下,實(shí)例化兩個(gè)實(shí)例是否相同:"+(s==sReflection));}輸出為:
com.lxp.pattern.singleton.Singleton@1540e19d com.lxp.pattern.singleton.Singleton@1540e19d com.lxp.pattern.singleton.Singleton@677327b6 正常情況下,實(shí)例化兩個(gè)實(shí)例是否相同:true 通過(guò)反射攻擊單例模式情況下,實(shí)例化兩個(gè)實(shí)例是否相同:false??既然存在反射可以攻擊的問(wèn)題,就需要按照J(rèn)oshua Bloch做說(shuō)的,加個(gè)異常處理。這里我就不演示了,等會(huì)講到枚舉我再演示。
3.2 序列化問(wèn)題
大家先看下面這個(gè)代碼:
public class SerSingleton implements Serializable {private volatile static SerSingleton uniqueInstance;private String content;public String getContent() {return content;}public void setContent(String content) {this.content = content;}private SerSingleton() {}public static SerSingleton getInstance() {if (uniqueInstance == null) {synchronized (SerSingleton.class) {if (uniqueInstance == null) {uniqueInstance = new SerSingleton();}}}return uniqueInstance;}public static void main(String[] args) throws IOException, ClassNotFoundException {SerSingleton s = SerSingleton.getInstance();s.setContent("單例序列化");System.out.println("序列化前讀取其中的內(nèi)容:"+s.getContent());ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("SerSingleton.obj"));oos.writeObject(s);oos.flush();oos.close();FileInputStream fis = new FileInputStream("SerSingleton.obj");ObjectInputStream ois = new ObjectInputStream(fis);SerSingleton s1 = (SerSingleton)ois.readObject();ois.close();System.out.println(s+"\n"+s1);System.out.println("序列化后讀取其中的內(nèi)容:"+s1.getContent());System.out.println("序列化前后兩個(gè)是否同一個(gè):"+(s==s1));}}先猜猜看輸出結(jié)果:
序列化前讀取其中的內(nèi)容:單例序列化 com.lxp.pattern.singleton.SerSingleton@135fbaa4 com.lxp.pattern.singleton.SerSingleton@58372a00 序列化后讀取其中的內(nèi)容:單例序列化 序列化前后兩個(gè)是否同一個(gè):false??可以看出,序列化前后兩個(gè)對(duì)象并不想等。為什么會(huì)出現(xiàn)這種問(wèn)題呢?這個(gè)講起來(lái),又可以寫一篇博客了,簡(jiǎn)單來(lái)說(shuō)“任何一個(gè)readObject方法,不管是顯式的還是默認(rèn)的,它都會(huì)返回一個(gè)新建的實(shí)例,這個(gè)新建的實(shí)例不同于該類初始化時(shí)創(chuàng)建的實(shí)例。”當(dāng)然,這個(gè)問(wèn)題也是可以解決的,想詳細(xì)了解的同學(xué)可以翻看《effective java》第77條:對(duì)于實(shí)例控制,枚舉類型優(yōu)于readResolve。
3.3枚舉類詳解
3.3.1枚舉單例定義
咱們先來(lái)看一下枚舉類型單例:
public enum EnumSingleton {INSTANCE;public EnumSingleton getInstance(){return INSTANCE;} }怎么樣,是不是覺(jué)得好簡(jiǎn)單,只有這么點(diǎn)代碼,其實(shí)也沒(méi)這么簡(jiǎn)單啦,編譯后相當(dāng)于:
public final class EnumSingleton extends Enum< EnumSingleton> {public static final EnumSingleton ENUMSINGLETON;public static EnumSingleton[] values();public static EnumSingleton valueOf(String s);static {}; }咱們先來(lái)驗(yàn)證下會(huì)不會(huì)避免上述的兩個(gè)問(wèn)題,先看下枚舉單例的優(yōu)點(diǎn),然后再來(lái)講原理。
3.3.2避免反射攻擊
public enum EnumSingleton {INSTANCE;public EnumSingleton getInstance(){return INSTANCE;}public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {EnumSingleton singleton1=EnumSingleton.INSTANCE;EnumSingleton singleton2=EnumSingleton.INSTANCE;System.out.println("正常情況下,實(shí)例化兩個(gè)實(shí)例是否相同:"+(singleton1==singleton2));Constructor<EnumSingleton> constructor= null;constructor = EnumSingleton.class.getDeclaredConstructor();constructor.setAccessible(true);EnumSingleton singleton3= null;singleton3 = constructor.newInstance();System.out.println(singleton1+"\n"+singleton2+"\n"+singleton3);System.out.println("通過(guò)反射攻擊單例模式情況下,實(shí)例化兩個(gè)實(shí)例是否相同:"+(singleton1==singleton3));} }結(jié)果就報(bào)異常了:
Exception in thread "main" java.lang.NoSuchMethodException: com.lxp.pattern.singleton.EnumSingleton.<init>()at java.lang.Class.getConstructor0(Class.java:3082)at java.lang.Class.getDeclaredConstructor(Class.java:2178)at com.lxp.pattern.singleton.EnumSingleton.main(EnumSingleton.java:20)at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at java.lang.reflect.Method.invoke(Method.java:498)at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144) 正常情況下,實(shí)例化兩個(gè)實(shí)例是否相同:true??然后debug模式,可以發(fā)現(xiàn)是因?yàn)镋numSingleton.class.getDeclaredConstructors()獲取所有構(gòu)造器,會(huì)發(fā)現(xiàn)并沒(méi)有我們所設(shè)置的無(wú)參構(gòu)造器,只有一個(gè)參數(shù)為(String.class,int.class)構(gòu)造器,然后看下Enum源碼就明白,這兩個(gè)參數(shù)是name和ordial兩個(gè)屬性:
public abstract class Enum<E extends Enum<E>>implements Comparable<E>, Serializable {private final String name;public final String name() {return name;}private final int ordinal;public final int ordinal() {return ordinal;}protected Enum(String name, int ordinal) {this.name = name;this.ordinal = ordinal;}//余下省略??枚舉Enum是個(gè)抽象類,其實(shí)一旦一個(gè)類聲明為枚舉,實(shí)際上就是繼承了Enum,所以會(huì)有(String.class,int.class)的構(gòu)造器。既然是可以獲取到父類Enum的構(gòu)造器,那你也許會(huì)說(shuō)剛才我的反射是因?yàn)樽陨淼念悰](méi)有無(wú)參構(gòu)造方法才導(dǎo)致的異常,并不能說(shuō)單例枚舉避免了反射攻擊。好的,那我們就使用父類Enum的構(gòu)造器,看看是什么情況:
public enum EnumSingleton {INSTANCE;public EnumSingleton getInstance(){return INSTANCE;}public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {EnumSingleton singleton1=EnumSingleton.INSTANCE;EnumSingleton singleton2=EnumSingleton.INSTANCE;System.out.println("正常情況下,實(shí)例化兩個(gè)實(shí)例是否相同:"+(singleton1==singleton2));Constructor<EnumSingleton> constructor= null; // constructor = EnumSingleton.class.getDeclaredConstructor();constructor = EnumSingleton.class.getDeclaredConstructor(String.class,int.class);//其父類的構(gòu)造器constructor.setAccessible(true);EnumSingleton singleton3= null;//singleton3 = constructor.newInstance();singleton3 = constructor.newInstance("testInstance",66);System.out.println(singleton1+"\n"+singleton2+"\n"+singleton3);System.out.println("通過(guò)反射攻擊單例模式情況下,實(shí)例化兩個(gè)實(shí)例是否相同:"+(singleton1==singleton3));} }然后咱們看運(yùn)行結(jié)果:
正常情況下,實(shí)例化兩個(gè)實(shí)例是否相同:true Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objectsat java.lang.reflect.Constructor.newInstance(Constructor.java:417)at com.lxp.pattern.singleton.EnumSingleton.main(EnumSingleton.java:25)at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at java.lang.reflect.Method.invoke(Method.java:498)at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)??繼續(xù)報(bào)異常。之前是因?yàn)闆](méi)有無(wú)參構(gòu)造器,這次拿到了父類的構(gòu)造器了,只是在執(zhí)行第17行(我沒(méi)有復(fù)制import等包,所以行號(hào)少于我自己運(yùn)行的代碼)時(shí)候拋出異常,說(shuō)是不能夠反射,我們看下Constructor類的newInstance方法源碼:
@CallerSensitivepublic T newInstance(Object ... initargs)throws InstantiationException, IllegalAccessException,IllegalArgumentException, InvocationTargetException{if (!override) {if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {Class<?> caller = Reflection.getCallerClass();checkAccess(caller, clazz, null, modifiers);}}if ((clazz.getModifiers() & Modifier.ENUM) != 0)throw new IllegalArgumentException("Cannot reflectively create enum objects");ConstructorAccessor ca = constructorAccessor; // read volatileif (ca == null) {ca = acquireConstructorAccessor();}@SuppressWarnings("unchecked")T inst = (T) ca.newInstance(initargs);return inst;}請(qǐng)看的第12行源碼,說(shuō)明反射在通過(guò)newInstance創(chuàng)建對(duì)象時(shí),會(huì)檢查該類是否ENUM修飾,如果是則拋出異常,反射失敗。
3.3.3避免序列化問(wèn)題
我按照3.2中方式來(lái)寫,作為對(duì)比,方面大家看的更清晰些:
public enum SerEnumSingleton implements Serializable {INSTANCE;private String content;public String getContent() {return content;}public void setContent(String content) {this.content = content;}private SerEnumSingleton() {}public static void main(String[] args) throws IOException, ClassNotFoundException {SerEnumSingleton s = SerEnumSingleton.INSTANCE;s.setContent("枚舉單例序列化");System.out.println("枚舉序列化前讀取其中的內(nèi)容:"+s.getContent());ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("SerEnumSingleton.obj"));oos.writeObject(s);oos.flush();oos.close();FileInputStream fis = new FileInputStream("SerEnumSingleton.obj");ObjectInputStream ois = new ObjectInputStream(fis);SerEnumSingleton s1 = (SerEnumSingleton)ois.readObject();ois.close();System.out.println(s+"\n"+s1);System.out.println("枚舉序列化后讀取其中的內(nèi)容:"+s1.getContent());System.out.println("枚舉序列化前后兩個(gè)是否同一個(gè):"+(s==s1));} }運(yùn)行結(jié)果如下:
枚舉序列化前讀取其中的內(nèi)容:枚舉單例序列化 INSTANCE INSTANCE 枚舉序列化后讀取其中的內(nèi)容:枚舉單例序列化 枚舉序列化前后兩個(gè)是否同一個(gè):true??枚舉類是JDK1.5才出現(xiàn)的,那之前的程序員面對(duì)反射攻擊和序列化問(wèn)題是怎么解決的呢?其實(shí)就是像Enum源碼那樣解決的,只是現(xiàn)在可以用enum可以使我們代碼量變的極其簡(jiǎn)潔了。至此,相信同學(xué)們應(yīng)該能明白了為什么Joshua Bloch說(shuō)的“單元素的枚舉類型已經(jīng)成為實(shí)現(xiàn)Singleton的最佳方法”了吧,也算解決了我自己的困惑。既然能解決這些問(wèn)題,還能使代碼量變的極其簡(jiǎn)潔,那我們就有理由選枚舉單例模式了。對(duì)了,解決序列化問(wèn)題,要先懂transient和readObject,鑒于我的主要目的不在于此,就不在此寫這兩個(gè)原理了。
總結(jié)
以上是生活随笔為你收集整理的Java设计模式——为什么要用枚举实现单例模式(避免反射、序列化问题)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Java设计模式——单例模式的七种写法
- 下一篇: Java面试宝典系列之基础面试题-常见的