En utilisant Java Config de Spring, j'ai besoin d'acquérir / d'instancier un bean à portée prototype avec des arguments de constructeur qui ne peuvent être obtenus qu'au moment de l'exécution. Considérez l'exemple de code suivant (simplifié par souci de concision):
@Autowired
private ApplicationContext appCtx;
public void onRequest(Request request) {
//request is already validated
String name = request.getParameter("name");
Thing thing = appCtx.getBean(Thing.class, name);
//System.out.println(thing.getName()); //prints name
}
où la classe Thing est définie comme suit:
public class Thing {
private final String name;
@Autowired
private SomeComponent someComponent;
@Autowired
private AnotherComponent anotherComponent;
public Thing(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
Avis name
est final
: il ne peut être fourni par un constructeur, et garantit immuabilité. Les autres dépendances sont des dépendances spécifiques à l'implémentation de la Thing
classe et ne doivent pas être connues (étroitement liées à) l'implémentation du gestionnaire de requêtes.
Ce code fonctionne parfaitement avec la configuration Spring XML, par exemple:
<bean id="thing", class="com.whatever.Thing" scope="prototype">
<!-- other post-instantiation properties omitted -->
</bean>
Comment obtenir la même chose avec la configuration Java? Ce qui suit ne fonctionne pas avec Spring 3.x:
@Bean
@Scope("prototype")
public Thing thing(String name) {
return new Thing(name);
}
Maintenant, je pourrais créer une usine, par exemple:
public interface ThingFactory {
public Thing createThing(String name);
}
Mais cela va à l' encontre de l'intérêt d'utiliser Spring pour remplacer le modèle de conception ServiceLocator et Factory , ce qui serait idéal pour ce cas d'utilisation.
Si Spring Java Config pouvait faire cela, je pourrais éviter:
- définition d'une interface Factory
- définition d'une implémentation Factory
- rédaction de tests pour l'implémentation Factory
C'est une tonne de travail (relativement parlant) pour quelque chose d'aussi trivial que Spring prend déjà en charge via la configuration XML.
Thing
implémentation est en fait plus complexe et a des dépendances sur d'autres beans (je les ai juste omis par souci de concision). En tant que tel, je ne veux pas que l'implémentation du gestionnaire de requêtes les sache, car cela couplerait étroitement le gestionnaire aux API / beans dont il n'a pas besoin. Je mettrai à jour la question pour refléter votre (excellente) question.@Qualifier
paramètres à un setter avec@Autowired
sur le setter lui-même.@Bean
fonctionne. La@Bean
méthode est appelée avec les arguments appropriés que vous avez passésgetBean(..)
.Réponses:
Dans une
@Configuration
classe, une@Bean
méthode comme celle-ciest utilisé pour enregistrer une définition de bean et fournir la fabrique pour créer le bean . Le bean qu'il définit n'est instancié que sur demande en utilisant des arguments qui sont déterminés soit directement, soit en analysant cela
ApplicationContext
.Dans le cas d'un
prototype
bean, un nouvel objet est créé à chaque fois et donc la@Bean
méthode correspondante est également exécutée.Vous pouvez récupérer un bean de la
ApplicationContext
via saBeanFactory#getBean(String name, Object... args)
méthode qui indiqueEn d'autres termes, pour ce
prototype
bean de portée, vous fournissez les arguments qui seront utilisés, non pas dans le constructeur de la classe du bean, mais dans l'@Bean
appel de la méthode.C'est au moins vrai pour les versions Spring 4+.
la source
@Bean
méthode à l'appel manuel. Si jamais@Autowire Thing
la@Bean
méthode sera appelée, elle mourra probablement de ne pas pouvoir injecter le paramètre. Même chose si vous@Autowire List<Thing>
. J'ai trouvé cela un peu fragile.@Autowire
?Avec Spring> 4.0 et Java 8, vous pouvez le faire avec plus de sécurité de type:
Usage:
Vous pouvez désormais récupérer votre bean au moment de l'exécution. C'est un modèle d'usine bien sûr, mais vous pouvez gagner du temps sur l'écriture d'une classe spécifique comme
ThingFactory
(cependant vous devrez écrire custom@FunctionalInterface
pour passer plus de deux paramètres).la source
fabric
place defactory
, mon mauvais :)Provider
ou à anObjectFactory
, ou est-ce que je me trompe? Et dans mon exemple, vous pouvez lui passer un paramètre de chaîne (ou n'importe quel paramètre)@Bean
etScope
annotations sur laThing thing
méthode. De plus cette méthode peut être rendue privée pour se cacher et ne laisser que l'usine.Depuis le printemps 4.3, il existe une nouvelle façon de le faire, qui a été cousue pour ce problème.
ObjectProvider - Il vous permet simplement de l'ajouter en tant que dépendance à votre bean à portée Prototype "argumenté" et de l'instancier en utilisant l'argument.
Voici un exemple simple de son utilisation:
Cela affichera bien sûr la chaîne Hello lors de l'appel de usePrototype.
la source
MISE À JOUR par commentaire
Premièrement, je ne sais pas pourquoi vous dites "cela ne fonctionne pas" pour quelque chose qui fonctionne très bien dans Spring 3.x. Je soupçonne que quelque chose ne va pas dans votre configuration quelque part.
Cela marche:
- Fichier de configuration:
- Fichier de test à exécuter:
En utilisant Spring 3.2.8 et Java 7, donne cette sortie:
Ainsi, le Bean «Singleton» est demandé deux fois. Cependant, comme on pouvait s'y attendre, Spring ne le crée qu'une seule fois. La deuxième fois, il voit qu'il a ce bean et renvoie simplement l'objet existant. Le constructeur (méthode @Bean) n'est pas invoqué une seconde fois. Par respect pour cela, lorsque le Bean 'Prototype' est demandé deux fois au même objet de contexte, nous voyons que la référence change dans la sortie ET que le constructeur (méthode @Bean) EST appelé deux fois.
Alors la question est de savoir comment injecter un singleton dans un prototype. La classe de configuration ci-dessus montre comment faire cela aussi! Vous devez transmettre toutes ces références au constructeur. Cela permettra à la classe créée d'être un pur POJO et de rendre les objets de référence contenus immuables comme ils devraient l'être. Ainsi, le service de transfert pourrait ressembler à quelque chose comme:
Si vous écrivez des tests unitaires, vous serez très heureux d'avoir créé les classes sans tous les @Autowired. Si vous avez besoin de composants auto-câblés, conservez-les en local dans les fichiers de configuration java.
Cela appellera la méthode ci-dessous dans BeanFactory. Notez dans la description comment cela est destiné à votre cas d'utilisation exact.
la source
Vous pouvez obtenir un effet similaire simplement en utilisant une classe interne :
la source
dans votre fichier XML de beans, utilisez l'attribut scope = "prototype"
la source
Réponse tardive avec une approche légèrement différente. C'est un suivi de cette question récente qui renvoie cette question elle-même.
Oui, comme cela a été dit, vous pouvez déclarer le bean prototype qui accepte un paramètre dans une
@Configuration
classe qui permet de créer un nouveau bean à chaque injection.Cela fera de cette
@Configuration
classe une usine et pour ne pas donner trop de responsabilités à cette usine, cela ne devrait pas inclure d'autres grains.Mais vous pouvez également injecter ce bean de configuration pour créer
Thing
s:Il est à la fois sûr de type et concis.
la source
@Configuration
modification de ce mécanisme.BeanFactory#getBean()
. Mais c'est bien pire en termes de couplage puisque c'est une usine qui permet d'obtenir / instancier n'importe quel bean de l'application et pas seulement celui dont le bean actuel a besoin. Avec une telle utilisation vous pouvez mélanger les responsabilités de votre classe très facilement puisque les dépendances qu'elle peut extraire sont illimitées, ce qui n'est vraiment pas conseillé mais cas exceptionnel.