web-dev-qa-db-fra.com

Obtenir une nouvelle instance d'un haricot de printemps

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

8
Mr T.

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();
}
13
Himanshu Ahire

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.

2
Gaetan

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();
}
2
Albin

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.

2

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);.

0
Ben Kern

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.

0
MCHAppy