获取 spring bean 的新实例

Get new instance of a spring bean

我有一个名为 MyInterface 的界面。实现 MyInterface 的 class(我们称之为 MyImplClass)也实现了 Runnable 接口,所以我可以用它来实例化线程。这是我的代码。

for (OtherClass obj : someList) {
    MyInterface myInter = new MyImplClass(obj);
    Thread t = new Thread(myInter);
    t.start();
} 

我想做的是在我的 ApplicationContext.xml 中声明实现 class 并为每次迭代获取一个新实例。所以我的代码看起来像这样:

for (OtherClass obj : someList) {
    MyInterface myInter = // getting the implementation from elsewhere
    Thread t = new Thread(myInter);
    t.start();
} 

如果可能的话,我想仍然保留 IoC 模式。
我该怎么做?
谢谢

如果您可以在运行时确定要使用哪个 MyImplClass 实例,您可以将所有实现列为上下文中的 beans xml 和 @Autowire 类型数组 MyInterface 获取所有 MyInterface 实施者。

鉴于上下文 xml 中的以下内容:

<bean class="MyImplClass" p:somethingCaseSpecific="case1"/>
<bean class="MyImplClass" p:somethingCaseSpecific="case2"/>

然后减速

@Autowire
MyInterface[] allInterfaceBeans;

将导致 allInterfaceBeans 包含上面定义的两个 bean。

如果您希望确定在注入时使用哪个实现的逻辑,您总是可以@Autowire一个setter方法setAllInterfaceBeans(MyInterface[] allInterfaceBeans);

将 spring 配置文件 beans.xml 保存在类路径的根目录中。 使 scope=prototype,将导致每次调用 getBean 方法时产生不同的 bean 实例。

beans.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="
            http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="myinterface" class="MyImplClass" scope="prototype"/>
</beans>

类似的方法,如果你想 Spring 到 return 每次需要一个相同的 bean 实例,你应该将 bean 的范围属性声明为单例。

初始化 IoC 容器后,您可以检索 Spring bean。但请确保,您只执行以下初始化一次。

ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

然后你可以改变你的代码如下。

for (OtherClass obj : someList) {
MyInterface myInter = (MyInterface ) context.getBean("myinterface");
Thread t = new Thread(myInter);
t.start();
}

首先,我们都知道默认情况下spring容器会以单例模式创建bean(如果你没有明确指定作用域)。顾名思义,单例保证每次调用 bean 时,它都会给你相同的实例。然而,spring 中的单例与 GoF 提到的单例之间存在细微差别。在 Spring 中,创建的实例将被限制在容器中(而不是我们在 GoF 中发现的 JVM)。

此外,在spring中,您可以定义两个相同类型但名称不同的不同bean实例,它们将是在堆上创建的两个不同实例。但是每次您按名称引用其中一个 bean(bean 定义中的 ref= 或 appContext 中的 getBean),每次都会得到相同的对象。这明显不同于实际的单例模式,但在概念上是相似的。

一般来说,在多线程应用程序(Spring 单例或实际单例)中使用单例会产生一些影响。您在这些对象上保留的任何状态都必须说明多个线程将访问它的事实。通常,任何存在的状态都将在实例化期间通过 setter 或构造函数参数进行设置。此类 Spring bean 对长寿命对象、线程安全对象有意义。如果您想要线程特定的东西并且仍然希望 spring 创建对象,那么原型范围就可以了。

鉴于您在给我的评论中提供的上下文,我建议您不要使用 Spring 创建的 MyImplClass 个实例。据我所知,Spring 实例化这个原型对象没有任何好处。

在我看来,在这里保持 IoC 模式的最佳方法是使用 Spring 托管工厂来生成 MyImplClass 的实例。类似这样的事情:

public class MyInterfaceFactory {
    public MyInterface newInstance(final OtherClass o) {
        return new MyImplClass(o);
    }
}

根据使用需要,您可以将此工厂的接口修改为 return MyImplClass,或者向 return 添加一些逻辑 MyInterface 的不同实现。

我倾向于认为 Factories 和 IoC/DI 可以很好地协同工作,您的用例就是一个很好的例子。

您可以尝试使用 spring 作用域原型的工厂模式,如下所示。定义一个抽象工厂 Class,它会给你 MyInterface 对象

public abstract class MyInterfaceFactoryImpl implements MyInterfaceFactory {

@Override
public abstract MyInterface getMyInterface();

}

