You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot如何为模块创建独立上下文的单例实例?

问题描述

我的Spring Boot应用包含一个提供技术功能的模块(命名为MyModule),该模块内所有标注@Component的类均为单例,仅需实例化一次,代码示例如下:

package my.module;

interface IMyFeature {
}

@Component
class MyFeature implements IMyFeature {  // 单例

    private final MyOtherClass1 myOtherClass1;
    private final MyOtherClass2 myOtherClass2;

    @Autowired
    public MyFeature(MyOtherClass1 myOtherClass1,    // 注入单例
                     MyOtherClass2 myOtherClass2) {  // 注入单例
        this.myOtherClass1 = myOtherClass1;
        this.myOtherClass2 = myOtherClass2;
    }

}

@Component
class MyOtherClass1 {  // 单例

    private final MyOtherClass2 myOtherClass2;

    @Autowired
    public MyOtherClass1(MyOtherClass2 myOtherClass2) { // 注入与MyFeature中相同的单例实例
        this.myOtherClass2 = myOtherClass2;
    }

}

@Component
class MyOtherClass2 {  // 单例

    @Autowired
    public MyOtherClass2() {
    }

}

在应用的其他模块中,可按需注入MyModule中的类,且获取的均为同一单例实例,示例代码如下:

package other.module1;

class FooBar {

   private final IMyFeature myFeature;     

   @Autowired
   public FooBar(IMyFeature myFeature) {
       this.myFeature = myFeature;
   }

}

package other.module2;

class HelloWorld {
     
   private final IMyFeature myFeature;     

   @Autowired
   public HelloWorld(IMyFeature myFeature) { // 此处获取的myFeature与other.module1.FooBar中的实例相同
       this.myFeature = myFeature;
   }

}

当前该模式运行正常。现需新增一个模块(命名为StrangeModule),需在不同配置上下文下运行MyModule的功能,因此需要获取MyModule内所有@Component单例类的独立实例,而非与其他模块共享实例,示例代码如下:

package strange.module;

class StrangeClass {

   private final IMyFeature myFeature;     

   @Autowired
   public StrangeClass(my.module.IMyFeature myFeature) {
    // !!! 此处不希望myFeature与other.module1.FooBar和other.module2.HelloWorld中的实例相同
    // !!! 同时不希望MyOtherClass1和MyOtherClass2的实例与其他模块中获取的实例相同
       this.myFeature = myFeature;
   }

}

请问该如何实现上述需求?


解决方案

方法1:自定义专属作用域

创建一个仅属于StrangeModule的自定义作用域,让MyModule的Bean在该作用域下生成独立的单例实例,与主上下文的实例完全隔离。

步骤1:实现自定义作用域类

import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;
import java.util.HashMap;
import java.util.Map;

public class StrangeModuleScope implements Scope {
    private final Map<String, Object> beanInstances = new HashMap<>();

    @Override
    public Object get(String beanName, ObjectFactory<?> objectFactory) {
        // 作用域内仅实例化一次,保持单例特性
        return beanInstances.computeIfAbsent(beanName, k -> objectFactory.getObject());
    }

    @Override
    public Object remove(String beanName) {
        return beanInstances.remove(beanName);
    }

    @Override
    public void registerDestructionCallback(String beanName, Runnable callback) {
        // 按需实现Bean销毁逻辑
    }

    @Override
    public Object resolveContextualObject(String key) {
        return null;
    }

    @Override
    public String getConversationId() {
        return "strange-module-scope";
    }
}

步骤2:注册自定义作用域

在配置类中把自定义作用域注册到Spring容器:

import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;

@Configuration
public class ScopeRegistrationConfig {
    private final org.springframework.beans.factory.config.ConfigurableBeanFactory beanFactory;

    public ScopeRegistrationConfig(org.springframework.beans.factory.config.ConfigurableBeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }

    @PostConstruct
    public void registerStrangeScope() {
        beanFactory.registerScope("strangeScope", new StrangeModuleScope());
    }
}

步骤3:为StrangeModule配置独立Bean实例

创建配置类,导入MyModule的Bean并指定使用自定义作用域:

import my.module.MyFeature;
import my.module.MyOtherClass1;
import my.module.MyOtherClass2;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.beans.factory.annotation.Qualifier;

