Quelles opportunités Spring offre-t-il pour personnaliser son comportement?

Bonjour à tous. En contact avec Vladislav Rodin. Actuellement, je dirige le cours d'architecte à charge élevée d'OTUS et j'enseigne également des cours sur l'architecture logicielle.

En plus d'enseigner, j'écris également du matériel pour le blog OTUS sur le Haber et je veux coïncider avec l'article d'aujourd'hui pour lancer le cours "Developer on the Spring Framework" , qui est maintenant ouvert au recrutement.




introduction


Du point de vue du lecteur, le code de l'application utilisant Spring semble assez simple: certains beans sont déclarés, les classes sont marquées d'annotations, puis les beans sont injectés si nécessaire, tout fonctionne bien. Mais un lecteur curieux a une question: «Comment ça marche? Que ce passe-t-il?". Dans cet article, nous allons essayer de répondre à cette question, mais pas pour satisfaire une curiosité oiseuse.

Le framework Spring est connu pour être suffisamment flexible et offre des options pour personnaliser le comportement du framework. Spring regorge également d'un certain nombre de règles plutôt intéressantes pour l'application de certaines annotations (par exemple, Transactional). Afin de comprendre la signification de ces règles, de pouvoir les dériver, ainsi que de comprendre quoi et comment configurer dans Spring, vous devez comprendre plusieurs principes de fonctionnement de ce qui se trouve sous le capot de Spring. Comme vous le savez, la connaissance de plusieurs principes dispense de la connaissance de nombreux faits. Je vous suggère de vous familiariser avec ces principes ci-dessous si vous ne les connaissez certainement pas.

Configurations de lecture


Au tout début, vous devez analyser les configurations présentes dans votre application. Puisqu'il existe plusieurs types de configurations (configurations xml, groovy, java, configuration basée sur des annotations), différentes méthodes sont utilisées pour les lire. D'une manière ou d'une autre, une carte de la forme Map <String, BeanDefinition> est collectée, dans laquelle les noms des beans sont affectés à leurs définitions de bean. Les objets de la classe BeanDefinition sont des méta-informations sur les beans et contiennent l'id-id du bean, son nom, sa classe, les méthodes destroy et init.

Exemples de classes impliquées dans ce processus: GroovyBeanDefinitionReader, XmlBeanDefinitionReader, AnnotatedBeanDefinitionReader, implémentant l'interface BeanDefinitionReader .

Configuration de Beandefinitions


Donc, nous avons des descriptions de beans, mais il n'y a pas de beans eux-mêmes, ils n'ont pas encore été créés. Avant de créer des beans, Spring offre la possibilité de personnaliser les BeanDefinitions résultantes. À ces fins, l'interface BeanFactoryPostProcessor est utilisée . Cela ressemble à ceci:

public interface BeanFactoryPostProcessor {
    void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}

Le paramètre de méthode de cette interface permet d'utiliser sa propre méthode getBeanDefinitionNames pour obtenir les noms par lesquels vous pouvez obtenir BeanDefinitions à partir de la carte et les modifier.

Pourquoi cela pourrait-il être nécessaire? Supposons que certains beans nécessitent des détails pour se connecter à n'importe quel système externe, comme une base de données. Nous voulons que les beans soient déjà créés avec les détails, mais les détails eux-mêmes sont stockés dans le fichier de propriétés. Nous pouvons appliquer l'un des BeanFactoryPostProcessors standard - PropertySourcesPlaceholderConfigurer, qui remplacera la propriété name dans BeanDefinition par la valeur réelle stockée dans le fichier de propriétés. Autrement dit, il remplacera en fait Value ("user") par Value ("root") dans BeanDefinion. Pour que cela fonctionne, le PropertySourcesPlaceholderConfigurer, bien sûr, doit être connecté. Mais cela ne se limite pas à cela, vous pouvez enregistrer votre BeanFactoryPostProcessor, dans lequel implémenter toute logique dont vous avez besoin pour traiter BeanDefinitions.

Création de haricots


À ce stade, nous avons une carte qui a les noms des beans localisés par les clés et les BeanDefinitions configurés par les valeurs. Vous devez maintenant créer ces beans. C'est ce que fait BeanFactory . Mais ici, vous pouvez ajouter une personnalisation en écrivant et en enregistrant votre FactoryBean . FactoryBean est une interface du formulaire:

public interface FactoryBean {
    T getObject() throws Exception;
    Class<?> getObjectType();
    boolean isSingleton();
}

Ainsi, un BeanFactory crée un bean lui-même s'il n'y a pas de FactoryBean correspondant à la classe de bean, ou demande au FactoryBean de créer ce bean. Il y a une petite nuance: si la portée du bean est monotone, alors le bean est créé à ce stade, si prototype, alors chaque fois que ce bean est nécessaire, il sera demandé à la BeanFactory.

En conséquence, nous obtenons à nouveau une carte, mais elle est déjà un peu différente: les clés contiennent les noms des beans et les valeurs des beans eux-mêmes. Mais cela n'est vrai que pour les singletones.

Configuration de Beans


Vient maintenant l'étape la plus intéressante. Nous avons une carte contenant les beans créés, mais ces beans n'ont pas encore été configurés. Autrement dit, nous n'avons pas traité les annotations qui définissent l'état du bean: Autowired, Value. Nous n'avons pas non plus traité d'annotations qui modifient le comportement du bean: Transactional, Async. Les BeanPostProcessors , qui sont à nouveau des implémentations de l'interface correspondante, nous permettent de résoudre ce problème :

public interface BeanPostProcessor {
    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
    Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}

Nous voyons 2 méthodes avec des noms effrayants mais complets. Les deux méthodes prennent un bean en entrée, à partir duquel vous pouvez demander de quelle classe il s'agit, puis utiliser l'API Reflection pour traiter les annotations. Retourne les méthodes de bean, éventuellement remplacées par un proxy.

Pour chaque bean avant de le placer dans le contexte, les événements suivants se produisent: les méthodes postProcessBeforeInitialization sont déclenchées pour tous les BeanPostProcessors, puis la méthode init est déclenchée, puis les méthodes postProcessAfterInitialization sont également déclenchées pour tous les BeanPostProcessors.

Ces deux méthodes ont une sémantique différente: postProcessBeforeInitialization traite les annotations d'état, postProcessAfterInitialization traite le comportement, car le proxy est utilisé pour traiter le comportement, ce qui peut entraîner la perte d'annotations. C'est pourquoi le comportement change en dernier lieu.

Où est la personnalisation? Nous pouvons écrire notre annotation, BeanPostProcessor pour cela, et Spring la traitera. Cependant, pour que le BeanPostProcessor fonctionne, il doit également être enregistré en tant que bean.

Par exemple, pour incorporer un nombre aléatoire dans le champ, nous créons une annotation InjectRandomInt (accrochée aux champs), créons et enregistrons un InjectRandomIntBeanPostProcessor, dans la première méthode dont nous traitons l'annotation créée, et dans la deuxième méthode, nous renvoyons simplement le bean entrant.

Pour profiler les beans, créez une annotation Profile qui diffuse des méthodes, créez et enregistrez un ProfileBeanPostProcessor, dans la première méthode dont nous renvoyons le bean entrant, et dans la deuxième méthode, nous renvoyons un proxy qui enveloppe l'appel à la méthode d'origine avec la journalisation de l'heure et de l'exécution.



En savoir plus sur le cours



All Articles