Scope作用域
在 Spring IoC 容器中具有以下幾種作用域:
- singleton:單例模式,在整個Spring IoC容器中,使用singleton定義的Bean將只有一個實例,適用于無狀態(tài)bean;
- prototype:原型模式,每次通過容器的getBean方法獲取prototype定義的Bean時,都將產(chǎn)生一個新的Bean實例,適用于有狀態(tài)的Bean;
- request:對于每次HTTP請求,使用request定義的Bean都將產(chǎn)生一個新實例,即每次HTTP請求將會產(chǎn)生不同的Bean實例。只有在Web應(yīng)用中使用Spring時,該作用域才有效;
- session:對于每次HTTP Session,使用session定義的Bean豆?jié){產(chǎn)生一個新實例。同樣只有在Web應(yīng)用中使用Spring時,該作用域才有效;
- globalsession:每個全局的HTTP Session,使用session定義的Bean都將產(chǎn)生一個新實例。典型情況下,僅在使用portlet context的時候有效。同樣只有在Web應(yīng)用中使用Spring時,該作用域才有效。
@scope默認(rèn)是單例模式(singleton),如果需要設(shè)置的話@scope("prototype")
或xml配置如下:
<bean id="service1" class="com.test.TestServiceImpl1" scope="singleton" />
<bean id="service2" class="com.test.TestServiceImpl2" scope="prototype" />
無狀態(tài)會話bean
bean一旦實例化就被加進(jìn)會話池中,各個用戶都可以共用。即使用戶已經(jīng)消亡,bean 的生命期也不一定結(jié)束,它可能依然存在于會話池中,供其他用戶調(diào)用。
由于沒有特定的用戶,那么也就不能保持某一用戶的狀態(tài),所以叫無狀態(tài)bean。但無狀態(tài)會話bean 并非沒有狀態(tài),如果它有自己的屬性(變量),那么這些變量就會受到所有調(diào)用它的用戶的影響。
有狀態(tài)會話bean
每個用戶有自己特有的一個實例,在用戶的生存期內(nèi),bean保持了用戶的信息,即“有狀態(tài)”;一旦用戶滅亡(調(diào)用結(jié)束或?qū)嵗Y(jié)束),bean的生命期也告結(jié)束。即每個用戶最初都會得到一個初始的bean。
有狀態(tài)bean,如果配置為singleton,會出現(xiàn)線程安全問題
示例:
package com.test;
public class TestServiceImpl implements TestService{
private User user;
public void test1(User u) throws Exception {
this.user = u; //1
test2();
}
public void test2() throws Exception {
System.out.println(user.getId()); //2
}
}
如果該Bean配置為singleton,在并發(fā)訪問下會出現(xiàn)問題
假設(shè)有2個用戶user1,user2訪問,都調(diào)用到了該Bean。
1.當(dāng)user1 調(diào)用到程序中的1步驟的時候,該Bean的私有變量user被付值為user1;
2.理想的狀況,當(dāng)user1走到2步驟的時候,私有變量user應(yīng)該為user1;
3.但如果在user1調(diào)用到2步驟之前,user2開始運行到了1步驟了,由于單態(tài)的資源共享,則私有變量user被修改為user2;
4.這種情況下,user1的步驟2用到的user.getId()實際用到是user2的對象。
實際應(yīng)該是這個例子不應(yīng)該用實例變量,這樣就使得這個Bean由無狀態(tài)變成了有狀態(tài)Bean。
常用web架構(gòu),線程安全問題場景分析
1.SSH架構(gòu)系統(tǒng)
對于SSH架構(gòu)的系統(tǒng),很少關(guān)心這方面,因為我們用到的一般都是singleton. Bean的注入由Spring管理。
Struts2中的Action因為會有User這樣的實例對象,是有狀態(tài)信息的,在多線程環(huán)境下是不安全的,所以Struts2默認(rèn)的實現(xiàn)是Prototype模式。也就是每個請求都新生成一個Action實例,所以不存在線程安全問題。需要注意的是,如果由Spring管理action的生命周期, scope要配成prototype作用域。
Struts1是基于單例模式實現(xiàn),也就是只有一個Action實例供多線程使用。默認(rèn)的模式是前臺頁面數(shù)據(jù)通過actionForm傳入,在action中的excute方法接收,這樣action是無狀態(tài)的,所以一般情況下Strunts1是線程安全的。如果Action中用了實例變量,那么就變成有狀態(tài)了,同樣是非線程安全的。像下面這樣就是線程不安全的。
/**
* 非線程安全的Struts1示例
*
*/
public class UserAction extends Action {
// 因為Struts1是單例實現(xiàn),有狀態(tài)情況下,對象引用是非線程安全的
private User user;
public void execute() {
// do something...
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
2.Servlet
Servlet體系結(jié)構(gòu)是建立在Java多線程機(jī)制之上的,它的生命周期是由Web 容器負(fù)責(zé)的。
一個Servlet類在Application中只有一個實例存在,有多個線程在使用這個實例。這是單例模式的應(yīng)用。
無狀態(tài)的單例是線程安全的,但我們?nèi)绻赟ervlet里用了實例變量(私有變量),那么就變成有狀態(tài)了,是非線程安全的。
如下面的用法就是不安全的,因為user是有狀態(tài)信息的。
public class UserServlet HttpServlet{
private User user;
public void doGet (HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException{
//do something...
}
}
總結(jié)
- singleton會造成資源混亂問題,而如果是prototype的話,就不會出現(xiàn)資源共享的問題。(即不會出現(xiàn)線程安全的問題)
- 應(yīng)該盡量使用無狀態(tài)Bean.如果在程序中出現(xiàn)私有變量(該bean會變?yōu)橛袪顟B(tài)的,一旦在其他線程中發(fā)生改變,就會產(chǎn)生線程不安全),解決方案就是盡量替換為方法中的參數(shù)。對于每個訪問私有變量的方法增加變量傳入(參數(shù)傳入)或者通過ThreadLocal來獲取。
- 如果用有狀態(tài)的bean,就要用prototype模式,每次在注入的時候就重新創(chuàng)建一個bean,在多線程中互不影響。
- 如Service層、Dao層用默認(rèn)singleton就行,雖然Service類也有dao這樣的屬性,但dao這些類都是沒有狀態(tài)信息的,也就是相當(dāng)于不變(immutable)類,所以不影響。
- Stateless無狀態(tài)用單例Singleton模式,Stateful有狀態(tài)就用原型Prototype模式。