然后定义Spring bean.xml文件如下。请注意 myinterface bean 被定义为原型(因此它将始终为您提供新实例)。

<bean name="myinterface" class="com.xxx.MyInterfaceImpl" scope="prototype"/>

然后用工厂方法名定义factorybean。

<bean name="myinterfaceFactory" class="com.xxx.MyInterfaceFactoryImpl">
    <lookup-method bean="myinterface" name="getMyInterface" />
</bean>

现在您可以调用 myinterfaceFactory 获取新实例。

for (OtherClass obj : someList) {
        MyInterface myInter = myInterfaceFactory.getMyInterface();
        Thread t = new Thread(myInter);
        t.start();
}

初始注释 1

我建议使用外部配置的线程池,而不是手动创建和启动线程,这样您就可以管理创建的线程数。如果 someList 的大小是 1000,创建这么多线程是低效的。您最好使用由线程池支持的执行程序。 Spring 提供了一些可以用作配置了 task 命名空间的 spring bean 的实现,像这样:

<task:executor id="executor" queue-capacity="10" rejection-policy="CALLER_RUNS" />

queue-capacity 是线程池的最大大小。如果超过该大小,当前线程将 运行 附加任务,从而阻塞循环,直到另一个线程被释放 (rejection-policy="CALLER_RUNS")。请参阅 task:executor 文档,或使用您自己的配置定义任何 ThreadPoolExecutor(spring 或 jdk-并发)。

初注2

如果你打算在MyClassImpl中存储的唯一状态是列表中的项目,那么你可以忘记下面其余的解释(除了线程池的东西),直接使用单例bean :删除 Runnable 接口及其无参数 run() 方法,添加 run(OtherClass obj) 方法并执行如下操作:

final MyInterface task = // get it from spring as a singleton
for (final OtherClass obj : someList) {
  executor.execute(new Runnable() {
    public void run() {task.run(obj);}
  });
  // jdk 8 : executor.execute(task::run);
}

如果您计划在 run() 执行期间将某些状态存储在 MyClassImpl 中(处理的对象除外),请继续阅读。但是您仍然会使用 run(OtherClass obj) 方法而不是无参数 run().

基本思想是根据定义为 spring bean 的某种模型或原型,为每个 运行ning 线程获取不同的对象。为了实现这一点,只需将您最初想要传递给每个线程的 bean 定义为代理,该代理将分派给绑定到 运行ning 线程的实例。这意味着相同的任务实例被注入到每个线程中,并且在线程执行期间,您调用方法的真正任务被绑定到当前线程。

主程序

由于您正在使用列表的元素来开展业务,因此您会将每个元素传递给它所属的任务。

public class Program {
  @Resource private MyInterface task; // this is a proxy
  @Resource private TaskExecutor executor;

  public void executeConcurrently(List<OtherClass> someList) {
    for (final OtherClass obj : someList) {
      executor.execute(new Runnable() {
        public void run() { task.run(obj); }
      });
      // jdk 8 : executor.execute(task::run);
    }
  }
}

我们假设 Program 是一个 spring bean,因此可以注入依赖项。如果 Program 不是 spring bean,您将需要从某个地方获取 spring ApplicationContext,然后自动装配 Program (即根据注释注入在 ApplicationContext 中找到的依赖项).像这样的东西(在构造函数中):

public Program(ApplicationContext ctx) {
  ctx.getAutowireCapableBeanFactory().autowireBean(this);
}

定义任务

<bean id="taskTarget" class="MyImplClass" scope="prototype" autowire-candidate="false" />

<bean id="task" class="org.springframework.aop.framework.ProxyFactoryBean">
  <property name="targetSource">
    <bean class="org.springframework.aop.target.ThreadLocalTargetSource">
      <property name="targetBeanName" value="taskTarget"/>
      <property name="targetClass" value="MyInterface"/>
    </bean>
  </property>
</bean>

taskTarget 是您定义业务的地方。这个 bean 被定义为一个原型,因为一个新的实例将被分配给每个线程。多亏了这一点,你甚至可以存储依赖于 run() 参数的状态。该 bean 从未被应用程序直接使用(因此 autowire-candidate="false"),而是通过 task bean 使用。在上面的 executeConcurrently() 中,行 task.run(obj) 实际上将在代理创建的原型 taskTarget 之一上分派。