1. 程式人生 > >Spring IOC(一)單例、非單例+容器關閉了,getBean獲取的物件(小老弟)你咋還在蹦躂? day--07

Spring IOC(一)單例、非單例+容器關閉了,getBean獲取的物件(小老弟)你咋還在蹦躂? day--07

Spring IOC(一)單例、非單例+容器關閉了,getBean獲取的物件(小老弟)你咋還在蹦躂? day–07

一路綠燈學過去的就不記了,只記錄重點和實驗過程,另外內容順序排列可能引起不適,但是是根我碰到問題順序走的,,,在這向有一丁點可能看到這篇文章的您抱歉。

一、.Spring IOC 獲取物件及Spring對Bean的生命週期控制(部分)

1.單利模式和非單例模式

1)單利模式下getBean獲取物件以及物件建立時間

測試getBean獲取的物件
預設單例模式
建立一個pojo (衣服類)包含顏色和大小 新增無參建構函式,列印記憶體地址

public
class Clothes { public Clothes() { System.out.println("一件衣服被建立了"+System.identityHashCode(this)); } private String color; private int size; public String getColor() { return color; } public void setColor(String color) { this.color = color;
} public int getSize() { return size; } public void setSize(int size) { this.size = size; } public void destory(){ //System.identityHashCode(Object)方法可以返回物件的記憶體地址 System.out.println("這件衣服被銷燬了"+System.identityHashCode(this)); } }

resources目錄下新增 applicationContext.xml (銷燬時呼叫destory方法)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean class="my.test.Clothes" id="clothes"   destroy-method="destory" >
        <property name="color" value="red"/>
        <property name="size" value="33"/>
    </bean>
</beans>

測試類

public class SpringTest {
    public static void main(String args[])
    {
        ApplicationContext applicationContext =new ClassPathXmlApplicationContext(new String[]{"applicationContext.xml"});
        Clothes clothes1 = (Clothes)applicationContext.getBean("clothes");
        System.out.println(clothes1.getColor()+clothes1.getSize());
        Clothes clothes2 = (Clothes)applicationContext.getBean("clothes");
        System.out.println(clothes2.getColor()+clothes2.getSize());
        ((ClassPathXmlApplicationContext) applicationContext).close();

    }

}

列印結果

一件衣服被建立了1210830415
red33
red33
這件衣服被銷燬了1210830415

程序完成,退出碼 0

設定clothes1 的顏色為 Green

System.out.println(clothes.getColor()+clothes.getSize());
 clothes1.setColor("Green");

列印結果

一件衣服被建立了282265585
red33
Green33
這件衣服被銷燬了282265585

 發現修改clothes1 的屬性 再次獲取 clothes2 並不會因為clothes1 做出修改而給出新例項。
測試結果:scope 設定為 singleton 一個容器中 只要獲取這個例項返回的都是同一個物件


測試物件是在getBean時候建立還是容器初始化時建立
修改上述java程式碼,在容器建立後列印一行欄位

ApplicationContext applicationContext =new ClassPathXmlApplicationContext(new String[]{"applicationContext.xml"});
        System.out.println("容器被建立完成");

輸出結果

一件衣服被建立了282265585
容器被建立完成
red33
Green33
這件衣服被銷燬了282265585

預設情況下lazy-init 為false 容器被建立後自動載入Bean並例項化。
設定lazy-int 為true

 <bean class="my.test.Clothes" id="clothes"  lazy-init="true"   destroy-method="destory" >

執行結果

容器被建立完成
一件衣服被建立了1740846921
red33
Green33
這件衣服被銷燬了1740846921

思考:
 單例模式下,一個bean對應一個物件,可以對每一個物件進行例項化,那麼非單利模式下呢 lazy-init 屬性是否還會生效呢? 在下一步進行測試。

 單例模式下生命週期,在非單例模式下(2)中有很多描述,在那裡碰到的問題引發的思考 往那插眼 忘記了就tp過去

2)非單利模式下getBean獲取物件以及物件建立時間

當scope 設定成 propert 每次getBean Spring都會new一個Bean例項,不會負責Bean例項生命週期的維護
設定scope="prototype"

<bean class="my.test.Clothes" id="clothes" scope="prototype" destroy-method="destory" >
        <property name="color" value="red"/>
        <property name="size" value="33"/>
    </bean>

測試程式碼(回到最初的起點)

