在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号