Implémentation de l'API Spring Framework à partir de zéro. Procédure pas à pas pour les débutants. Partie 1



Spring Framework est l'un des cadres de compréhension et d'apprentissage les plus complexes. La plupart des développeurs l'apprennent lentement, grâce à des tâches pratiques et à Google. Cette approche n'est pas efficace, car elle ne donne pas une image complète et en même temps est coûteuse.

Je voudrais vous proposer une approche fondamentalement nouvelle de l'étude du printemps. Il consiste dans le fait qu'une personne passe par une série de tutoriels spécialement préparés et met en œuvre indépendamment la fonction du ressort. La particularité de cette approche est qu'en plus d'une compréhension à 100% des aspects étudiés de Spring, elle donne également une forte augmentation de Java Core (Annotations, Réflexion, Fichiers, Génériques).

L'article vous offrira une expérience inoubliable et vous fera vous sentir comme un développeur Pivotal. Étape par étape, vous ferez haricoter vos cours et organiserez leur cycle de vie (le même que dans un vrai printemps). Les classes que vous implémenterez sont BeanFactory , Component , Service , BeanPostProcessor , BeanNameAware , BeanFactoryAware , InitializingBean , PostConstruct , PreDestroy , DisposableBean , ApplicationContext , ApplicationListener , ContextClosedEvent .

Un peu de moi


Je m'appelle Yaroslav et je suis développeur Java avec 4 ans d'expérience. En ce moment je travaille pour EPAM Systems (SPB), et je plonge profondément dans les technologies que nous utilisons. Très souvent, je dois faire face au printemps, et je vois en lui un terrain d'entente dans lequel vous pouvez grandir (Java tout le monde le sait si bien, et des outils et des technologies trop spécifiques peuvent aller et venir).

