DéveloppeurWeb.Com
    DéveloppeurWeb.Com
    • Agile Zone
    • AI Zone
    • Cloud Zone
    • Database Zone
    • DevOps Zone
    • Integration Zone
    • Web Dev Zone
    DéveloppeurWeb.Com
    Home»Java Zone»Injection de dépendance intelligente avec ressort – Génériques (partie 3/3)
    Java Zone

    Injection de dépendance intelligente avec ressort – Génériques (partie 3/3)

    novembre 9, 2021
    Injection de dépendance intelligente avec ressort - Génériques (partie 3/3)
    Share
    Facebook Twitter Pinterest Reddit WhatsApp Email

    Préface

    Le framework Spring est un framework puissant qui fournit un support de première classe pour l’injection de dépendance (DI). Cet article est le dernier de ma mini-série consacrée à l’injection de dépendances avec Spring Framework.

    Cette série est divisée en trois articles :

    Dans cet article, vous apprendrez :

    • Comment injecter un seul bean défini avec une classe générique
    • Comment injecter une collection ou une carte de beans définis avec une classe générique
    • Quelques conseils utiles et problèmes liés à l’injection de beans avec une classe générique

    Aperçu

    Dans mes précédents articles de cette série, j’ai revu les bases de la DI et l’injection par type assignable. Ici, nous mettons en lumière l’injection de beans définis par une classe générique. Pour ce sujet, nous avons besoin d’un autre ensemble de classes. Commençons par eux d’abord.

    Domaine de commande

    Dans le dernier article, nous utilisons un domaine de boisson. Ce domaine réutilise toutes les classes du domaine des boissons. Ce domaine est très simple et direct. La relation entre les classes et leur relation est décrite ci-dessous.

    La hiérarchie des classes du domaine des boissons

    Interface de commande de boissons

    L’élément racine dans le domaine de commande est un BeverageOrder interface avec une seule méthode appelée takeOrder. L’objectif de cette méthode est d’accepter un grain de boisson pour la commande et de renvoyer un message spécifique à la boisson manipulée.

    public interface BeverageOrder<T extends Beverage> {
    
    	String takeOrder(T beverage);
    	
    }

    Remarque : il s’agit d’un exemple très artificiel servant uniquement aux fins de cet article.

    Classe de commande de thé

    La première classe mettant en œuvre le BeverageOrder l’interface est la TeaOrder classe définie comme :

    @Component
    public class TeaOrder implements BeverageOrder<Tea> {
    
    	public String takeOrder(Tea beverage) {
    		return beverage.getName() + " order is taken.";
    	}
    	
    }

    Noter la TeaOrder la classe utilise le Tea classe (du domaine des boissons) pour accepter une commande.

    De plus, nous pouvons également définir des beans dans JavaConfig. Les sodaOrder bean est un exemple de cette approche.

    @SpringBootApplication
    public class WiringConfig {
    
    	@Bean
    	public BeverageOrder<Soda> sodaOrder() {
    		return beverage -> beverage.getName() + " is ready to be served.";
    	}
    
    }

    C’est tout pour notre domaine de commande. Ensuite, nous commençons par les exemples d’utilisation de ces classes pour l’injection.

    Injection de haricot unique

    Vous devriez être familiarisé avec l’injection d’un seul grain maintenant. Ici, nous modifions simplement nos précédents exemples d’injection pour utiliser les classes génériques.

    Remarque : vous pouvez consulter par exemple ces articles si vous devez actualiser le sujet générique :

    Injection par nom

    La façon courante d’injecter un grain (bien sûr, à l’exception de l’injection par type) est l’injection par le nom. Une telle injection avec des génériques se comporte de la même manière que précédemment (type simple ou type assignable). Lorsque vous avez plus de beans du même type (par exemple, plus de classes étendant le AbstractCarbonatedBeverage class) alors nous pouvons injecter l’instance de bean souhaitée comme ceci (ligne 5):

    @SpringBootTest(classes = WiringConfig.class)
    class OrderSingleWiringTest {
    
    	@Autowired
    	private BeverageOrder<? extends AbstractCarbonatedBeverage> sodaOrder;
    
    	@Test
    	void shouldWireBeanByName() {
    		assertThat(((BeverageOrder<Soda>) sodaOrder).takeOrder(new Soda())).isEqualTo("Soda is ready to be served.");
    	}
    
    }

    Comme d’habitude, nous pouvons vérifier l’injection en vérifiant le message retourné de la commande (ligne 9).

    Remarque : nous ne pouvons pas simplement injecter BeverageOrder<AbstractCarbonatedBeverage>. Vous trouverez plus d’informations sur ce problème à la fin (la partie finale de la note).

    Injection de grain primaire

    L’injection d’un haricot avec le @Primary l’annotation fonctionne exactement de la même manière que dans les cas précédents (type simple ou type assignable), car les génériques ne peuvent pas changer le principe du bean primaire. En d’autres termes, nous n’avons pas besoin de traiter les génériques d’une manière particulière. Vous pouvez trouver l’exemple ici.

    Injection par qualification

    Comme d’habitude, nous pouvons injecter n’importe quel bean par sa valeur de qualificateur (ligne 2).

    @Autowired
    @Qualifier("colaOrder")
    private BeverageOrder<? extends AbstractCarbonatedBeverage> beverageOrder;
    
    @Test
    void shouldWireBeanByQualifier() {
    	assertThat(((BeverageOrder<Cola>) beverageOrder).takeOrder(new Cola())).isEqualTo("Cola is temporarily not available.");
    }

    Pour que cela fonctionne, nous devons indiquer le type correct au compilateur. Soit lancer l’argument comme <T extends AbstractCarbonatedBeverage> ou transtypez le bean order vers le type de bean passé. Nous utilisons ici cette dernière option.

    Remarque : honnêtement, la classe générique n’a pas de valeur réelle ici. On peut facilement définir beverageOrder Comme BeverageOrder<?> ou BeverageOrder avec le même résultat. Le qualificatif gagne toujours une fois que le type est assignable.

    Injection de type générique

    Plus intéressante est l’injection par le type générique (ligne 2). Cela fonctionne de la même manière que le qualificatif, mais nous utilisons la classe générique au lieu du qualificatif.

    @Autowired
    private BeverageOrder<Tea> teaOrder;
    
    Test
    void shouldWireBeanByType() {
    	assertThat(teaOrder.takeOrder(new Tea())).isEqualTo("Tea order is taken.");
    }

    Collection d’injection de haricots

    L’injection d’une collection de beans définie avec la classe générique a moins d’options que l’injection d’une collection de beans assignables. L’injection par le qualificateur, le nom ou le bean primaire n’a pas de sens dans ce cas. On parle ici de plusieurs haricots. Par conséquent, nous ne pouvons pas appliquer de principes pour le haricot unique.

    De plus, l’injection par un type annoté ne fonctionne pas. Remarque : vous trouverez plus d’informations sur ce problème à la fin (la partie finale de la note). Par conséquent, nous pouvons injecter une collection de beans avec des génériques uniquement par leur type.

    Injecter toutes les commandes

    Afin d’injecter toutes les commandes disponibles (quelle que soit la boisson à laquelle elles sont destinées), nous devons les injecter comme Collection<BeverageOrder<?>> (ligne 10) ou toute autre classe ancêtre partagée.

    @SpringBootTest(classes = WiringConfig.class)
    class OrderCollectionWiringTest {
    	
    	/**
    	 * The exact name cannot be predicted as it's derived from lambda.
    	 */
    	private static final String SODA_ORDER = "WiringConfig$$Lambda$";
    
    	@Autowired
    	private Collection<BeverageOrder<?>> allOrders;
    
    	@Test
    	void shouldWireAllOrders() {
    		assertThat(allOrders).hasSize(4);
    		assertThat(allOrders).map(BeverageOrder::getClass).map(Class::getSimpleName)
    				.contains("BeerOrder", "ColaOrder", "TeaOrder")
    				.anyMatch(c -> c.startsWith(SODA_ORDER));
    	}
    
    }

    La vérification est effectuée comme d’habitude par le shouldWireAllOrders méthode d’essai. La partie intéressante ici est lorsque nous voulons vérifier le nom de classe du bean défini dans JavaConfig (sodaOrder). De telles classes n’ont pas de classe connue à l’avance. La classe est générée à l’exécution. On ne peut donc pas simplement vérifier sodaOrder classe comme dans les autres cas. Dans notre cas, nous utilisons une solution de contournement et vérifions simplement sodaOrder contre SODA_ORDER value (car c’est le seul bean défini comme ça).

    Remarque : nous pouvons modifier l’injection aussi pour taper Collection<BeverageOrder> ou Collection<BeverageOrder<? extends Beverage>>, mais jamais aussi Collection<BeverageOrder<Beverage>>. L’explication est mentionnée à la fin de cet article.

    Injecter des commandes spécifiques

    Comme nous l’avons vu plusieurs fois auparavant, nous pouvons réduire le nombre de grains injectés. Cette fois, nous voulons que toutes les commandes n’acceptent que les boissons gazeuses. Cela peut être réalisé en mettant une contrainte sur la classe générique. L’exemple ci-dessous utilise ? extends AbstractCarbonatedBeverage tapez pour injecter tous les grains de manutention pour une boisson gazeuse (ligne 2).

    @Autowired
    private BeverageOrder<? extends AbstractCarbonatedBeverage>[] carbonatedOrders;
    
    @Autowired
    private BeverageOrder<Beer> beerOrder;
    
    @Autowired
    private BeverageOrder<Cola> colaOrder;
    
    @Autowired
    private BeverageOrder<Soda> sodaOrder;
    
    @Test
    void shouldWireAllCarnonatedOrders() {
    	assertThat(carbonatedOrders)
    			.hasSize(3)
    			.contains(beerOrder, colaOrder, sodaOrder);
    }

    Remarque : la vérification est effectuée par rapport aux grains entiers cette fois. Par conséquent, nous devons injecter tous les beans attendus par leur type (voir lignes 4-11).

    Carte d’injection de haricots

    Le dernier exemple montre l’injection d’une carte de beans définie avec une classe générique. Cela fonctionne de la même manière que l’injection d’une collection de haricots. Un exemple de câblage de toutes les boissons gazeuses peut ressembler à ceci (ligne 2) :

    @Autowired
    private Map<String, BeverageOrder<? extends AbstractCarbonatedBeverage>> carbonatedOrders;
    
    @Test
    void shouldWireAllOrders() {
    	assertThat(carbonatedOrders)
    			.hasSize(3)
    			.containsKeys("beerOrder", "colaOrder", "sodaOrder");
    }

    Notes finales

    Maintenant, nous savons comment utiliser l’injection de dépendances avec les génériques dans Spring Framework. Cependant, il y a quelques problèmes dont nous devons être conscients.

    Utilisation du type brut

    Nous devons définir précisément le type injecté. Le but est de permettre à Spring de trouver la correspondance exacte de notre type et des beans existants dans son contexte. Pensez-vous que l’injection du BeverageOrder<AbstractCarbonatedBeverage> travaillerait?

    @Autowired
    private BeverageOrder<AbstractCarbonatedBeverage> sodaOrder;

    Malheureusement non, car le contenu Spring n’a pas de haricot du type AbstractCarbonatedBeverage. Il y a plusieurs descendants, mais aucun d’entre eux n’est défini à part ce type. Par conséquent, nous obtenons NoSuchBeanDefinitionException exception lorsque nous essayons d’injecter ce type.

    Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.github.aha.sat.core.wiring.order.BeverageOrder<com.github.aha.sat.core.wiring.beverage.AbstractCarbonatedBeverage>' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
    	at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1790)
    	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1346)
    	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1300)
    	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:657)
    	... 74 common frames omitted

    Fonderie

    Lorsque nous injectons des beans avec le type générique en utilisant le caractère générique (?), nous voulons évidemment les utiliser d’une manière générale. Sinon, nous pouvons leur injecter le type spécifique, n’est-ce pas ?

    Par exemple, lorsque nous utilisons le type étendu <? extends AbstractCarbonatedBeverage> alors nous voulons déclencher certaines de ses méthodes puis nous devons nous occuper du casting. C’est parce que le compilateur n’est pas capable de le faire tout seul. Pour que cela fonctionne, nous devons spécifier le type correct au compilateur. Soit lancer l’argument comme <T extends AbstractCarbonatedBeverage> (côté argument) ou transtypez le bean de traitement pour accepter le type de bean passé (côté fournisseur).

    Côté de l’argument

    Afin de mouler notre haricot au type désiré, nous pouvons introduire par exemple castType méthode (lignes 9-11) renvoyant le T extends AbstractCarbonatedBeverage taper. Cela rend le compilateur heureux.

    @Autowired
    private BeverageOrder<? extends AbstractCarbonatedBeverage> sodaOrder;
    
    @Test
    void shouldWireBeanByName() {
    	assertThat(sodaOrder.takeOrder(castType(new Soda()))).isEqualTo("Soda is ready to be served.");
    }
    
    <T extends AbstractCarbonatedBeverage> T castType(AbstractCarbonatedBeverage b) {
    	return (T)...
    Share. Facebook Twitter Pinterest LinkedIn WhatsApp Reddit Email
    Add A Comment

    Leave A Reply Cancel Reply

    Catégories

    • Politique de cookies
    • Politique de confidentialité
    • CONTACT
    • Politique du DMCA
    • CONDITIONS D’UTILISATION
    • Avertissement
    © 2023 DéveloppeurWeb.Com.

    Type above and press Enter to search. Press Esc to cancel.