首先,我們要理解什么叫Bean的作用域。我們都知道變量的作用域,即變量起作用的區域。類比可知,spring的Bean的作用域就是實例起作用的區域。
spring的Bean的作用域包括單例(singleton)、原型(prototype)、request、session。
singleton 被標注為singleton的類,只會被實例化一次,這個實例可以無限重復注入。
prototype 被標注為prototype的類可以被實例化多次,每個實例只能注入一次,即每次注入prototype的實例時,要檢查這個實例是否已經在其他地方注入過,如果已經注入過,則不能再使用這個實例,要新建一個實例。
request,是一個請求周期內,實例可以重復注入。超過一個請求周期,再要注入就要新建實例,原來的實例不能使用了。
session,是一個會話周期內,實例可以重復注入。超過一個會話周期,再要注入就要新建實例,原來的實例不能使用了。
默認情況下,spring的Bean默認的作用域為單例,即在整個應用的周期內,每個類只創建一個實例。在大多數情況下,這種作用域是可以滿足要求的。但是對於像電商網站的購物車這樣的場景,單例作用域就不適用了,因為每個人的購物車都必須是獨立的,不能使用同一個。
寫到這里的時候,我想起了自己做過的一個助學貸款的項目,當時我並沒有關注過spring的Bean的作用域的問題,也就是說所有的學生使用的實例都是相同的,那為什么項目沒有出現錯誤呢?畢竟每個的學生信息都是不同的,如果共享單例,必然會發生混亂。后來想通了,所有使用依賴注入的類都是service、dao,即這些實例都是單例的,那些存儲信息的對象都是在方法中new出來的,哎,雖然事情早已過去,還是讓我虛驚一場。
我們先實驗一下prototype類型,將prototype類型的Bird注入到UserController3中,代碼如下:
package com.zaoren.bean; import java.util.Date; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @Component @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class Bird extends Animal { @Override public void move() { Date d = new Date(); d.getDay(); d.getDate(); this.play(); } @Override public void play() { } }
package com.zaoren.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import com.zaoren.bean.Bird; @RequestMapping("user3") @Controller public class UserController3 { @Autowired private Bird bird; @RequestMapping("test") public void test() { System.out.println("bird = "+bird); } }
兩次請求test方法,打印結果為:
打印結果竟然不符合我們的期望。明明將Bird類標注為prototype類型了,為什么兩次請求注入的是同一個實例呢?
這里我們要注意,spring處理請求時,首先要在容器中找到一個controller實例,用這個controller實例來處理請求。上面的代碼中,類UserController3並沒有標注為prototype類型,所以它默認為singleton類型,因此兩次請求實際上是由同一個UserController3實例來處理的,同一個UserController3實例的Bird屬性自然是相同的。
這里可以看出,我們查找問題時,要使用聯系和發散的思維方式。將局部問題放在整體過程中審視,並聯系相關的知識和過往經驗。
由此可以看出,要想試驗prototype的情況,需要將作用域同時設置在類Bird和類UserController3上,代碼如下:
package com.zaoren.bean; import java.util.Date; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @Component @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class Bird extends Animal { @Override public void move() { Date d = new Date(); d.getDay(); d.getDate(); this.play(); } @Override public void play() { } }
package com.zaoren.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import com.zaoren.bean.Bird; @RequestMapping("user3") @Controller @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class UserController3 { @Autowired private Bird bird; @RequestMapping("test") public void test() { System.out.println("bird = "+bird); } }
請求test方法,控制台輸出結果為:
這一次,在類UserController3上加了prototype作用域,所以兩次請求調用的controller是不同的實例,兩個UserController3實例都需要被注入一個Bird實例,由於類Bird的作用域為prototype,所以注入兩個UserController3實例的Bird實例是不同的,即第二次請求創建UserController3實例時,又重新創建了一個Bird實例來注入,沒有注入原來的Bird實例,控制台的輸出結果符合我們的期望。
這里,我又想到了另一個問題。我們的代碼中,並沒有將controller注入到任何地方,那為什么還可以對controller使用作用域呢?
要知道,我們最初學習的時候,http請求都是由我們自己編寫的servlet處理的,而spring是一個框架,它內部實際上封裝了servlet,只不過servlet這個類並沒有展示給我看。我們發起請求時,請求實際上先到達那個我們看不到的servlet,而servlet實例已經被注入了我們的controller實例,具體的業務處理正是由我們的controller實例來處理的。所以說controller的作用域也會起作用。