首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Spring 單例 Bean 注入原型 Bean 解決方案一:查找方法注入

     編輯:Java 相關知識

問題背景

Spring 預設 Bean 為單例。當單例 Bean(如 Service)依賴非單例 Bean(如有狀態的 DAO、請求域物件)時,若直接注入,容器啟動時僅建立一次原型 Bean,後續所有請求共用同一實例,導致狀態汙染或執行緒不安全。

單純設定 singleton="false"(或 @Scope("prototype"))無效,因為單例 Bean 的依賴注入僅發生於初始化階段。

解決方案:查找方法注入

讓單例 Bean 宣告一個 abstract 方法(或具體方法),由容器在運行時動態覆寫,每次呼叫該方法時向容器請求新的原型 Bean 實例。

實作步驟

1. 定義介面與原型 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();
    }
}

2. 單例 Service 宣告查找方法

@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); // 每次新實例
    }
}

3. XML 配置查找方法映射

<bean id="userLoginDao" class="com.example.UserLoginDaoImpl" scope="prototype"/>

<bean id="userLoginService" class="com.example.UserLoginService">
    <lookup-method name="getUserLoginDao" bean="userLoginDao"/>
</bean>

4. Java Config 等效寫法

@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 所以新實例
            }
        };
    }
}

原理剖析

  • Spring 使用 CGLIB 為抽象類別/方法建立子類別。
  • 覆寫 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 代理類別數量,啟動略慢

替代方案速查

  • ObjectFactory<T> / ObjectProvider<T>(Spring 4.3+ / 5.0+):注入工廠介面,呼叫 getObject() 取得新實例,無需抽象類別。
  • javax.inject.Provider<T>:JSR-330 標準,用法同 ObjectFactory。
  • ScopedProxy:@Scope(value="prototype", proxyMode=ScopedProxyMode.TARGET_CLASS),注入代理物件,方法呼叫轉發至真實原型實例。

結語

查找方法注入是 Spring 解決單例依賴原型的經典模式,適合輕量級場景。現代專案建議優先考慮 ObjectProvider 或 ScopedProxy,程式碼更直觀且不受 final 限制。理解底層 CGLIB 代理機制,有助於除錯與效能調校。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved