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

