J'ai une interface appelée MyInterface
. La classe qui implémente MyInterface
(appelons-la MyImplClass
) implémente également l'interface Runnable
afin que je puisse l'utiliser pour instancier des threads. Ceci est mon code maintenant.
for (OtherClass obj : someList) {
MyInterface myInter = new MyImplClass(obj);
Thread t = new Thread(myInter);
t.start();
}
Ce que je veux faire, c'est déclarer la classe d'implémentation dans mon ApplicationContext.xml et obtenir une nouvelle instance pour chaque itération. Donc, mon code ressemblera à quelque chose comme ça:
for (OtherClass obj : someList) {
MyInterface myInter = // getting the implementation from elsewhere
Thread t = new Thread(myInter);
t.start();
}
Je veux toujours conserver le modèle IoC si possible.
Comment puis-je le faire?
Merci
Vous pouvez essayer un modèle d’usine avec un prototype à ressort comme ci-dessous. Définissez une classe d'abstraction abstraite qui vous donnera l'objet MyInterface
public abstract class MyInterfaceFactoryImpl implements MyInterfaceFactory {
@Override
public abstract MyInterface getMyInterface();
}
Définissez ensuite le fichier Spring bean.xml comme indiqué ci-dessous. Veuillez noter que myinterface
bean est défini comme un prototype (il vous donnera donc toujours une nouvelle instance).
<bean name="myinterface" class="com.xxx.MyInterfaceImpl" scope="prototype"/>
Définissez ensuite le factorybean avec le nom de la méthode factory.
<bean name="myinterfaceFactory" class="com.xxx.MyInterfaceFactoryImpl">
<lookup-method bean="myinterface" name="getMyInterface" />
</bean>
Vous pouvez maintenant appeler myinterfaceFactory
pour obtenir une nouvelle instance.
for (OtherClass obj : someList) {
MyInterface myInter = myInterfaceFactory.getMyInterface();
Thread t = new Thread(myInter);
t.start();
}
Note initiale 1
Au lieu de créer et de démarrer des threads à la main, il est conseillé d'utiliser un pool de threads configuré de manière externe, afin de pouvoir gérer le nombre de threads créés. Si la taille de someList
est 1000, créer autant de threads est inefficace. Vous devriez mieux utiliser un exécuteur sauvegardé par un pool de threads. Spring fournit des implémentations qui peuvent être utilisées comme beans printemps configurés avec l’espace de noms task
, comme ceci:
<task:executor id="executor" queue-capacity="10" rejection-policy="CALLER_RUNS" />
queue-capacity
est la taille maximale du pool de threads. Si cette taille est dépassée, le thread actuel exécutera la tâche supplémentaire, bloquant ainsi la boucle jusqu'à ce qu'un autre thread soit libéré (rejection-policy="CALLER_RUNS"
). Reportez-vous à la documentation task:executor
ou définissez une ThreadPoolExecutor
(source ou jdk-simultanée) avec votre propre configuration.
Note initiale 2
Si le seul état que vous avez l'intention de stocker dans MyClassImpl
est l'élément de la liste, vous pouvez alors oublier le reste de l'explication ci-dessous (à l'exception du contenu ThreadPool) et utiliser directement un bean singleton: supprimez l'interface Runnable
et sa valeur no. -arg run()
, ajoutez une méthode run(OtherClass obj)
et procédez comme suit:
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);
}
Si vous envisagez de stocker un état dans MyClassImpl lors de l'exécution de run()
(autre que l'objet traité), continuez la lecture. Mais vous utiliserez toujours la méthode run(OtherClass obj)
au lieu de no-args run()
.
L'idée de base est d'obtenir un objet différent pour chaque thread en cours d'exécution, en fonction d'un type de modèle ou d'un prototype défini comme un haricot à ressort. Pour ce faire, il suffit de définir le bean que vous souhaitez initialement transmettre à chaque thread en tant que proxy envoyé à une instance liée au thread en cours d'exécution. Cela signifie que la même instance de tâche est injectée dans chaque thread et que, lors de son exécution, la tâche réelle sur laquelle vous appelez des méthodes est liée au thread en cours.
Programme principal
Puisque vous utilisez les éléments de la liste pour faire votre travail, vous passerez chaque élément à sa tâche.
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);
}
}
}
Nous supposons que Program
est un haricot de printemps, ainsi les dépendances peuvent être injectées. Si Program
n'est pas un haricot de printemps, vous devrez obtenir le printemps ApplicationContext quelque part, puis autowire Program
(c'est-à-dire injecter des dépendances trouvées dans le ApplicationContext, en fonction des annotations). Quelque chose comme ça (dans le constructeur):
public Program(ApplicationContext ctx) {
ctx.getAutowireCapableBeanFactory().autowireBean(this);
}
Définir la tâche
<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
est l'endroit où vous définissez votre entreprise. Ce bean est défini comme un prototype, car une nouvelle instance sera allouée à chaque thread. Grâce à cela, vous pouvez même stocker un état qui dépend du paramètre run()
. Ce bean n'est jamais utilisé directement par l'application (donc autowire-candidate="false"
), mais par l'intermédiaire du bean task
. Dans executeConcurrently()
ci-dessus, la ligne task.run(obj)
sera effectivement distribuée sur l'un des prototypes taskTarget
créés par le proxy.
Conservez le fichier de configuration printanière, beans.xml, à la racine du chemin de classe . La création de scope = prototype produira différentes instances de bean pour chaque invocation de méthode getBean.
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>
De la même manière, si vous souhaitez que Spring renvoie la même instance de bean à chaque fois que nécessaire, vous devez déclarer l'attribut scope du bean comme étant singleton.
Une fois le conteneur IoC initialisé, vous pouvez récupérer vos beans Spring. Mais assurez-vous que vous ne faites l'initialisation ci-dessous qu'une seule fois.
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Ensuite, vous pouvez changer votre code comme ci-dessous.
for (OtherClass obj : someList) {
MyInterface myInter = (MyInterface ) context.getBean("myinterface");
Thread t = new Thread(myInter);
t.start();
}
Compte tenu du contexte que vous m'avez fourni dans votre commentaire, je suggérerais que vous n'ayez pas les instances MyImplClass
créées par Spring. Avoir cet objet prototypé instancié par Spring ne procure aucun avantage de ce que je peux dire.
La meilleure façon, à mon avis, de rester avec le modèle IoC ici serait d'utiliser plutôt une usine gérée par Spring qui produit des instances de MyImplClass
. Quelque chose dans le sens de ceci:
public class MyInterfaceFactory {
public MyInterface newInstance(final OtherClass o) {
return new MyImplClass(o);
}
}
En fonction des besoins d'utilisation, vous pouvez modifier l'interface de cette fabrique pour renvoyer MyImplClass
ou ajouter une logique pour renvoyer une implémentation différente de MyInterface
.
J'ai tendance à penser que les usines et IoC/DI fonctionnent assez bien ensemble, et votre cas d'utilisation en est un très bon exemple.
Si vous pouvez déterminer au moment de l'exécution quelle instance MyImplClass
utiliser, vous pouvez répertorier toutes les implémentations en tant que beans dans votre contexte xml et @Autowire
un tableau de type MyInterface
pour obtenir tous les implémenteurs MyInterface
.
Compte tenu de ce qui suit dans le contexte XML:
<bean class="MyImplClass" p:somethingCaseSpecific="case1"/>
<bean class="MyImplClass" p:somethingCaseSpecific="case2"/>
Puis une décélération
@Autowire
MyInterface[] allInterfaceBeans;
allInterfaceBeans
contiendra les deux haricots définis ci-dessus.
Si vous souhaitez que la logique permettant de déterminer quelle implémentation utiliser soit effectuée au moment de l'injection, vous pouvez toujours @Autowire
une méthode de définition setAllInterfaceBeans(MyInterface[] allInterfaceBeans);
.
Tout d’abord, nous savons tous que par défaut, Spring Container créera un bean en mode singleton (si vous ne spécifiez pas explicitement la portée). Comme son nom l'indique, singleton garantit que chaque fois que vous appelez le haricot, il vous donnera la même instance. Néanmoins, il y a de légères différences entre le singleton au printemps et le singleton mentionné par le GoF. Au printemps, l'instance créée sera limitée au conteneur (et non à la machine virtuelle Java comme nous l'avons trouvé dans GoF).
De plus, au printemps, vous pouvez définir deux instances de bean différentes du même type, mais avec des noms différents. Il s'agira de deux instances différentes créées sur le segment de mémoire. Mais chaque fois que vous référencez un de ces beans par son nom (ref = dans une définition de bean ou getBean sur le appContext), vous obtenez le même objet à chaque fois. Cela est évidemment différent du modèle singleton actuel mais son concept est similaire.
En règle générale, l’utilisation d’un singleton dans une application multithread (Spring singleton ou singleton réel) a des implications. Tout état que vous conservez sur ces objets doit tenir compte du fait que plusieurs threads y accéderont. Généralement, tout état existant sera défini lors de l'instanciation via un argument setter ou constructor. Cette catégorie de haricots printaniers est logique pour les objets à vie longue, les objets thread-safe Si vous voulez quelque chose de spécifique à un fil et que vous souhaitez tout de même créer l'objet, la portée du prototype fonctionne.