单例对象在初始化时只会注入一次原型对象,后续即使原型对象本身是多例的,单例对象中持有的引用也不会更新。所以在单例对象中注入原型对象需要经过特殊处理。

@Lookup方法注入

通过定义一个抽象方法,Spring 会动态生成子类并覆盖该方法,每次调用时从容器中获取新的原型实例。

  1. 在单例 Bean 中定义一个抽象方法,并用 @Lookup 标注;

  2. 方法返回类型为原型 Bean 的类型;

    @Component
    @Scope("prototype")
    public class PrototypeBean {
        public void doSomething() {
            System.out.println("Prototype instance: " + this.hashCode());
        }
    }
    
    @Service
    public abstract class SingletonService { // 可以是抽象类
        // 通过 @Lookup 动态获取 PrototypeBean
        @Lookup
        public abstract PrototypeBean getPrototypeBean();
    
        public void usePrototype() {
            PrototypeBean prototypeBean = getPrototypeBean(); // 每次调用都会返回新的实例
            prototypeBean.doSomething();
        }
    }

@Scope代理模式

通过为原型 Bean 创建代理对象,每次调用时代理会委托给新的真实实例。

  1. 在原型 Bean 上添加 @Scope 并指定代理模式为 TARGET_CLASS(基于 CGLIB 代理);

  2. 单例 Bean 直接注入原型 Bean,但实际调用时会动态获取新实例;

    @Component
    @Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS) // 关键配置
    public class PrototypeBean {
        public void doSomething() {
            System.out.println("Prototype instance: " + this.hashCode());
        }
    }
    
    @Service
    public class SingletonService {
        @Autowired
        private PrototypeBean prototypeBean; // 注入的是代理对象
    
        public void usePrototype() {
            prototypeBean.doSomething(); // 每次调用都会触发代理,返回新的实例
        }
    }

ObjectProvider 延迟注入

通过 ObjectProvider(Spring 5+)或 JSR-330 的 Provider 接口,显式获取原型实例。

  1. 注入 ObjectProvider<PrototypeBean>Provider<PrototypeBean>

  2. 调用 getObject()get() 方法获取新实例;

    @Service
    public class SingletonService {
        @Autowired
        private ObjectProvider<PrototypeBean> prototypeBeanProvider; // Spring 方式
        // 或使用 JSR-330 的 Provider(需依赖 javax.inject)
        // @Autowired
        // private Provider<PrototypeBean> prototypeBeanProvider;
    
        public void usePrototype() {
            PrototypeBean prototypeBean = prototypeBeanProvider.getObject(); // 每次获取新实例
            prototypeBean.doSomething();
        }
    }