Spring 預設 Bean 為單例。當單例 Bean(如 Service)依賴非單例 Bean(如有狀態的 DAO、請求域物件)時,若直接注入,容器啟動時僅建立一次原型 Bean,後續所有請求共用同一實例,導致狀態汙染或執行緒不安全。
單純設定 singleton="false"(或 @Scope("prototype"))無效,因為單例 Bean 的依賴注入僅發生於初始化階段。
讓單例 Bean 宣告一個 abstract 方法(或具體方法),由容器在運行時動態覆寫,每次呼叫該方法時向容器請求新的原型 Bean 實例。
public interface UserLoginDao {
List getUser(String username, String password);
}
@Component
@Scope("prototype") // 或 XML singleton="false"
public class UserLoginDaoImpl implements UserLoginDao {
private int counter = 0; // 有狀態欄位,驗證每次新實例
public List getUser(String username, String password) {
System.out.println("counter=" + (++counter));
// 實際查詢邏輯...
return Collections.emptyList();
}
}
@Service
public abstract class UserLoginService implements UserLoginServiceInterface {
// 容器會動態實作此方法,每次呼叫返回新的 prototype Bean
protected abstract UserLoginDao getUserLoginDao();
public List getUser(String username, String password) {
return getUserLoginDao().getUser(username, password); // 每次新實例
}
}
<bean id="userLoginDao" class="com.example.UserLoginDaoImpl" scope="prototype"/>
<bean id="userLoginService" class="com.example.UserLoginService">
<lookup-method name="getUserLoginDao" bean="userLoginDao"/>
</bean>
@Configuration
public class AppConfig {
@Bean
@Scope("prototype")
public UserLoginDao userLoginDao() {
return new UserLoginDaoImpl();
}
@Bean
public UserLoginService userLoginService() {
// 使用 CGLIB 子類化實作 lookup-method
return new UserLoginService() {
@Override
protected UserLoginDao getUserLoginDao() {
return userLoginDao(); // 每次呼叫 @Bean 方法,因為 prototype 所以新實例
}
};
}
}
getUserLoginDao(),方法體內呼叫 BeanFactory.getBean("userLoginDao")。userLoginDao 為 prototype,每次 getBean 產生新實例。連續呼叫 Service 方法,主控台輸出:
counter=1
counter=1
counter=1
...
證明每次皆獲得新的 DAO 實例。
| 優點 | 缺點/限制 |
|---|---|
| 不引入額外依賴,純 Spring 核心功能 | 目標類別/方法必須非 final、非 private(需可被 CGLIB 子類化) |
| 程式碼簡潔,業務邏輯無感知容器 | 僅適用單例→原型單向注入,雙向或多層級需改用 Provider/ObjectFactory |
| XML 與 Java Config 雙支援 | 大量使用會增加 CGLIB 代理類別數量,啟動略慢 |
getObject() 取得新實例,無需抽象類別。@Scope(value="prototype", proxyMode=ScopedProxyMode.TARGET_CLASS),注入代理物件,方法呼叫轉發至真實原型實例。查找方法注入是 Spring 解決單例依賴原型的經典模式,適合輕量級場景。現代專案建議優先考慮 ObjectProvider 或 ScopedProxy,程式碼更直觀且不受 final 限制。理解底層 CGLIB 代理機制,有助於除錯與效能調校。