概述:
- 之前學(xué)習(xí)了單例模式的幾種實(shí)現(xiàn)拢驾,解決了多線(xiàn)程情況下繁疤,單例的線(xiàn)程安全問(wèn)題稠腊,保證了單例的實(shí)現(xiàn)架忌。但是單例模式在下面兩種情況下也會(huì)被破壞:反射叹放、序列化许昨。
反射:
- 通過(guò)反射是可以破壞單例的糕档,例如使用內(nèi)部類(lèi)實(shí)現(xiàn)的單例拌喉。通過(guò)反射獲取其默認(rèn)的構(gòu)造函數(shù)尿背,然后使默認(rèn)構(gòu)造函數(shù)可訪(fǎng)問(wèn)田藐,就可以創(chuàng)建新的對(duì)象了。如下面代碼:
public class MainClass {
public static void main(String[] args) {
PersonLazyInnerClass person1 = PersonLazyInnerClass.getPersonLazyInnerClass();
PersonLazyInnerClass person2 = null;
try {
Class<PersonLazyInnerClass> cla = PersonLazyInnerClass.class;
//獲得默認(rèn)構(gòu)造函數(shù)
Constructor<PersonLazyInnerClass> cons = cla.getDeclaredConstructor();
//使默認(rèn)構(gòu)造函數(shù)可訪(fǎng)問(wèn)
cons.setAccessible(true);
//創(chuàng)建對(duì)象
person2 = cons.newInstance();
}catch (Exception e){
e.printStackTrace();
}
System.out.println(person1.hashCode());
System.out.println(person2.hashCode());
}
}
-
測(cè)試結(jié)果
image.png 所以想要阻止破壞單例,思路是阻止外部能調(diào)用類(lèi)的構(gòu)造函數(shù)一次以上吝岭。所以可以增加一個(gè)標(biāo)志位窜管,用于判斷構(gòu)造函數(shù)是否被調(diào)用過(guò)幕帆。如下代碼:
public class PersonLazyInnerClassSafe {
//申明一個(gè)標(biāo)志位蜓肆,用于標(biāo)志構(gòu)造函數(shù)是否被調(diào)用過(guò)
private static Boolean alreadyNew = false;
private PersonLazyInnerClassSafe(){
//加鎖防止并發(fā)
synchronized (PersonLazyInnerClassSafe.class){
//第一次被調(diào)用仗扬,僅修改標(biāo)志位早芭;后續(xù)被調(diào)用拋異常
if(alreadyNew == false){
alreadyNew = true;
}else {
throw new RuntimeException("單例模式被破壞退个!");
}
}
}
private static class Holder{
private static PersonLazyInnerClassSafe personLazyInnerClassSafe = new PersonLazyInnerClassSafe();
}
public static PersonLazyInnerClassSafe getInstance(){
return Holder.personLazyInnerClassSafe;
}
}
-
測(cè)試結(jié)果
image.png - 增加標(biāo)志位的確能阻止單例的破壞,但是這個(gè)代碼有一個(gè)BUG刀荒,那就是如果單例是先用的反射創(chuàng)建的缠借,那如果你再用正常的方法getInstance()獲取單例泼返,就會(huì)報(bào)錯(cuò)绅喉。因?yàn)榇藭r(shí)標(biāo)志位已經(jīng)標(biāo)志構(gòu)造函數(shù)被調(diào)用過(guò)了霹疫。這種寫(xiě)法除非你能保證getInstance先于反射執(zhí)行丽蝎。
- 先試正常執(zhí)行代碼屠阻,此時(shí)是可以正確返回單例国觉。這里簡(jiǎn)單解釋一下麻诀,person2執(zhí)行的時(shí)候不會(huì)去調(diào)用PersonLazyInnerClassSafe類(lèi)的構(gòu)造函數(shù)蝇闭。因?yàn)镠older內(nèi)部類(lèi)里面personLazyInnerClassSafe屬性是靜態(tài)的呻引,靜態(tài)屬性在類(lèi)加載的時(shí)候就初始化一次逻悠,在生命周期內(nèi)就不會(huì)再被初始化了童谒。
public class MainClass {
public static void main(String[] args) {
//正常執(zhí)行代碼
PersonLazyInnerClassSafe person1 = PersonLazyInnerClassSafe.getInstance();
PersonLazyInnerClassSafe person2 = PersonLazyInnerClassSafe.getInstance();
System.out.println(person1.hashCode());
System.out.println(person2.hashCode());
}
}
- 先反射后正常調(diào)用饥伊,此時(shí)會(huì)發(fā)現(xiàn)正常調(diào)用沒(méi)法用了撵渡。
public class MainClass {
public static void main(String[] args) {
//先反射獲取單例
PersonLazyInnerClassSafe person2 = null;
try {
Class<PersonLazyInnerClassSafe> cla = PersonLazyInnerClassSafe.class;
//獲得默認(rèn)構(gòu)造函數(shù)
Constructor<PersonLazyInnerClassSafe> cons = cla.getDeclaredConstructor();
//使默認(rèn)構(gòu)造函數(shù)可訪(fǎng)問(wèn)
cons.setAccessible(true);
//調(diào)用默認(rèn)構(gòu)造函數(shù),創(chuàng)建對(duì)象
person2 = cons.newInstance();
}catch (Exception e){
e.printStackTrace();
}
System.out.println("反射對(duì)象: " + person2.hashCode());
//在用正常的方法獲取單例节腐,此時(shí)會(huì)報(bào)錯(cuò)
PersonLazyInnerClassSafe person1 = PersonLazyInnerClassSafe.getInstance();
System.out.println("正常獲取" + person1.hashCode());
}
}
-
測(cè)試結(jié)果
反序列化:
1. 破壞單例
- 反序列化也是一種會(huì)破壞單例的方法饱苟。簡(jiǎn)單來(lái)講箱熬,反序列化也是通過(guò)反射調(diào)用newInstance()實(shí)例化對(duì)象城须,下面我一步步解釋糕伐。例如下面代碼可以通過(guò)反序列化破壞單例:
public class MainClass {
public static void main(String[] args) {
//序列化
PersonLazyInnerClassSafe person3 = PersonLazyInnerClassSafe.getInstance();
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("tempfile"));
out.writeObject(PersonLazyInnerClassSafe.getInstance());
File file = new File("tempfile");
ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
//調(diào)用readObject()反序列化
PersonLazyInnerClassSafe person4 = (PersonLazyInnerClassSafe)in.readObject();
System.out.println("正常構(gòu)造:" + person3.hashCode());
System.out.println("反序列化Person:" + person4.hashCode());
}
}
-
測(cè)試結(jié)果
可以看到良瞧,反序列化后褥蚯,生成了一個(gè)新的實(shí)例遵岩。
image.png - 原理解釋
反序列化為什么能生成新的實(shí)例,必須從源碼看起誊锭。這里分析readObject()里面的調(diào)用源碼丧靡。會(huì)發(fā)現(xiàn)readObject()方法后進(jìn)入了readObject0(false)方法温治。
public final Object readObject()
throws IOException, ClassNotFoundException
{
if (enableOverride) {
return readObjectOverride();
}
// if nested read, passHandle contains handle of enclosing object
int outerHandle = passHandle;
try {
Object obj = readObject0(false); //通過(guò)debug會(huì)發(fā)現(xiàn)進(jìn)入此方法
handles.markDependency(outerHandle, passHandle);
ClassNotFoundException ex = handles.lookupException(passHandle);
if (ex != null) {
throw ex;
}
if (depth == 0) {
vlist.doCallbacks();
}
return obj;
} finally {
passHandle = outerHandle;
if (closed && depth == 0) {
clear();
}
}
}
分析readObject0方法,會(huì)發(fā)現(xiàn)進(jìn)入了readOrdinaryObject()方法卤恳。
private Object readObject0(boolean unshared) throws IOException {
boolean oldMode = bin.getBlockDataMode();
if (oldMode) {
int remain = bin.currentBlockRemaining();
if (remain > 0) {
throw new OptionalDataException(remain);
} else if (defaultDataEnd) {
/*
* Fix for 4360508: stream is currently at the end of a field
* value block written via default serialization; since there
* is no terminating TC_ENDBLOCKDATA tag, simulate
* end-of-custom-data behavior explicitly.
*/
throw new OptionalDataException(true);
}
bin.setBlockDataMode(false);
}
byte tc;
while ((tc = bin.peekByte()) == TC_RESET) {
bin.readByte();
handleReset();
}
depth++;
try {
switch (tc) {
...省略部分源碼
case TC_OBJECT:
return checkResolve(readOrdinaryObject(unshared)); //會(huì)進(jìn)入該邏輯
case TC_EXCEPTION:
IOException ex = readFatalException();
throw new WriteAbortedException("writing aborted", ex);
case TC_BLOCKDATA:
case TC_BLOCKDATALONG:
if (oldMode) {
bin.setBlockDataMode(true);
bin.peek(); // force header read
throw new OptionalDataException(
bin.currentBlockRemaining());
} else {
throw new StreamCorruptedException(
"unexpected block data");
}
case TC_ENDBLOCKDATA:
if (oldMode) {
throw new OptionalDataException(true);
} else {
throw new StreamCorruptedException(
"unexpected end of block data");
}
default:
throw new StreamCorruptedException(
String.format("invalid type code: %02X", tc));
}
} finally {
depth--;
bin.setBlockDataMode(oldMode);
}
}
- 通過(guò)分析若债,readOrdinaryObject()中有兩處關(guān)鍵代碼蠢琳,其中關(guān)鍵代碼1中的關(guān)鍵語(yǔ)句為:
obj = desc.isInstantiable() ? desc.newInstance() : null;
- 此處代碼是通過(guò)描述對(duì)象desc挪凑,先判斷類(lèi)是否可以實(shí)例化,如果可以實(shí)例化散怖,則執(zhí)行desc.newInstance()通過(guò)反射實(shí)例化類(lèi)镇眷,否則返回null欠动。
private Object readOrdinaryObject(boolean unshared)
throws IOException
{
if (bin.readByte() != TC_OBJECT) {
throw new InternalError();
}
ObjectStreamClass desc = readClassDesc(false); //根據(jù)對(duì)象的格式,下一步是讀取類(lèi)描述信息
desc.checkDeserialize();
Class<?> cl = desc.forClass();
if (cl == String.class || cl == Class.class //讀取Class
|| cl == ObjectStreamClass.class) {
throw new InvalidClassException("invalid class descriptor");
}
/**===============關(guān)鍵代碼1====================== **/
Object obj;
try {
obj = desc.isInstantiable() ? desc.newInstance() : null; //通過(guò)類(lèi)描述信息惑申,初始化對(duì)象obj具伍。
} catch (Exception ex) {
throw (IOException) new InvalidClassException(
desc.forClass().getName(),
"unable to create instance").initCause(ex);
}
/**===============關(guān)鍵代碼1====================== **/
passHandle = handles.assign(unshared ? unsharedMarker : obj);
ClassNotFoundException resolveEx = desc.getResolveException();
if (resolveEx != null) {
handles.markException(passHandle, resolveEx);
}
if (desc.isExternalizable()) {
readExternalData((Externalizable) obj, desc);
} else {
readSerialData(obj, desc);
}
handles.finish(passHandle);
/**===============關(guān)鍵代碼2====================== **/
if (obj != null &&
handles.lookupException(passHandle) == null &&
desc.hasReadResolveMethod())
{
Object rep = desc.invokeReadResolve(obj);
if (unshared && rep.getClass().isArray()) {
rep = cloneArray(rep);
}
if (rep != obj) {
handles.setObject(passHandle, obj = rep);
}
}
/**===============關(guān)鍵代碼2====================== **/
return obj;
}
- 此處通過(guò)debug可以發(fā)現(xiàn),類(lèi)是可實(shí)例化的圈驼,所以執(zhí)行desc.newInstance() 去實(shí)例化對(duì)象人芽,此處就是重點(diǎn),讓我疑問(wèn)的地方了萤厅。我們知道newInstance必定會(huì)調(diào)用默認(rèn)的無(wú)參構(gòu)造函數(shù)。但是我們?cè)谏厦?em>PersonLazyInnerClassSafe單例類(lèi)已經(jīng)禁止了反射創(chuàng)建新實(shí)例靴迫,但是反序列化還是能創(chuàng)建出新實(shí)例惕味,那么此處的反序列化中的反射具體是如何執(zhí)行的呢?繼續(xù)分析newInstance()源碼玉锌。
- newInstance()的源碼如下所示名挥,可以發(fā)現(xiàn),程序會(huì)自動(dòng)加載合適的構(gòu)造函數(shù)cons芬沉,然后再去根據(jù)這個(gè)構(gòu)造函數(shù)去cons.newInstance()創(chuàng)建對(duì)象躺同。通過(guò)debug發(fā)現(xiàn)阁猜,此處的cons對(duì)象是Object,真像大白了蹋艺。所以反序列化是通過(guò)Object的構(gòu)造函數(shù)去反射剃袍,生成了新的實(shí)例。
/** serialization-appropriate constructor, or null if none */
private Constructor<?> cons;
...省略部分源碼
Object newInstance()
throws InstantiationException, InvocationTargetException,
UnsupportedOperationException
{
requireInitialized();
if (cons != null) {
try {
return cons.newInstance();
} catch (IllegalAccessException ex) {
// should not occur, as access checks have been suppressed
throw new InternalError(ex);
}
} else {
throw new UnsupportedOperationException();
}
}
-
此處可以給大家看下debug 的截圖
image.png - 然后我改寫(xiě)了上面的測(cè)試序列化破壞單例的代碼捎谨,驗(yàn)證一下反序列化是通過(guò)Object的構(gòu)造函數(shù)去反射民效,生成了新的實(shí)例,具體代碼如下
public class MainClass {
public static void main(String[] args) {
//驗(yàn)證一下反序列化是通過(guò)Object的構(gòu)造函數(shù)去反射涛救,生成了新的實(shí)例
PersonLazyInnerClassSafe person3 = PersonLazyInnerClassSafe.getInstance();
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("tempfile"));
out.writeObject(PersonLazyInnerClassSafe.getInstance());
File file = new File("tempfile");
ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
//反序列化生成的Object對(duì)象
Object ob = in.readObject();
//將Object強(qiáng)轉(zhuǎn)為PersonLazyInnerClassSafe
PersonLazyInnerClassSafe person4 = (PersonLazyInnerClassSafe)ob;
System.out.println("正常構(gòu)造:" + person3.hashCode());
System.out.println("反序列化Object:" + ob.hashCode());
System.out.println("反序列化Person:" + person4.hashCode());
}
}
- 其中會(huì)發(fā)現(xiàn)反序列化出來(lái)的Object對(duì)象強(qiáng)制轉(zhuǎn)換為PersonLazyInnerClassSafe時(shí)畏邢,只是把ob 對(duì)象的引用指向了person4 ,并沒(méi)有調(diào)用PersonLazyInnerClassSafe 的構(gòu)造函數(shù)检吆。
//反序列化生成的Object對(duì)象
Object ob = in.readObject();
//將Object強(qiáng)轉(zhuǎn)為PersonLazyInnerClassSafe
PersonLazyInnerClassSafe person4 = (PersonLazyInnerClassSafe)ob;
-
運(yùn)行結(jié)果如下舒萎,可以看到ob和person4 是相同的對(duì)象:
image.png
2. 阻止破壞單例
- 上面解釋了那么多序列化是如何通過(guò)反射破壞的單例,那么如何阻止單例被序列化破壞呢蹭沛?關(guān)鍵點(diǎn)在上面readOrdinaryObject()方法源碼的關(guān)鍵代碼2中臂寝,下面單獨(dú)截取該段代碼:
if (obj != null &&
handles.lookupException(passHandle) == null &&
desc.hasReadResolveMethod()) //判斷對(duì)象是否實(shí)現(xiàn)readResolve方法
{
Object rep = desc.invokeReadResolve(obj); //反射調(diào)用對(duì)象的readResolve方法
if (unshared && rep.getClass().isArray()) {
rep = cloneArray(rep);
}
if (rep != obj) { //如果對(duì)象readResolve返回的對(duì)象與 默認(rèn)序列化對(duì)象不等,返回readResolve方法返回的對(duì)象
handles.setObject(passHandle, obj = rep);
}
}
- 通過(guò)閱讀上面代碼的注釋?zhuān)覀兗纯芍捞穑恍枰獙?shí)現(xiàn)我們自己的readResolve()方法咆贬,在這個(gè)方法中返回我們想要的單例,就可以解決序列化破壞單例帚呼。具體改寫(xiě)如下:
public class PersonLazyInnerClassSafe implements Serializable{
//申明一個(gè)標(biāo)志位掏缎,用于標(biāo)志構(gòu)造函數(shù)是否被調(diào)用過(guò)
public static Boolean alreadyNew = false;
private PersonLazyInnerClassSafe(){
System.out.println("調(diào)用構(gòu)造函數(shù)!C荷薄眷蜈!");
//加鎖防止并發(fā)
synchronized (PersonLazyInnerClassSafe.class){
//第一次被調(diào)用,僅修改標(biāo)志位怜珍;后續(xù)被調(diào)用拋異常
if(alreadyNew == false){
alreadyNew = true;
}else {
throw new RuntimeException("單例模式被破壞端蛆!");
}
}
}
private static class Holder{
private static PersonLazyInnerClassSafe personLazyInnerClassSafe = new PersonLazyInnerClassSafe();
}
public static PersonLazyInnerClassSafe getInstance(){
return Holder.personLazyInnerClassSafe;
}
//阻止序列化破壞單例
private Object readResolve(){
return Holder.personLazyInnerClassSafe;
}
}
-
測(cè)試如下,成功解決單例被破壞問(wèn)題
image.png
總結(jié):
- 破壞單例有兩種方式 反射酥泛、反序列化
- 反射破壞的原理是:通過(guò)反射獲取其默認(rèn)的構(gòu)造函數(shù)今豆,并且改變其構(gòu)造函數(shù)的訪(fǎng)問(wèn)域,從而實(shí)現(xiàn)調(diào)用構(gòu)造函數(shù)創(chuàng)建新實(shí)例柔袁。解決方案是:在構(gòu)造函數(shù)中增加一個(gè)標(biāo)志位呆躲,用于判斷構(gòu)造函數(shù)是否被調(diào)用過(guò),阻止外部能調(diào)用類(lèi)的構(gòu)造函數(shù)一次以上捶索。
- 反序列化破壞構(gòu)造函數(shù)的原理:通過(guò)Object的構(gòu)造函數(shù)插掂,反射出單例類(lèi)對(duì)象,從而創(chuàng)建了新的實(shí)例。解決方案是:在單例類(lèi)中寫(xiě)一個(gè)readResolve()方法辅甥,在這個(gè)方法中返回我們想要的單例酝润,就可以解決序列化破壞單例。