【问题标题】:Spring Prototype scoped bean in a singleton单例中的 Spring Prototype 范围 bean
【发布时间】:2014-09-29 16:34:54
【问题描述】:

我正在尝试在 singleton bean 中注入 prototype bean,这样每次对单例 bean 方法的新调用都有一个原型 bean 的新实例。

考虑如下的单例 bean:

    @Component
    public class SingletonBean {
       @Autowired 
       private PrototypeBean prototypeBean;

       public void doSomething() {
         prototypeBean.setX(1);
         prototypeBean.display();
       }
    }

我希望每次调用doSomething() 方法时,都会使用一个新的PrototypeBean 实例。

下面是原型bean:

     @Component 
     @Scope(value="prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
     public class PrototypeBean {
        Integer x;

        void setX(Integer x) {
         this.x = x;
        }

        void display() {
          System.out.println(x);
        }
    }

似乎正在发生的事情是 spring 过于渴望在 doSomething() 方法中移交 PrototypeBean 的新实例。也就是说,doSomething() 方法中的 2 行代码在每一行都创建了一个新的 prototypeBean 实例。

所以在第二行 - prototypeBean.display() 打印 NULL

此注入的配置中缺少什么?

【问题讨论】:

  • 使用代理原型 bean 的用例是什么?
  • @FranciscoSpaeth:我在单例 bean 中有多个使用原型 bean 方法的方法。原型 bean 是有状态的,因此每次调用单例 bean 方法时我都需要获取一个新副本。

标签: java spring


【解决方案1】:

来自春天documentation

您不需要将<aop:scoped-proxy/> 与 范围为单例或原型的 bean。如果你尝试 为单例 bean 创建一个作用域代理,BeanCreationException 被提升了。

3.2 版documentation 的文档似乎发生了一些变化,您可以在其中找到这句话:

您不需要将<aop:scoped-proxy/> 与 范围为单例或原型的 bean。

您似乎不希望使用代理原型 bean,因为每次向 BeanFactory 请求它时,它都会创建它的一个新实例。


为了为您的原型 bean 创建一种工厂,您可以使用 ObjectFactory,如下所示:

@Component
public class SingletonBean {

    @Autowired
    private ObjectFactory<PrototypeBean> prototypeFactory;

    public void doSomething() {
        PrototypeBean prototypeBean = prototypeFactory.getObject();
        prototypeBean.setX(1);
        prototypeBean.display();
    }
}

您的原型 bean 将声明如下:

@Component 
@Scope(value="prototype")
public class PrototypeBean {
    // ...
}

【讨论】:

  • 如果我不使用 proxyMode,那么在单例中自动装配原型不会每次都给出新的 bean。相反,要做到这一点,我必须做的是从 spring 的 applicationContext 调用 getBean()。但是,我希望简单地让原型 bean 自动装配。我不确定这是否可能。
  • hummm 恐怕没那么简单,spring 怎么知道什么时候创建一个新的原型 bean?每次再次调用使用原型bean的方法?
  • 检查最后一次编辑完成,用这种方法你不需要执行 getBean(PrototypeBean.class) 但使用工厂,我认为它或多或少是你正在寻找的
  • 是的,这就是我要找的。我最终使用了与您发布的类似的解决方案。我没有使用 ObjectFactory,而是使用了 JSR-303 Provider 接口。基本上做同样的事情。但感谢您的输入。 docs.oracle.com/javaee/6/api/javax/inject/Provider.html
  • 我认为这个解决方案很糟糕。您的业​​务 bean 依赖于 spreang 内部,这是非常大的耦合。
【解决方案2】:

单例bean只创建一次,因此注入的原型bean也将在单例bean实例化时创建一次。每个请求都将使用相同的原型bean实例。

如果在运行时为每个请求创建新的原型bean实例,可以使用下面的方法注入

示例

public class Singleton {
    private Prototype prototype;

    public Singleton(Prototype prototype) {
        this.prototype = prototype;
    }

    public void doSomething() {
         prototype.foo();
    }

    public void doSomethingElse() {
        prototype.bar();
    }
}

public abstract class Singleton {
    protected abstract Prototype createPrototype();

    public void doSomething() {
        createPrototype().foo();
    }

    public void doSomethingElse() {
        createPrototype().bar();
    }
}


<bean id="prototype" class="ch.frankel.blog.Prototype" scope="prototype" />
<bean id="singleton" class="sample.MySingleton">
   <lookup-method name="createPrototype" bean="prototype" />
</bean>

【讨论】:

  • 您的回答很好,我已经实际使用过,但示例不清楚。为什么你有两个名为 Singelton 的类?第一个执行第二个吗?什么是 MySingleton?
  • 为什么这不是公认的答案?看起来是一个更好的解决方案。
【解决方案3】:

实现它的正确方法 - 使用查找方法注入,并且在使用 bean 的任何地方都使用查找方法调用 (detailed answer)

【讨论】:

    【解决方案4】:

    从 Spring 4.1 开始可以使用注解@Lookup

    @Lookup
        public PrototypeBean getPrototypeBean() {
            return null;
        }
    

    每次您调用方法 getPrototypeBean() - 您都会收到新的原型 bean 实例。 不用担心空方法实现:Spring 会为您覆盖它。

    official documentation阅读更多内容。

    【讨论】:

      【解决方案5】:

      Spring 以一种非常直接的方式连接您的 bean。我在一个大型商业应用程序中工作,我插入了以下代码 sn-ps 来验证加载顺序。

      1) 所有的单例 bean 类结构最初都是由 Spring 加载的(只要 Spring 通过注解和/或 xml 知道它们)。这只会发生一次。您可以通过在静态块中记录或打印来测试:

          static {
              log.info("#### classNameHere loaded"); //or println if no log setup
          }
      

      2) Spring 创建它知道的所有单例实例(但不是原型!如果原型实例在单例 bean 中被引用,则将创建原型实例 - 当然首先加载类结构)。您可以通过将此方法添加到每个类来进行测试:

          @PostConstruct
          public void methodHitAfterClassInstantiation() {
              LOGGER.info("#### instance of classNameHere");
          }
      

      所以在你的例子中,SingletonBean 的类结构是在 Spring 启动时加载的。创建了一个新的 SingletonBean 实例。 而且因为 PrototypeBean 在 SingletonBean 内部是自动装配的,所以它的类结构被加载并创建了它的一个实例。现在,如果有另一个 bean,比如说 AnotherSingletonBean,里面有一个 Autowired PrototypeBean ,然后将创建一个不同的 PrototypeBean 实例(无需再次加载类结构)。所以只有1个SingletonBean,里面是一个PrototypeBean,它 将始终指向同一个bean。因此,单例应该始终是无状态的,因为所有其他使用单例的 bean 都将指向 同一个对象。但是您可以在原型 bean 中维护状态,因为无论您在何处创建新引用,您都将指向另一个 bean 对象。 http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#beans-factory-scopes-prototype

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-11-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-23
        • 2013-07-10
        • 1970-01-01
        相关资源
        最近更新 更多