Bonjour! Alors que l'administration JavaRush travaille à de nouveaux niveaux, je souhaite lancer une série d'articles de formation sur Spring Framework. Oui, je sais qu'il existe déjà beaucoup de matériel sur ce sujet sur Internet, mais, comme le montre la pratique, ils sont tous au niveau Hello World. Je veux parler non pas de la façon de placer correctement les annotations, mais de la façon dont tout cela fonctionne « sous le capot ». L'article est destiné à ceux qui ont déjà travaillé avec ce framework d'une manière ou d'une autre et qui connaissent les concepts de base.
Initialisation du contexte.
Alors commençons par les bases. À mon avis, l’un des points les plus importants est de comprendre comment le contexte est configuré et comment les beans sont initialisés. Comme vous le savez, avant que Spring ne commence à fonctionner, il doit être configuré. À l'époque antédiluvienne, cela se faisait à l'aide de fichiers XML (sur certains projets, principalement les plus anciens, ils continuent de le faire aujourd'hui). Voici un petit exemple d'un tel fichier de configuration :<?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-3.0.xsd">
<bean id="helloWorld" class="ru.javarush.HelloWorld">
<property name="message" value="Hello World!"/>
</bean>
</beans>
En gros, cela suffit pour créer quelques contrôleurs et lancer une startup (qui ne décollera pas). Mais comment cette configuration fera-t-elle fonctionner Spring ? Et c'est ici que les choses deviennent intéressantes. Pour que notre configuration soit comprise par Spring, il existe un fichier XmlBeanDefinitionReader
. BeanDefinition
Il s'agit d'un composant interne de Spring qui analyse (analyse) XML et crée des fichiers basés sur ce que nous avons écrit ici . BeanDefinition
est un objet qui stocke des informations sur le bean. Cela inclut : à partir de quelle classe il (le bean) doit être créé ; portée; si l'initialisation paresseuse est installée ; Est-il nécessaire d'initialiser avant ce bean une autre propriété et d'autres propriétés décrites en XML. Tous les reçus BeanDefinition
sont ajoutés à HashMap
, dans lequel l'identifiant est le nom du bean (défini par vous ou attribué par Spring) et BeanDefinition
l'objet lui-même. Une fois que tout BeanDefinition
est créé, un nouveau héros apparaît sur scène - BeanFactory
. Cet objet parcourt HashMap’e
s BeanDefinition
, crée des beans basés sur eux et les place dans un conteneur IoC. Il y a une nuance ici, en fait, au démarrage de l'application, le conteneur IoC contiendra des beans qui ont une portée Singleton (définie par défaut), tandis que les autres sont créés lorsqu'ils sont nécessaires (prototype, requête, session). Et maintenant une petite digression, faisons connaissance avec un autre personnage.
Découvrez BeanPostProcessor. (BPP)
Le fait est qu’un bean n’est pas nécessairement une classe de logique métier pour votre application. Un bean est également appelé bean d'infrastructure. En bref, un bean d'infrastructure est une classe qui configure vos beans de logique métier (oui, trop de beans). Je vais vous en dire plus ci-dessous, mais pour que ce soit un peu plus clair sur ce que BPP configure exactement, je vais donner un exemple. Est-ce que tout le monde connaît le résumé@Autowired
? Vous êtes donc AutowiredAnnotationBeanPostProcessor
responsable de vous assurer que tous vos cours sont intégrés les uns aux autres.
Revenons à BeanFactory
Connaissant maintenant BPP, vous devez préciser que lors d'une itération surHashMap
les , BeanDefinition
tous les ' sont d'abord créés et placés séparément (pas dans le conteneur IoC) BeanPostProcessor
. Après cela, les beans réguliers de notre logique métier sont créés, placés dans un conteneur IoC, et leur configuration commence à l'aide de BPP différés séparément. Et voilà comment ça se passe, chaque BPP a 2 méthodes :
postProcessorBeforeInitialization(Object bean, String beanName);
postProcessorAfterInitialization(Object bean, String beanName);
Parcourt nos bacs deux fois. La première fois que la méthode est appelée postProcessorBeforeInitialization
, et la deuxième fois, la méthode est appelée postProcessorAfterInitialization
. La question s'est sûrement posée de savoir pourquoi deux méthodes sont nécessaires, permettez-moi de vous expliquer. Le fait est que pour traiter certaines annotations (comme @Transactional
, par exemple), notre bean est remplacé par une classe proxy. Pour comprendre pourquoi cela se fait, vous devez savoir comment cela fonctionne @Transactional
, et voici comment cela fonctionne. Vous devez ajouter quelques lignes de code supplémentaires à la méthode marquée de cette annotation à la volée. Comment faire? C'est vrai, en créant une classe proxy, à l'intérieur de laquelle le code nécessaire sera ajouté à la méthode requise. Imaginez maintenant cette situation, nous avons une classe :
class A {
@Autowired
private SomeClass someClass;
@Transactional
public void method() {
// модификатор доступа обязательно public
}
}
Cette classe a 2 annotations @Autowired
et @Transactional
. Les deux annotations sont traitées par des BPP différents. Si cela fonctionne en premier AutowiredBPP
, alors tout ira bien, mais sinon, nous rencontrerons ce problème. Le fait est que lors de la création d’une classe proxy, toutes les méta-informations sont perdues. En d'autres termes, il n'y aura aucune information sur l'annotation @Autowired
dans la classe proxy, et donc AutowiredBPP
cela ne fonctionnera pas, ce qui signifie que notre champ someClass
aura la valeur null
, ce qui conduira très probablement à un NPE. Il faut également savoir qu'entre les appels de méthode, la -method postProcessorBeforeInitialization
est postProcessorAfterInitialization
appelée init
, s'il y en a une. Il s'agit essentiellement du deuxième constructeur, mais la différence est qu'à ce moment, toutes nos dépendances sont déjà intégrées dans la classe et nous pouvons y accéder à partir init
de la méthode -. Donc, encore une fois l'algorithme d'initialisation du contexte :
XmlBeanDefinitionReader
analyse notre fichier de configuration XML.- Crée
BeanDefinition
les et les met dansHashMap
. - Vient
BeanFactory
et à partir de celaHashMap
additionne séparément tous lesBeanPostProcessor
'. - Crée
BeanDefinition
des beans à partir de et les place dans un conteneur IoC. - Ici les BPP viennent configurer ces beans en utilisant 2 méthodes.
- Prêt.
GO TO FULL VERSION