知識庫 / Spring RSS 訂閱

在Spring中注入原型Bean到單例實例

Spring
HongKong
11
02:05 PM · Dec 06 ,2025

1. 概述

在本文中,我們將展示幾種將原型 Bean 注入到單例實例中的方法。 我們將討論每種場景的用例和優勢/劣勢。

默認情況下,Spring Bean 是單例。 當我們嘗試將不同作用域的 Bean 注入到單例中時,問題就出現了。 例如,將原型 Bean 注入到單例中。 這被稱為“作用域 Bean 注入問題”

要了解更多關於 Bean 作用域的信息,本文是一個不錯的起點。

2. 原型 Bean 注入問題

為了描述該問題,讓我們配置以下 Bean:

@Configuration
public class AppConfig {

    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public PrototypeBean prototypeBean() {
        return new PrototypeBean();
    }

    @Bean
    public SingletonBean singletonBean() {
        return new SingletonBean();
    }
}

請注意,第一個 Bean 具有原型作用域,其他的 Bean 具有單例作用。

現在,我們將原型作用域的 Bean 注入到單例 Bean 中,然後通過 getPrototypeBean()方法進行暴露:

public class SingletonBean {

    // ..

    @Autowired
    private PrototypeBean prototypeBean;

    public SingletonBean() {
        logger.info("Singleton instance created");
    }

    public PrototypeBean getPrototypeBean() {
        logger.info(String.valueOf(LocalTime.now()));
        return prototypeBean;
    }
}

然後,讓我們加載 ApplicationContext,並兩次獲取該單例 Bean:

public static void main(String[] args) throws InterruptedException {
    AnnotationConfigApplicationContext context 
      = new AnnotationConfigApplicationContext(AppConfig.class);
    
    SingletonBean firstSingleton = context.getBean(SingletonBean.class);
    PrototypeBean firstPrototype = firstSingleton.getPrototypeBean();
    
    // get singleton bean instance one more time
    SingletonBean secondSingleton = context.getBean(SingletonBean.class);
    PrototypeBean secondPrototype = secondSingleton.getPrototypeBean();

    isTrue(firstPrototype.equals(secondPrototype), "The same instance should be returned");
}

這是控制枱的輸出:

Singleton Bean created
Prototype Bean created
11:06:57.894
// should create another prototype bean instance here
11:06:58.895

兩個 Bean 僅在應用程序上下文啓動時初始化一次。

3. 注入 ApplicationContext </h2

我們還可以直接將 ApplicationContext 注入到bean中。

要實現這一點,可以使用 @Autowire 註解或實現 ApplicationContextAware 接口:

public class SingletonAppContextBean implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    public PrototypeBean getPrototypeBean() {
        return applicationContext.getBean(PrototypeBean.class);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) 
      throws BeansException {
        this.applicationContext = applicationContext;
    }
}

每次調用 getPrototypeBean() 方法時,都會從 ApplicationContext 中返回一個新的 PrototypeBean 實例。

然而,這種方法存在嚴重缺點。 它與依賴反轉原則相悖,因為我們直接從容器中請求依賴項。

此外,我們在 SingletonAppcontextBean 類中從 applicationContext 中獲取原型 Bean。 這意味着 將代碼與 Spring 框架耦合

4. 方法注入

另一種解決該問題的方案是使用方法注入,並結合使用 @Lookup 註解:

@Component
public class SingletonLookupBean {

    @Lookup
    public PrototypeBean getPrototypeBean() {
        return null;
    }
}

Spring 會覆蓋使用 getPrototypeBean() 方法並帶有 @Lookup 註解的方法。它隨後將該 Bean 註冊到應用程序上下文中。每次我們請求 getPrototypeBean() 方法時,它都會返回一個新的 PrototypeBean 實例。

它將使用 CGLIB 生成用於從應用程序上下文中檢索 PrototypeBean 的字節碼。

5. javax.inject API

本文檔描述了 Spring 注入所需的配置和依賴項。

以下是一個單例 Bean 的示例:

public class SingletonProviderBean {

    @Autowired
    private Provider<PrototypeBean> myPrototypeBeanProvider;

