我有一个名为MyInterface
的界面。实现MyInterface
的类(让我们称之为MyImplClass
)也实现了Runnable
接口,因此我可以使用它来实例化线程。这是我的代码。
for (OtherClass obj : someList) {
MyInterface myInter = new MyImplClass(obj);
Thread t = new Thread(myInter);
t.start();
}
我想要做的是在ApplicationContext.xml中声明实现类,并为每次迭代获取一个新实例。所以我的代码看起来像这样:
for (OtherClass obj : someList) {
MyInterface myInter = // getting the implementation from elsewhere
Thread t = new Thread(myInter);
t.start();
}
如果可能,我还想保留IoC模式。
我怎么能这样做?
感谢
答案 0 :(得分:13)
您可以尝试使用弹簧范围原型的工厂模式,如下所示。定义一个抽象工厂类,它将为您提供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 :(得分:2)
将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时返回相同的bean实例,你应该将bean的scope属性声明为singleton。
初始化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();
}
答案 2 :(得分:2)
根据您在评论中提供的上下文,我建议您不要使用Spring创建的MyImplClass
个实例。拥有这个由Spring实例化的原型对象并没有给我所知的任何好处。
在我看来,最好的方法是在这里保持IoC模式,而是使用一个生成MyImplClass
实例的Spring托管工厂。有点像这样:
public class MyInterfaceFactory {
public MyInterface newInstance(final OtherClass o) {
return new MyImplClass(o);
}
}
根据使用需求,您可以修改此工厂的界面以返回MyImplClass
,或添加一些逻辑以返回MyInterface
的不同实现。
我倾向于认为Factories和IoC / DI可以很好地协同工作,而你的用例就是一个非常好的例子。
答案 3 :(得分:2)
初始注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-concurrent)。
初步说明2
如果您打算在MyClassImpl
中存储的唯一状态是列表中的项目,那么您可以忘记下面的其余解释(除了ThreadPool的东西),并直接使用单例bean:删除Runnable
界面及其no-arg 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)
方法而非no-args run()
。
基本思想是根据定义为spring bean的某种模型或原型为每个正在运行的线程获取一个不同的对象。为了实现这一点,只需将您最初想要传递给每个线程的bean定义为分派给绑定到正在运行的线程的实例的代理。这意味着将相同的任务实例注入到每个线程中,并且在线程执行期间,调用方法的实际任务将绑定到当前线程。
主程序
由于您使用列表中的元素来开展业务,因此您将每个元素都传递给其拥有的任务。
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,然后autowire 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
。
答案 4 :(得分:0)
如果您可以在运行时确定要使用哪个MyImplClass
实例,则可以将所有实现列为上下文xml中的bean,将@Autowire
列为MyInterface
类型的数组,以获取所有MyInterface
实例<bean class="MyImplClass" p:somethingCaseSpecific="case1"/>
<bean class="MyImplClass" p:somethingCaseSpecific="case2"/>
1}}实现者。
在上下文中给出以下内容xml:
@Autowire
MyInterface[] allInterfaceBeans;
然后减速
allInterfaceBeans
将导致@Autowire
包含上面定义的两个bean。
如果您想要确定在注射时使用哪种实现的逻辑,您可以始终setAllInterfaceBeans(MyInterface[] allInterfaceBeans);
设置方法{{1}}。
答案 5 :(得分:0)
首先,我们都知道默认情况下,spring容器将以单例模式创建bean(如果你没有明确指定范围)。顾名思义,singleton保证每次调用bean时,它都会为你提供相同的实例。尽管如此,春季的单身人士与GoF提到的单身人士之间存在细微差别。在Spring中,创建的实例将仅限于容器(而不是我们在GoF中找到的JVM)。
此外,在spring中,您可以定义两个相同类型但具有不同名称的不同bean实例,它们将是在堆上创建的两个不同实例。但是每次按名称引用其中一个bean时(ref =在bean定义中或在appContext上的getBean中),每次都会获得相同的对象。这显然不同于实际的单身人士模式,但无论如何都在概念上相似。
一般来说,在多线程应用程序(Spring singleton或实际单例)中使用单例会有一些含义。您保留在这些对象上的任何状态都必须考虑到多个线程将访问它的事实。通常,任何存在的状态都将在实例化期间通过setter或constructor参数设置。这类Spring bean对长期存在的对象,线程安全对象有意义。如果你想要特定于线程的东西并且仍然需要spring来创建对象,那么原型范围就可以工作。