驀然回頭-單例模式篇章三
阿新 • • 發佈:2018-12-21
單例模式引發相關整理
如何破壞單例模式
示例:
/**
* 如果破壞單例模式
*
* @author sunyang
* @date 2018/11/13 20:14
*/
public class Singleton7 {
private Singleton7(){
System.out.println("Singleton7");
}
private static final class SingletonHolder{
SingletonHolder(){
System.out.println("SingletonHolder" );
}
private static final Singleton7 INSTANCE = new Singleton7();
}
public static Singleton7 getInstance(){
return SingletonHolder.INSTANCE;
}
}
測試得出結果
/**
* @author sunyang
* @date 2018/11/13 20:18
*/
public class Test {
/**
* ----------------------start-------------
*Singleton7
* -----------------------end---------------
*
*/
public static void main(String[] args) {
System.out.println("----------------------start-------------");
Singleton7.getInstance();
System.out.println("-----------------------end---------------");
}
}
上圖的單例,最主要的一步是將構造方法私有化,從而外界無法new物件。但是java的反射可以強制訪問private修飾的變數,方法和建構函式,如圖
/**
* @author sunyang
* @date 2018/11/13 20:31
*/
public class ReflectTest {
/**
* Singleton7
* Singleton7
* 反射獲取構造器和直接獲取構造器,比較是否同一個:false
*/
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class<Singleton7> singleton7Class = Singleton7.class;
Constructor<Singleton7> constructor = singleton7Class.getDeclaredConstructor();
constructor.setAccessible(true);
//反射獲取建構函式
Singleton7 singleton1 = constructor.newInstance();
Singleton7 singleton2 = Singleton7.getInstance();
System.out.println("反射獲取構造器和直接獲取構造器,比較是否同一個:" + (singleton1 == singleton2));
}
}
聯想
java中的四種建立物件的方式
type | explan |
---|---|
new | 需要呼叫建構函式 |
reflect | 需要呼叫建構函式,免疫一切訪問許可權的限制public,private等 |
clone | 需要實現Cloneable介面,分淺複製,深層 |
Serializable | 1.將物件儲存在硬碟中 2.通過網路傳輸物件,需要實現Serializable |
單例模式,是不能抵抗反射,clone,序列化的破壞的。
如何保護單例模式
思路:
對於clone和序列化,可以在設計的過程中不直接或間接的去實現Cloneable和Serializable介面即可。
對於反射,可以通過在呼叫第二次建構函式的方式進行避免。
嘗試解決程式碼:
/**
* 保護單例模式測試demo
*
* @author sunyang
* @date 2018/11/14 10:32
*/
public class ReflectTestProtect {
public static void main(String[] args) {
try {
Class<Singleton7Protect> clazz = Singleton7Protect.class;
Constructor<Singleton7Protect> constructor = clazz.getDeclaredConstructor(null);
constructor.setAccessible(true);
Singleton7Protect singleton1 = constructor.newInstance();
Singleton7Protect singleton2 = Singleton7Protect.getInstance();
System.out.println("保護單例模式:"+ (singleton1 == singleton2));
}catch (Exception e){
e.printStackTrace();
}
}
}
結果圖
推薦新寫法
jdk1.5
以後,實現單例新寫法,只需要編寫一個包含單個元素的列舉型別。
分析
列舉類能夠實現介面,但不能繼承類,列舉類使用enum定義後再編譯時就預設繼承了java.lang.Enum類,而不是普通的繼承Object類。
列舉類會預設實現Serializable和Comparable兩個介面,且採用enum聲明後,該類會被編譯器加上final宣告,故該類是無法繼承的。列舉類的內部定義的列舉值就是該類的例項。除此之外,列舉類和普通類一致。因此可以利用列舉類來實現一個單例模式。直觀圖如下:
推薦寫法程式碼:
/**
* 新寫法:列舉類來實現一個單例,
* 來測試破壞,是否會成功?
*
* @author sunyang
* @date 2018/11/14 11:22
*/
public enum Singleton8 {
INSTANCE;
public static Singleton8 getInstance(){
return INSTANCE;
}
}
測試是否反射破壞程式碼:
/**
* 列舉測試反射是否能破壞單例
* 結論:可以防止單例模式被侵犯
* @author sunyang
* @date 2018/11/14 11:26
*/
public class EnumTest {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class<Singleton8> clazz = Singleton8.class;
Constructor<Singleton8> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
constructor.newInstance();
}
}
結果圖:
結論:單元素的列舉型別已經成為實現單例模式的最佳方法。
原始碼分析
java.lang.reflect.Constructor#newInstance
@CallerSensitive
public 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);
}
}
//反射再通過newInstance建立物件時,會檢查該類是否ENUM修飾,如果是則丟擲異常,反射失敗。
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}
擴充套件:測試對列舉類進行序列化操作程式碼:
/**
* 測試列舉類是否能防止序列化破壞單例模式
* 結果:可以防止序列化破壞單例模式
*
* @author sunyang
* @date 2018/11/14 11:52
*/
public class SerializableTest {
public static void main(String[] args) throws Exception{
File objectFile = new File("Singleton8.javaObject");
Singleton8 instance1 = Singleton8.INSTANCE;
Singleton8 instance2 = null;
ObjectOutputStream oos = null;
ObjectInputStream ois = null;
try {
//序列化到本地
oos = new ObjectOutputStream(new FileOutputStream(objectFile));
oos.writeObject(instance1);
oos.flush();
//反序列化到記憶體
ois = new ObjectInputStream(new FileInputStream(objectFile));
instance2= (Singleton8) ois.readObject();
}catch (Exception e){
}finally {
oos.close();
ois.close();
}
//true,說明兩者引用著同一個物件
System.out.println(Objects.equals(instance1, instance2));
}
}
結論圖