Il y a quelques mois, j'ai réussi la certification Spring Professional v5.0 (sans suivre de cours). Après cela, j'ai réfléchi à la façon d'enseigner à d'autres personnes qui sautaient. Malheureusement, il n'existe actuellement aucune méthodologie d'enseignement efficace. La plupart des développeurs ont une idée très superficielle du framework et de ses fonctionnalités. Le débogage des sources printanières est trop difficile et absolument inefficace du point de vue de la formation (j'aimais en quelque sorte cela). Faites 10 projets? Oui, quelque part, vous pouvez approfondir vos connaissances et acquérir beaucoup d'expérience pratique, mais une grande partie de ce qui est «sous le capot» ne s'ouvrira jamais devant vous. Lire Printemps en action? Cool, mais coûteux en efforts. Je l'ai travaillé à 40% (lors de la préparation à la certification), mais ce n'était pas facile.

La seule façon de comprendre quelque chose jusqu'au bout est de le développer vous-même. Récemment, j'ai eu l'idée que vous pouvez guider une personne à travers un tutoriel intéressant qui supervisera le développement de son cadre DI. Sa principale caractéristique sera que l'API coïncidera avec l'API étudiée. L'émerveillement de cette approche est qu'en plus d'une compréhension profonde (sans espaces) du printemps, une personne aura une énorme expérience dans Java Core. Franchement, j'ai moi-même appris beaucoup de nouvelles choses lors de la préparation de l'article, à la fois sur Spring et sur Java Core. Commençons à développer!

Projetez à partir de zéro


Donc, la première chose à faire est d'ouvrir votre IDE préféré et de créer un projet à partir de zéro. Nous ne connecterons aucun Maven ni aucune bibliothèque tierce. Nous ne connecterons même pas les dépendances Spring. Notre objectif est de développer une API qui ressemble le plus à l'API Spring et de l'implémenter nous-mêmes.

Dans un projet propre, créez 2 packages principaux. Le premier package est votre application ( com.kciray ) et la classe Main.java intérieur. Le deuxième package est org.springframework. Oui, nous dupliquerons la structure du package du ressort d'origine, le nom de ses classes et leurs méthodes. Il y a un effet tellement intéressant - lorsque vous créez quelque chose de vous-même, celui de vous-même commence à sembler simple et compréhensible. Ensuite, lorsque vous travaillez dans de grands projets, il vous semble que tout y est créé en fonction de votre pièce. Cette approche peut avoir un effet très positif sur la compréhension du système dans son ensemble, son amélioration, la correction de bogues, la résolution de problèmes, etc.

Si vous avez des problèmes, vous pouvez prendre un projet de travail ici .

Créer un conteneur


Pour commencer, définissez la tâche. Supposons que nous ayons 2 classes - ProductFacade et PromotionService . Imaginez maintenant que vous voulez connecter ces classes les unes aux autres, mais pour que les classes elles-mêmes ne se connaissent pas (Pattern DI). Nous avons besoin d'une classe distincte qui gérera toutes ces classes et déterminera les dépendances entre elles. Appelons cela un conteneur. Créons la classe Container ... Bien que non, attendez! Spring n'a pas de classe de conteneur unique. Nous avons de nombreuses implémentations de conteneurs, et toutes ces implémentations peuvent être divisées en 2 types - les usines bin et les contextes. La fabrique de bacs crée des beans et les relie entre eux (injection de dépendances, DI), et le contexte fait à peu près la même chose, en plus d'ajouter des fonctionnalités supplémentaires (par exemple, internationaliser les messages). Mais nous n'avons pas besoin de ces fonctions supplémentaires maintenant, nous allons donc travailler avec l'usine bin.

Créez une nouvelle classe BeanFactory et placez-la dans le package org.springframework.beans.factory . Laissez les Map<String, Object> singletons stockés dans cette classe, dans laquelle l' id bin est mappé au bin lui-même. Ajoutez-y la Object getBean(String beanName) , qui extrait les beans par identifiant.

 public class BeanFactory { private Map<String, Object> singletons = new HashMap(); public Object getBean(String beanName){ return singletons.get(beanName); } } 

Veuillez noter que BeanFactory et BeanFactory sont deux choses différentes. La première est l'usine de bacs (conteneur) et la seconde est l'usine de bacs, qui se trouve à l'intérieur du récipient et produit également des bacs. Usine à l'intérieur de l'usine. Si vous êtes confus entre ces définitions, vous vous souvenez peut-être qu'en anglais, le deuxième nom est le premier et le premier est quelque chose comme un adjectif. Dans Bean Factory, le mot principal est l'usine et dans Factory Bean , le bean.

Maintenant, créez les classes ProductService et PromotionsService . ProductService renverra le produit de la base de données, mais avant cela, vous devez vérifier si des remises (promotions) s'appliquent à ce produit. Dans le commerce électronique, le travail à prix réduit est souvent attribué à une classe de service distincte (et parfois à un service Web tiers).

 public class PromotionsService { } public class ProductService { private PromotionsService promotionsService; public PromotionsService getPromotionsService() { return promotionsService; } public void setPromotionsService(PromotionsService promotionsService) { this.promotionsService = promotionsService; } } 

Nous devons maintenant faire en sorte que notre conteneur ( BeanFactory ) détecte nos classes, les crée pour nous et les injecte l'une dans l'autre. Les opérations telles que le new ProductService() doivent être situées à l'intérieur du conteneur et effectuées pour le développeur. Utilisons l'approche la plus moderne (analyse de classe et annotations). Pour ce faire, nous devons créer une annotation @Component avec les @Component ( org.springframework.beans.factory.stereotype ).

 @Retention(RetentionPolicy.RUNTIME) public @interface Component { } 

Par défaut, les annotations ne sont pas chargées en mémoire pendant l'exécution du programme ( RetentionPolicy.CLASS ). Nous avons modifié ce comportement via une nouvelle stratégie de rétention ( RetentionPolicy.RUNTIME ).

Ajoutez maintenant @Component avant les classes ProductService et avant PromotionService .

 @Component public class ProductService { //... } @Component public class PromotionService { //... } 


Nous avons besoin de BeanFactory analyser notre package ( com.kciray ) et y trouver des classes qui sont annotées par @Component . Cette tâche est loin d'être anodine. Il n'y a pas de solution toute faite dans Java Core, et nous devrons faire nous-mêmes une béquille. Des milliers d'applications à ressort utilisent le balayage des composants à travers cette béquille. Vous avez appris la terrible vérité. Vous devrez extraire les noms de ClassLoader de ClassLoader et vérifier ClassLoader se terminent par ".class" ou non, puis créer leur nom complet et en extraire les objets de classe!

Je veux vous avertir immédiatement qu'il y aura de nombreuses exceptions vérifiées, alors soyez prêt à les envelopper. Mais d'abord, décidons de ce que nous voulons. Nous voulons ajouter une méthode spéciale à BeanFactory et l'appeler dans Main :

 //BeanFactory.java public class BeanFactory{ public void instantiate(String basePackage) { } } //Main.java BeanFactory beanFactory = new BeanFactory(); beanFactory.instantiate("com.kciray"); 

Ensuite, nous devons obtenir ClassLoader . Il est responsable du chargement des classes, et il est extrait tout simplement:

 ClassLoader classLoader = ClassLoader.getSystemClassLoader(); 

Vous avez probablement déjà remarqué que les packages sont séparés par un point et les fichiers par une barre oblique. Nous devons convertir le chemin du lot en chemin du dossier et obtenir quelque chose comme List<URL> (les chemins de votre système de fichiers où vous pouvez rechercher des fichiers de classe).

 String path = basePackage.replace('.', '/'); //"com.kciray" -> "com/kciray" Enumeration<URL> resources = classLoader.getResources(path); 

Alors attendez un instant! Enumeration<URL> n'est pas une List<URL> . De quoi s'agit-il? Oh, horreur, c'est l'ancien ancêtre d' Iterator , disponible depuis Java 1.0. C'est l'héritage auquel nous devons faire face. S'il est possible de parcourir Iterable aide de for (toutes les collections l'implémentent), alors dans le cas de l' Enumeration vous devrez faire un bypass de poignée, à travers while(resources.hasMoreElements()) et nextElement() . Et pourtant, il n'y a aucun moyen de supprimer des éléments de la collection. Seulement 1996, seulement hardcore. Oh oui, dans Java 9, ils ont ajouté la méthode Enumeration.asIterator() , afin que vous puissiez y travailler.

Allons plus loin. Nous devons extraire les dossiers et parcourir le contenu de chacun d'eux. Convertissez l'URL en fichier, puis obtenez son nom. Il convient de noter ici que nous n'analyserons pas les packages imbriqués afin de ne pas compliquer le code. Vous pouvez compliquer votre tâche et faire une récursion si vous le souhaitez.

 while (resources.hasMoreElements()) { URL resource = resources.nextElement(); File file = new File(resource.toURI()); for(File classFile : file.listFiles()){ String fileName = classFile.getName();//ProductService.class } } 

Ensuite, nous devons obtenir le nom du fichier sans l'extension. Dans la cour en 2018, Java a développé des E / S de fichiers (NIO 2) pendant de nombreuses années, mais ne peut toujours pas séparer l'extension du nom de fichier. Je dois créer mon propre vélo, car nous avons décidé de ne pas utiliser de bibliothèques tierces comme Apache Commons. Utilisons l'ancienne méthode grand-père lastIndexOf(".") :

 if(fileName.endsWith(".class")){ String className = fileName.substring(0, fileName.lastIndexOf(".")); } 

Ensuite, nous pouvons obtenir l'objet classe en utilisant le nom complet de la classe (pour cela, nous appelons la classe de la classe Class ):

 Class classObject = Class.forName(basePackage + "." + className); 

D'accord, maintenant nos cours sont entre nos mains. De plus, il ne reste plus qu'à mettre en évidence parmi eux ceux qui ont l'annotation @Component :

 if(classObject.isAnnotationPresent(Component.class)){ System.out.println("Component: " + classObject); } 

Exécutez et vérifiez. La console devrait ressembler à ceci:

 Component: class com.kciray.ProductService Component: class com.kciray.PromotionsService 

Maintenant, nous devons créer notre bean. Vous devez faire quelque chose comme new ProductService() , mais pour chaque bean, nous avons notre propre classe. La réflexion en Java nous fournit une solution universelle (le constructeur par défaut est appelé):

 Object instance = classObject.newInstance();//=new CustomClass() 

Ensuite, nous devons mettre ce bean dans les Map<String, Object> singletons . Pour ce faire, sélectionnez le nom du bean (son id). En Java, nous appelons des variables comme des classes (seule la première lettre est en minuscule). Cette approche peut également être appliquée aux beans, car Spring est un framework Java! Convertissez le nom du bac de sorte que la première lettre soit petite et ajoutez-le à la carte:

 String beanName = className.substring(0, 1).toLowerCase() + className.substring(1); singletons.put(beanName, instance); 

Assurez-vous maintenant que tout fonctionne. Le conteneur doit créer des beans et ils doivent être récupérés par leur nom. Veuillez noter que le nom de votre méthode classObject.newInstance(); et le nom de la méthode classObject.newInstance(); ont une racine commune. De plus, instantiate() fait partie du cycle de vie du bean. En Java, tout est interconnecté!

 //Main.java BeanFactory beanFactory = new BeanFactory(); beanFactory.instantiate("com.kciray"); ProductService productService = (ProductService) beanFactory.getBean("productService"); System.out.println(productService);//ProductService@612 


Essayez également d'implémenter l'annotation org.springframework.beans.factory.stereotype.Service . Il remplit exactement la même fonction que @Component , mais il est appelé différemment. Tout est dans le nom - vous démontrez que la classe est un service, pas seulement un composant. C'est quelque chose comme la frappe conceptuelle. Lors de la certification du printemps, il y avait une question «Quelles annotations sont stéréotypées?» (de ceux énumérés). " Les annotations stéréotypées sont donc celles qui se trouvent dans le package de stereotype .

Remplissez les propriétés


Regardez le schéma ci-dessous, il montre le début du cycle de vie du bean. Ce que nous avons fait auparavant est Instantiate (création de beans via newInstance() ). L'étape suivante est l'injection croisée de beans (injection de dépendance, c'est aussi l'inversion de contrôle (IoC)). Vous devez parcourir les propriétés des grains et comprendre quelles propriétés vous devez injecter. Si vous appelez productService.getPromotionsService() , vous obtiendrez null , car dépendance non encore ajoutée.



Tout d'abord, créez le package org.springframework.beans.factory.annotation et ajoutez-y l'annotation @Autowired . L'idée est de marquer les champs qui sont des dépendances avec cette annotation.

 @Retention(RetentionPolicy.RUNTIME) public @interface Autowired { } 

Ensuite, ajoutez-le à la propriété:

 @Component public class ProductService { @Autowired PromotionsService promotionsService; //... } 

Maintenant, nous devons apprendre à notre BeanFactory trouver ces annotations et à leur injecter des dépendances. Ajoutez une méthode distincte pour cela et appelez-la depuis Main :

 public class BeanFactory { //... public void populateProperties(){ System.out.println("==populateProperties=="); } } 

Ensuite, nous avons juste besoin de parcourir tous nos bacs dans la carte des singletons , et pour chaque bac de parcourir tous ses champs ( object.getClass().getDeclaredFields() renvoie tous les champs, y compris les champs privés). Et vérifiez si le champ a une annotation @Autowired :

 for (Object object : singletons.values()) { for (Field field : object.getClass().getDeclaredFields()) { if (field.isAnnotationPresent(Autowired.class)) { } } } 

Ensuite, nous devons parcourir tous les bacs une fois de plus et voir leur type - soudain, c'est le type que notre bac veut prendre pour lui-même. Oui, nous obtenons un cycle en trois dimensions!

 for (Object dependency : singletons.values()) { if (dependency.getClass().equals(field.getType())) { } } 

De plus, lorsque nous avons découvert la dépendance, nous devons l'injecter. La première chose à laquelle vous pourriez penser est d'écrire le champ promotionsService utilisant directement la réflexion. Mais le printemps ne fonctionne pas comme ça. Après tout, si le champ a un modificateur private , nous devrons d'abord le définir comme public , puis écrire notre valeur, puis le redéfinir sur private (pour maintenir l'intégrité). Cela ressemble à une grosse béquille. Au lieu d'une grande béquille, faisons une petite béquille (nous formerons le nom du setter et l'appellerons):

 String setterName = "set" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1);//setPromotionsService System.out.println("Setter name = " + setterName); Method setter = object.getClass().getMethod(setterName, dependency.getClass()); setter.invoke(object, dependency); 

Maintenant, exécutez votre projet et assurez-vous qu'en appelant productService.getPromotionsService() au lieu de null , notre bean est retourné.

Ce que nous avons implémenté, c'est l'injection par type. Il existe également une injection par nom (annotation javax.annotation.Resource ). Il diffère en ce qu'au lieu du type du champ, son nom sera extrait, et selon lui - la dépendance de la carte. Tout est similaire ici, même dans quelque chose de plus simple. Je vous recommande d'expérimenter et de créer votre propre bean, puis de l'injecter avec @Resource et d'étendre la méthode populateProperties() .

Nous soutenons les haricots qui connaissent leur nom




Il y a des moments où vous devez mettre son nom dans le bac. Un tel besoin ne se pose pas souvent, car les poubelles, par essence, ne devraient pas se connaître et qu'elles sont des poubelles. Dans les premières versions du printemps, on supposait que le bean était un POJO (Plain Old Java Objec, le bon vieil objet Java), et toute la configuration était rendue dans des fichiers XML et séparée de l'implémentation. Mais nous implémentons cette fonctionnalité, car l'injection de nom fait partie du cycle de vie du bac.

Comment savons-nous quel haricot veut savoir quel est son nom et ce qu'il ne veut pas? La première chose qui vous vient à l'esprit est de créer une nouvelle annotation de type @InjectName et de la sculpter en champs de type String. Mais cette solution sera trop générale et vous permet de vous tirer plusieurs fois dans le pied (placez cette annotation sur des champs de types inappropriés (pas String), ou essayez d'injecter un nom dans plusieurs champs de la même classe). Il existe une autre solution, plus précise - pour créer une interface spéciale avec une méthode de définition. Tous les bacs qui l'implémentent obtiennent leur nom. Créez la classe BeanNameAware dans le package org.springframework.beans.factory :

 public interface BeanNameAware { void setBeanName(String name); } 

Ensuite, laissez notre PromotionsService implémenter:

 @Component public class PromotionsService implements BeanNameAware { private String beanName; @Override public void setBeanName(String name) { beanName = name; } public String getBeanName() { return beanName; } } 

Et enfin, ajoutez une nouvelle méthode à l'usine de haricots. Tout est simple ici - nous passons par notre bin-singleton, vérifions si le bin implémente notre interface, et appelons le setter:

 public void injectBeanNames(){ for (String name : singletons.keySet()) { Object bean = singletons.get(name); if(bean instanceof BeanNameAware){ ((BeanNameAware) bean).setBeanName(name); } } } 

Exécutez et assurez-vous que tout fonctionne:

 BeanFactory beanFactory = new BeanFactory(); beanFactory.instantiate("com.kciray"); beanFactory.populateProperties(); beanFactory.injectBeanNames(); //... System.out.println("Bean name = " + promotionsService.getBeanName()); 

Il convient de noter qu'au printemps, il existe d'autres interfaces similaires. Je vous recommande d'implémenter vous- même l' interface BeanFactoryAware , qui permet aux beans de recevoir un lien vers la fabrique de beans. Il est mis en œuvre de manière similaire.

Initialiser les haricots




Imaginez que vous ayez une situation où vous devez exécuter du code après que les dépendances ont été injectées (les propriétés du bac sont définies). En termes simples, nous devons donner au bac la possibilité de s'initialiser. Alternativement, nous pouvons créer une interface InitializingBean et y mettre la signature de la void afterPropertiesSet() . L'implémentation de ce mécanisme est exactement la même que celle présentée pour l'interface BeanNameAware , donc la solution est sous le spoiler. Pratiquez et faites-le vous-même en une minute:

Solution d'initialisation du bean
 //InitializingBean.java package org.springframework.beans.factory; public interface InitializingBean { void afterPropertiesSet(); } //BeanFactory.java public void initializeBeans(){ for (Object bean : singletons.values()) { if(bean instanceof InitializingBean){ ((InitializingBean) bean).afterPropertiesSet(); } } } //Main.java beanFactory.initializeBeans(); 



Ajout de post-processeurs


Imaginez-vous à la place des premiers développeurs du printemps. Votre framework se développe et est très populaire auprès des développeurs, des lettres sont envoyées chaque jour par mail avec des demandes d'ajout de l'une ou l'autre fonctionnalité utile. Si pour chacune de ces fonctionnalités, vous ajoutez votre propre interface et la vérifiez dans le cycle de vie du bean, alors (le cycle de vie) sera obstrué par des informations inutiles. Au lieu de cela, nous pouvons créer une interface universelle qui vous permet d'ajouter de la logique (absolument aucune, qu'il s'agisse de vérifier l'annotation, de remplacer le bac par un autre bac, de définir des propriétés spéciales, etc.).

Réfléchissons à quoi sert cette interface. Il doit effectuer un post-traitement des beans, il peut donc être appelé BeanPostProcessor. Mais nous sommes confrontés à une question difficile - quand faut-il suivre la logique? Après tout, nous pouvons l'exécuter avant l'initialisation, mais nous pouvons l'exécuter après. Pour certaines tâches, la première option est meilleure, pour d'autres - la seconde ... Comment être?

Nous pouvons activer les deux options à la fois. Laissez un post-processeur transporter deux logiques, deux méthodes. L'un est exécuté avant l'initialisation (avant la méthode afterPropertiesSet() ) et l'autre après. Réfléchissons maintenant aux méthodes elles-mêmes - quels paramètres devraient-elles avoir? De toute évidence, le Object bean lui-même ( Object bean ) doit être là. Pour plus de commodité, en plus du bac, vous pouvez transmettre le nom de ce bac. Vous vous souvenez que le bac lui-même ne connaît pas son nom. Et nous ne voulons pas forcer tous les beans à implémenter l'interface BeanNameAware. Mais, au niveau du post-processeur, le nom du bean peut être très utile. Par conséquent, nous l'ajoutons comme deuxième paramètre.

Et que doit renvoyer la méthode lors du post-traitement du bean? Faisons-le retourner le bac lui-même. Cela nous donne une super flexibilité, car au lieu d'un bac, vous pouvez glisser un objet proxy qui encapsule ses appels (et ajoute de la sécurité). Ou vous pouvez renvoyer complètement un autre objet en recréant le bac. Les développeurs bénéficient d'une très grande liberté d'action. Voici la version finale de l'interface conçue:

 package org.springframework.beans.factory.config; public interface BeanPostProcessor { Object postProcessBeforeInitialization(Object bean, String beanName); Object postProcessAfterInitialization(Object bean, String beanName); } 

Ensuite, nous devons ajouter une liste de processeurs simples à notre fabrique de haricots et la possibilité d'en ajouter de nouveaux. Oui, il s'agit d'une liste de tableaux régulière.

 //BeanFactory.java private List<BeanPostProcessor> postProcessors = new ArrayList<>(); public void addPostProcessor(BeanPostProcessor postProcessor){ postProcessors.add(postProcessor); } 

Modifiez maintenant la méthode initializeBeans pour qu'elle prenne en compte les post-processeurs:

 public void initializeBeans() { for (String name : singletons.keySet()) { Object bean = singletons.get(name); for (BeanPostProcessor postProcessor : postProcessors) { postProcessor.postProcessBeforeInitialization(bean, name); } if (bean instanceof InitializingBean) { ((InitializingBean) bean).afterPropertiesSet(); } for (BeanPostProcessor postProcessor : postProcessors) { postProcessor.postProcessAfterInitialization(bean, name); } } } 

Créons un petit post-processeur qui trace simplement les appels à la console et l'ajoutons à notre fabrique de bean:

 public class CustomPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) { System.out.println("---CustomPostProcessor Before " + beanName); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) { System.out.println("---CustomPostProcessor After " + beanName); return bean; } } 

 //Main.java BeanFactory beanFactory = new BeanFactory(); beanFactory.addPostProcessor(new CustomPostProcessor()); 


Maintenant, lancez-vous et assurez-vous que tout fonctionne. En tant que tâche de formation, créez un post-processeur qui fournira l'annotation @PostConstruct (javax.annotation.PostConstruct) . Il fournit un autre moyen d'initialiser (enraciné en Java, pas au printemps). Son essence est que vous placez l'annotation sur une méthode, et cette méthode sera appelée AVANT l'initialisation du ressort standard (InitializingBean).

Assurez-vous de créer toutes les annotations et les packages (même javax.annotation) manuellement, ne connectez pas les dépendances! Cela vous aidera à voir la différence entre le noyau du ressort et ses extensions (support javax), et à vous en souvenir. Cela gardera un style à l'avenir.

Vous serez intéressé par le fait que dans un vrai printemps l'annotation @PostConstructest implémentée de cette manière, via le post-processeur CommonAnnotationBeanPostProcessor. Mais n'y jetez pas un œil, écrivez votre implémentation.

Enfin, je vous recommande d'ajouter une méthode void close()à la classe BeanFactoryet d'élaborer deux autres mécanismes. La première est une annotation @PreDestroy (javax.annotation.PreDestroy), destinée aux méthodes qui doivent être appelées lorsque le conteneur est fermé. La seconde est l'interface org.springframework.beans.factory.DisposableBeanqui contient la méthode void destroy(). Tous les bacs exécutant cette interface auront la capacité de se détruire (libérer des ressources, par exemple).

@PreDestroy + DisposableBean
 //DisposableBean.java package org.springframework.beans.factory; public interface DisposableBean { void destroy(); } //PreDestroy.java package javax.annotation; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.RUNTIME) public @interface PreDestroy { } //DisposableBean.java public void close() { for (Object bean : singletons.values()) { for (Method method : bean.getClass().getMethods()) { if (method.isAnnotationPresent(PreDestroy.class)) { try { method.invoke(bean); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } } if (bean instanceof DisposableBean) { ((DisposableBean) bean).destroy(); } } } 



Cycle de vie complet du bean


Nous avons donc mis en œuvre le cycle de vie complet du bac, dans sa forme moderne. J'espère que cette approche vous aidera à vous en souvenir.

Notre contexte préféré


Les programmeurs utilisent très souvent le terme contexte, mais tout le monde ne comprend pas ce qu'il signifie vraiment. Maintenant, nous allons tout mettre en ordre. Comme je l'ai noté au début de l'article, le contexte est la mise en œuvre du conteneur, ainsi que BeanFactory. Mais, en plus des fonctions de base (DI), il ajoute encore quelques fonctionnalités intéressantes. L'une de ces fonctionnalités est l'envoi et le traitement d'événements entre les bacs.

L'article s'est avéré trop volumineux et le contenu a commencé à être coupé, j'ai donc mis les informations de contexte sous le spoiler.

Nous réalisons le contexte
. org.springframework.context , ApplicationContext . BeanFactory . , close() .

 public class ApplicationContext { private BeanFactory beanFactory = new BeanFactory(); public ApplicationContext(String basePackage) throws ReflectiveOperationException{ System.out.println("******Context is under construction******"); beanFactory.instantiate(basePackage); beanFactory.populateProperties(); beanFactory.injectBeanNames(); beanFactory.initializeBeans(); } public void close(){ beanFactory.close(); } } 


Main , , :

 ApplicationContext applicationContext = new ApplicationContext("com.kciray"); applicationContext.close(); 

, . close() , « » - . , :

 package org.springframework.context.event; public class ContextClosedEvent { } 

ApplicationListener , . , ( ApplicationListener<E> ). , Java-, . , , :

 package org.springframework.context; public interface ApplicationListener<E>{ void onApplicationEvent(E event); } 

ApplicationContext . close() , , . ApplicationListener<ContextClosedEvent> , onApplicationEvent(ContextClosedEvent) . , ?

 public void close(){ beanFactory.close(); for(Object bean : beanFactory.getSingletons().values()) { if (bean instanceof ApplicationListener) { } } } 

. . bean instanceof ApplicationListener<ContextClosedEvent> . Java. (type erasure) , <T> <Object>. , ? , ApplicationListener<ContextClosedEvent> , ?

, , . , , , , :

 for (Type type: bean.getClass().getGenericInterfaces()){ if(type instanceof ParameterizedType){ ParameterizedType parameterizedType = (ParameterizedType) type; } } 

, , , — . , :

 Type firstParameter = parameterizedType.getActualTypeArguments()[0]; if(firstParameter.equals(ContextClosedEvent.class)){ Method method = bean.getClass().getMethod("onApplicationEvent", ContextClosedEvent.class); method.invoke(bean, new ContextClosedEvent()); } 

ApplicationListener:

 @Service public class PromotionsService implements BeanNameAware, ApplicationListener<ContextClosedEvent> { //... @Override public void onApplicationEvent(ContextClosedEvent event) { System.out.println(">> ContextClosed EVENT"); } } 

, Main , , :

 //Main.java void testContext() throws ReflectiveOperationException{ ApplicationContext applicationContext = new ApplicationContext("com.kciray"); applicationContext.close(); } 


Conclusion


Au départ, j'avais prévu cet article pour Baeldung en anglais, mais j'ai ensuite pensé que le public de l'Habré pouvait évaluer positivement cette approche de la formation. Si vous avez aimé mes idées, assurez-vous de soutenir l'article. Si elle obtient une note de plus de 30, je promets de continuer. Lors de la rédaction de l'article, j'ai essayé de montrer exactement les connaissances de Spring Core, qui est le plus souvent utilisé, et également basé sur le Guide d'étude de certification Core Spring 5.0 . À l'avenir, à l'aide de ces didacticiels, vous pourrez couvrir l'intégralité de la certification et rendre le printemps plus accessible aux développeurs Java.

Mise à jour 05/10/2018


Des lettres me viennent constamment avec des questions "et quand la suite, on l'attend." Mais il n'y a pas de temps du tout, et d'autres projets personnels sont une priorité. Cependant, si l'un d'entre vous a vraiment aimé l'idée, vous pouvez étudier la section étroite du ressort et écrire un article de suite. Si vous n'avez pas de compte habr, je peux publier un article depuis mon compte ou vous aider à obtenir une invitation.

Distribution des sujets:
Spring Container - [nom d'utilisateur]
Spring AOP - [nom d'utilisateur]
Spring Web - [nom d'utilisateur]
Spring Cloud - [nom d'utilisateur]

Source: https://habr.com/ru/post/fr419679/


All Articles