在spring boot 6中,singleton 的bean 要依赖 prototype 的bean的接口时,的最佳实践

在 Spring Framework 中,@Singleton@Prototype 是两种常见的作用域,分别用于创建单例和多例对象。当一个单例 Bean 需要依赖一个多例(@Prototype)Bean 时,尤其是通过该 Bean 的接口间接依赖时,直接注入 @Prototype Bean 的接口可能会导致作用域失效,Spring 容器会将 @Prototype Bean 的实例变为单例,导致每次使用的是同一个实例。

为了解决这个问题,可以采用以下 最佳实践


1. 使用 ObjectFactoryProvider 注入 @Prototype Bean

Spring 提供了 ObjectFactoryjavax.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 实例。


最佳实践的选择

  • 推荐使用 ObjectFactoryjavax.inject.Provider

    • 优点
      • 代码简洁,使用简单,Spring 原生支持。
      • 减少了对 Spring 容器的耦合。
    • 场景:适用于绝大多数情况。
  • 代理模式(Scoped Proxy)

    • 优点
      • 代码透明,开发者不需要显式调用工厂方法。
    • 缺点
      • 需要为每个 Prototype Bean 创建动态代理,可能增加内存开销。
    • 场景:适用于对 Spring AOP 机制熟悉且需要使用接口的项目。
  • 直接使用 ApplicationContext

    • 优点
      • 灵活,可以动态选择不同类型的 Bean。
    • 缺点
      • 增加了代码与 Spring 容器的耦合,不推荐在现代 Spring 项目中大量使用。
    • 场景:少量 Prototype Bean,且不经常调用的场景。

总结

如果你需要从 Singleton Bean 间接依赖 Prototype Bean 的接口,在现代 Spring 应用中,建议优先使用 ObjectFactoryProvider 的方式。它简单、直接、对容器依赖较小,并能很好地满足分布式和多线程环境中的需求。

posted @ 2024-11-22 15:11  gongchengship  阅读(10)  评论(0)    收藏  举报