 ApplicationContext applicationContext =new ClassPathXmlApplicationContext(new String[]{"applicationContext.xml"});
        Clothes clothes1 = (Clothes)applicationContext.getBean("clothes");
        System.out.println(clothes1.getColor()+clothes1.getSize());
        clothes1.setColor("Green");
        Clothes clothes2 = (Clothes)applicationContext.getBean("clothes");
        System.out.println(clothes2.getColor()+clothes2.getSize());
        ((ClassPathXmlApplicationContext) applicationContext).close();

結果

一件衣服被建立了1833848849
red33
一件衣服被建立了252480153
red33

這裡命名設定了destroy-method=“destory”,為什麼容器關閉物件卻沒有銷燬呢?物件還在嗎?
在容器關閉以後加入對clothes1的列印

        ((ClassPathXmlApplicationContext) applicationContext).close();
        System.out.println("我是clothes1 我還活著哦,看我的記憶體地址:"+System.identityHashCode(clothes1));

列印結果

一件衣服被建立了1833848849
red33
一件衣服被建立了252480153
red33
我是clothes1 我還活著哦,看我的記憶體地址:1833848849

非單例模式容器關閉並沒有呼叫destory-method指定的方法,多例模式下Spring不負責銷燬(管理Bean例項生命週期),所以沒有呼叫destroy方法,Spring 容器關閉了可是卻沒有銷燬物件clothes1 這個貨仍然在記憶體中。
destory-method指定Bean銷燬前所做的操作

看到這想到單例模式了,單例情況下呢,測試結果如下

一件衣服被建立了282265585
red33
Green33
這件衣服被銷燬了282265585
我是clothes1 我還活著哦,看我的記憶體地址:282265585

程序完成,退出碼 0

這裡可以看到 destory-method指定的方法已經被執行了,那麼下面的物件為什麼沒有被銷燬呢?
 這裡可能是理解上出了問題,好多資料上說,單例模式下Spring負責管理Bean例項的生命週期,這種說話方式讓我這種對Spring理解不深的人陷入那無盡的深淵,比如這樣一段話:對於singleton作用域的Bean,客戶端的每次請求都返回同一個Bean例項,客戶端程式碼不能控制Bean的銷燬,它的生命週期都在Spring的掌握之中。這麼一來,容器就可以管理例項化結束後(某些資源的申請)和銷燬之前(進行某些資源的回收)的行為。
這裡很關鍵的一句客戶端程式碼不能控制Bean的銷燬下面測試

 ApplicationContext applicationContext =new ClassPathXmlApplicationContext(new String[]{"applicationContext.xml"});
        Clothes clothes1 = (Clothes)applicationContext.getBean("clothes");
        System.out.println(clothes1.getColor()+clothes1.getSize());
        clothes1.setColor("Green");
        //設定clothes1 為null
        clothes1=null;
        Clothes clothes2 = (Clothes)applicationContext.getBean("clothes");
        System.out.println(clothes2.getColor()+clothes2.getSize());
//        ((ClassPathXmlApplicationContext) applicationContext).destroy();
        ((AbstractApplicationContext)applicationContext).registerShutdownHook();
        ((ClassPathXmlApplicationContext) applicationContext).close();

        System.out.println("我是clothes1 我還活著哦,看我的記憶體地址:"+System.identityHashCode(clothes1));

 這裡設定clothes1為null,(客戶端的每次請求都返回同一個Bean例項) 也就是clothes1是 Bean的唯一例項化物件,將clothes1設定成null 這個唯一的bean例項化物件就變成了null?? 上面也說(客戶端程式碼不能控制Bean的銷燬,它的生命週期都在Spring的掌握之中),是不是將clothes1設定成null,這個單例Bean並沒有變為null呢 看結果:
測試結果

一件衣服被建立了282265585
red33
Green33
這件衣服被銷燬了282265585
我是clothes1 我還活著哦,看我的記憶體地址:0

