在spring boot 6中,singleton 的bean 要依赖 prototype 的bean的接口时,的最佳实践
在 Spring Framework 中,@Singleton 和 @Prototype 是两种常见的作用域,分别用于创建单例和多例对象。当一个单例 Bean 需要依赖一个多例(@Prototype)Bean 时,尤其是通过该 Bean 的接口间接依赖时,直接注入 @Prototype Bean 的接口可能会导致作用域失效,Spring 容器会将 @Prototype Bean 的实例变为单例,导致每次使用的是同一个实例。
为了解决这个问题,可以采用以下 最佳实践:
1. 使用 ObjectFactory 或 Provider 注入 @Prototype Bean
Spring 提供了 ObjectFactory 和 javax.inject.Provider 来延迟加载 @Prototype Bean,并确保每次获取的都是新的实例。
实现步骤
定义接口和实现类
public interface MyPrototypeBean {
void doSomething();
}
@Component
@Scope("prototype")
public class MyPrototypeBeanImpl implements MyPrototypeBean {
@Override
public void doSomething() {
System.out.println("MyPrototypeBeanImpl: " + this);
}
}
在 Singleton Bean 中使用 ObjectFactory
@Component
public class MySingletonBean {
private final ObjectFactory<MyPrototypeBean> prototypeBeanFactory;
// 构造器注入
public MySingletonBean(ObjectFactory<MyPrototypeBean> prototypeBeanFactory) {
this.prototypeBeanFactory = prototypeBeanFactory;
}
public void execute() {
// 每次调用都获取新的 Prototype Bean 实例
MyPrototypeBean prototypeBean = prototypeBeanFactory.getObject();
prototypeBean.doSomething();
}
}
运行测试
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(DemoApplication.class, args);
MySingletonBean singletonBean = context.getBean(MySingletonBean.class);
singletonBean.execute(); // 第一次获取 Prototype Bean
singletonBean.execute(); // 第二次获取 Prototype Bean
}
}
输出示例:
MyPrototypeBeanImpl: com.example.MyPrototypeBeanImpl@1d81eb93
MyPrototypeBeanImpl: com.example.MyPrototypeBeanImpl@6dfaa767
2. 使用 Spring 的代理模式
Spring 支持通过 AOP 的方式对 @Prototype Bean 进行动态代理,使得每次访问时都会创建一个新的实例。
实现步骤
定义接口和实现类
public interface MyPrototypeBean {
void doSomething();
}
@Component
@Scope(value = "prototype", proxyMode = ScopedProxyMode.INTERFACES)
public class MyPrototypeBeanImpl implements MyPrototypeBean {
@Override
public void doSomething() {
System.out.println("MyPrototypeBeanImpl: " + this);
}
}
在 Singleton Bean 中注入接口
@Component
public class MySingletonBean {
private final MyPrototypeBean prototypeBean;
// 构造器注入
public MySingletonBean(MyPrototypeBean prototypeBean) {
this.prototypeBean = prototypeBean;
}
public void execute() {
// 每次调用方法都会创建新的 Prototype 实例
prototypeBean.doSomething();
}
}
运行测试
同样使用上述测试代码,运行后,输出的 MyPrototypeBeanImpl 实例每次都会不同。
输出示例:
MyPrototypeBeanImpl: com.example.MyPrototypeBeanImpl@1d81eb93
MyPrototypeBeanImpl: com.example.MyPrototypeBeanImpl@6dfaa767
3. 使用 ApplicationContext 手动获取 Prototype Bean
直接通过 ApplicationContext 获取 Prototype Bean 也是一种简单的方式,但可能会引入一定的代码侵入性。
实现步骤
定义接口和实现类
public interface MyPrototypeBean {
void doSomething();
}
@Component
@Scope("prototype")
public class MyPrototypeBeanImpl implements MyPrototypeBean {
@Override
public void doSomething() {
System.out.println("MyPrototypeBeanImpl: " + this);
}
}
在 Singleton Bean 中注入 ApplicationContext
@Component
public class MySingletonBean {
private final ApplicationContext applicationContext;
public MySingletonBean(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
public void execute() {
// 手动从 ApplicationContext 获取 Prototype Bean
MyPrototypeBean prototypeBean = applicationContext.getBean(MyPrototypeBean.class);
prototypeBean.doSomething();
}
}
运行测试
与前面的示例相同,每次调用都会获取不同的 @Prototype Bean 实例。
最佳实践的选择
-
推荐使用
ObjectFactory或javax.inject.Provider:- 优点:
- 代码简洁,使用简单,Spring 原生支持。
- 减少了对 Spring 容器的耦合。
- 场景:适用于绝大多数情况。
- 优点:
-
代理模式(Scoped Proxy):
- 优点:
- 代码透明,开发者不需要显式调用工厂方法。
- 缺点:
- 需要为每个 Prototype Bean 创建动态代理,可能增加内存开销。
- 场景:适用于对 Spring AOP 机制熟悉且需要使用接口的项目。
- 优点:
-
直接使用 ApplicationContext:
- 优点:
- 灵活,可以动态选择不同类型的 Bean。
- 缺点:
- 增加了代码与 Spring 容器的耦合,不推荐在现代 Spring 项目中大量使用。
- 场景:少量 Prototype Bean,且不经常调用的场景。
- 优点:
总结
如果你需要从 Singleton Bean 间接依赖 Prototype Bean 的接口,在现代 Spring 应用中,建议优先使用 ObjectFactory 或 Provider 的方式。它简单、直接、对容器依赖较小,并能很好地满足分布式和多线程环境中的需求。
浙公网安备 33010602011771号