    public PrototypeBean getPrototypeInstance() {
        return myPrototypeBeanProvider.get();
    }
}

我們使用 Provider 接口 注入原型 Bean。對於每次 getPrototypeInstance() 方法調用,myPrototypeBeanProvider.get() 方法返回 PrototypeBean 的新實例。

6. 範圍代理 (Scoped Proxy)

默認情況下,Spring 會持有對真實對象的引用以進行注入。 在這裏,我們創建一個代理對象,用於將真實對象與依賴對象連接起來。

每次在代理對象上調用方法時,代理對象會自行決定是創建真實對象的實例還是重用現有實例。

要設置此功能,我們修改 Appconfig 類以添加新的 @Scope 註解:

@Scope(
  value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, 
  proxyMode = ScopedProxyMode.TARGET_CLASS)

默認情況下,Spring 使用 CGLIB 庫直接子類化對象。為了避免使用 CGLIB,可以配置代理模式為 ScopedProxyMode.INTERFACES,從而使用 JDK 動態代理。

7. ObjectFactory 接口

Spring 提供了 ObjectFactory 接口,用於按需生成指定類型的對象:

public class SingletonObjectFactoryBean {

    @Autowired
    private ObjectFactory<PrototypeBean> prototypeBeanObjectFactory;

    public PrototypeBean getPrototypeInstance() {
        return prototypeBeanObjectFactory.getObject();
    }
}

讓我們來查看 getPrototypeInstance() 方法; getObject() 方法會為每個請求返回一個新的 PrototypeBean 實例。在這裏,我們對原型實例的初始化擁有更大的控制權。

此外,ObjectFactory 是框架的一部分,這意味着為了使用此選項,無需進行額外的設置。

8. 在運行時使用 java.util.Function 創建 Bean

另一個選項是在運行時創建原型 Bean 實例,這也能允許我們向實例添加參數。

為了查看此示例,讓我們為我們的 PrototypeBean 類添加一個名為 "name" 的字段:

public class PrototypeBean {
    private String name;
    
    public PrototypeBean(String name) {
        this.name = name;
        logger.info("Prototype instance " + name + " created");
    }

    //...   
}

接下來,我們將通過利用 java.util.Function 接口,將一個 Bean 工廠注入到我們的單例 Bean 中:

public class SingletonFunctionBean {
    
    @Autowired
    private Function<String, PrototypeBean> beanFactory;
    
    public PrototypeBean getPrototypeInstance(String name) {
        PrototypeBean bean = beanFactory.apply(name);
        return bean;
    }

}

最後,我們需要在我們的配置中定義工廠 Bean、原型 Bean 和單例 Bean:

@Configuration
public class AppConfig {
    @Bean
    public Function<String, PrototypeBean> beanFactory() {
        return name -> prototypeBeanWithParam(name);
    } 

    @Bean
    @Scope(value = "prototype")
    public PrototypeBean prototypeBeanWithParam(String name) {
       return new PrototypeBean(name);
    }
    
    @Bean
    public SingletonFunctionBean singletonFunctionBean() {
        return new SingletonFunctionBean();
    }
    //...
}

9. 測試

現在,我們來編寫一個簡單的 JUnit 測試,以測試使用 ObjectFactory 接口的情況:

@Test
public void givenPrototypeInjection_WhenObjectFactory_ThenNewInstanceReturn() {

    AbstractApplicationContext context
     = new AnnotationConfigApplicationContext(AppConfig.class);

    SingletonObjectFactoryBean firstContext
     = context.getBean(SingletonObjectFactoryBean.class);
    SingletonObjectFactoryBean secondContext
     = context.getBean(SingletonObjectFactoryBean.class);

    PrototypeBean firstInstance = firstContext.getPrototypeInstance();
    PrototypeBean secondInstance = secondContext.getPrototypeInstance();

    assertTrue("New instance expected", firstInstance != secondInstance);
}

在測試成功啓動後,我們可以看到每次調用 getPrototypeInstance() 方法時,都會創建一個新的原型 Bean 實例。

10. 結論

在本教程中,我們學習了多種將原型 Bean 注入到單例實例的方法。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.