 結果可以看出clothes1 確實被設定成null,clothes2再次獲取這個Bean 依然可以獲取到,這是為什麼呢? 其實上面的話很容易誤導新人,我這樣的。稍微思考下 getBean返回的是這個Bean的Java例項的一個引用地址? 這麼正確理解上面那句話呢?(也不一定正確我是這麼理解的)

參考

        //Spring容器建立時來建立c1 
        Clothes c1=new Clothes();

        //getBean時候給c2 賦予c1指向的地址  (堆疊思考)
        Clothes c2=c1;
        // c2 指向空
        c2=null; 
        // c3 和 c1 指向同一塊記憶體地址
        Clothes c3=c1;
        //c2指向 為null
        System.out.println(c2);
        //c2 和c1 指向相同
        System.out.println(c3);

        

總結:
c1單例 被 Spring 來管理,客戶端無法操作c1 但能通過引用來修改其內部的資訊。
再進一步思考:假如設定lazy-init=“true” 是不是相當於 c2 = new Clothes(); 再執行c2 = null; 是不是到時候 就 沒有任何物件指向這個 例項的Bean(new Clothes()) 也就是這個單例Bean 就會丟失呢 ?
 答案是不會的,即使設定成lazy-init=“true” getBean 時例項化這個bean,也是先在容器內建立一個物件再給你而不是容器本身不包含這物件引用(也就是還是會走 先建立c1 給Spring管理),直接給你(c2)。有點繞口不過很容易理解了吧。

記住上面這個例子,下面馬上用奧?

下面是從一篇對Bean理解很有幫助的文章中擷取部分欄位
原文地址

1)管理Bean的生命週期行為主要有兩個時機:注入依賴關係後,銷燬例項之前
2)Spring 的 Bean 和 JavaBean比較:
 規範:Spring容器對Bean 沒有特殊要求,不像JavaBean 一樣遵循一些規範(為每個屬性提供相應的setter 和 getter 方法),不過對於設值注入的Bean,一定要提供setter 方法。
 作用:Spring 中的Bean 是 java 例項,java元件,它的作用幾乎無所不包,任何應用元件都被稱為Bean,而傳統的Java應用中的JavaBean通常作為DTO(資料傳輸物件),來封裝值物件,在各層之間傳遞資料。
 傳統的JavaBean作為值物件傳遞,不接受任何容器管理其生命週期,Spring中的Bean有Spring管理其生命週期行為。

Spring中的Bean由Spring管理其生命週期行為+Spring 中的Bean 是 java 例項
  這兩句加起來很容易讓人聯想到java例項化物件的建立和銷燬,都是由Spring來完成的,那麼為什麼還會看到容器已經關閉,getBean獲取的資料還在蹦躂。
 兩個知識點:
 1)關閉容器只會呼叫設定的destroy方法。
 2)物件從記憶體中清除是GC來完成的,保留這個物件的引用,怎麼可能會被銷燬!!!
原諒我會思考這個問題這麼久:哈哈哈
上面示例拷過來

       //Spring容器建立時來建立c1 
       Clothes c1=new Clothes();

       //getBean時候給c2 賦予c1指向的地址  (堆疊思考)
       Clothes c2=c1;
       // c2 指向空
       c2=null; 
       // c3 和 c1 指向同一塊記憶體地址
       Clothes c3=c1;
       //c2指向 為null
       System.out.println(c2);
       //Spring 容器關閉
       c1=null;
       //c2 和c1 指向相同
       System.out.println(c3);

 跟上面一樣的比方:Spring容器建立 建立c1 c1有指向物件 ,下面c3也指向c1的指向物件(這時候c3 c1共用同一個物件,有點邪惡) Sprng 容器關閉(c1) 這時候我們怎麼找到那個物件呢? 當然可以用c3來找啊,當然,如果這個物件沒人要,這個物件就將會被GC銷燬(引發思考:單身狗這麼死亡的嗎???)