@Configuration
public class StrangeModuleBeanConfig {

    @Bean
    @Scope("strangeScope")
    public MyOtherClass2 strangeMyOtherClass2() {
        return new MyOtherClass2();
    }

    @Bean
    @Scope("strangeScope")
    public MyOtherClass1 strangeMyOtherClass1(MyOtherClass2 strangeMyOtherClass2) {
        return new MyOtherClass1(strangeMyOtherClass2);
    }

    @Bean
    @Scope("strangeScope")
    public MyFeature strangeMyFeature(MyOtherClass1 strangeMyOtherClass1, MyOtherClass2 strangeMyOtherClass2) {
        return new MyFeature(strangeMyOtherClass1, strangeMyOtherClass2);
    }

    @Bean
    @Qualifier("strange")
    public IMyFeature strangeIMyFeature(MyFeature strangeMyFeature) {
        return strangeMyFeature;
    }
}

步骤4:在StrangeModule中注入专属实例

使用@Qualifier指定注入自定义作用域下的Bean:

package strange.module;

import my.module.IMyFeature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

class StrangeClass {

   private final IMyFeature myFeature;     

   @Autowired
   public StrangeClass(@Qualifier("strange") IMyFeature myFeature) {
       this.myFeature = myFeature;
   }

}

方法2:创建独立子上下文

为StrangeModule创建一个独立的Spring子ApplicationContext,该上下文会重新实例化MyModule的所有Bean,与主上下文完全隔离。

步骤1:定义子上下文配置类

import my.module.MyFeature;
import my.module.MyOtherClass1;
import my.module.MyOtherClass2;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackageClasses = {MyFeature.class, MyOtherClass1.class, MyOtherClass2.class})
public class StrangeModuleContextConfig {
}

步骤2:在StrangeModule中初始化子上下文

package strange.module;

import my.module.IMyFeature;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

class StrangeClass {

   private final IMyFeature myFeature;     

   public StrangeClass() {
       // 初始化独立子上下文
       AnnotationConfigApplicationContext childContext = new AnnotationConfigApplicationContext();
       childContext.register(StrangeModuleContextConfig.class);
       childContext.refresh();
       // 从子上下文获取独立实例
       this.myFeature = childContext.getBean(IMyFeature.class);
   }

}

注意:需要自行管理子上下文的生命周期,避免内存泄漏。


方法3:使用Profile区分实例

通过Spring的Profile功能,为MyModule的Bean创建两套实例,分别对应默认环境和StrangeModule的环境。

步骤1:修改MyModule的Bean定义

为原有Bean添加@Profile("!strange"),表示仅在非strange环境下生效:

package my.module;

import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;

interface IMyFeature {
}

@Component
@Profile("!strange")
class MyFeature implements IMyFeature {
    // 原有代码不变
}

@Component
@Profile("!strange")
class MyOtherClass1 {
    // 原有代码不变
}

@Component
@Profile("!strange")
class MyOtherClass2 {
    // 原有代码不变
}

步骤2:创建StrangeModule专属Bean配置

package my.module;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

@Configuration
@Profile("strange")
public class StrangeModuleMyModuleConfig {

    @Bean
    public MyOtherClass2 strangeMyOtherClass2() {
        return new MyOtherClass2();
    }

    @Bean
    public MyOtherClass1 strangeMyOtherClass1(MyOtherClass2 strangeMyOtherClass2) {
        return new MyOtherClass1(strangeMyOtherClass2);
    }

    @Bean
    public IMyFeature strangeMyFeature(MyOtherClass1 strangeMyOtherClass1, MyOtherClass2 strangeMyOtherClass2) {
        return new MyFeature(strangeMyOtherClass1, strangeMyOtherClass2);
    }
}

步骤3:标记StrangeModule的Bean所属Profile

package strange.module;

import my.module.IMyFeature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;

@Component
@Profile("strange")
class StrangeClass {

   private final IMyFeature myFeature;     

   @Autowired
   public StrangeClass(IMyFeature myFeature) {
       this.myFeature = myFeature;
   }

}

注意:需要在启动时激活对应的Profile,适合需要按环境隔离实例的场景。


内容的提问来源于stack exchange,提问作者Philippe MESMEUR

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 10:34:57