Spring propose depuis longtemps plusieurs mécanismes permettant d’enrichir le comportement du conteneur lors de la création ou la destruction des beans :
- des interfaces, configurations et annotations permettant de notifier le bean lors de sa construction/destruction
- *des interfaces Aware permettant d’injecter automatiquement certaines dépendances particulières
- une interface (FactoryBean) permettant d’avoir entièrement la main sur la création et la destruction d’un objet (nous en reparlerons plus en détail la prochaine fois).
Un certain nombre d’exemples de config XML fonctionnent grâce à ces mécanismes. De nombreux frameworks tiers, dans le cadre de leur intégration avec Spring, les utilisent aussi.
****Lorsqu’on utilise JavaConfig, il faut garder en tête ces mécanismes pour adapter correctement les anciennes configurations XML encore beaucoup présentes dans les différentes documentations. En effet, si JavaConfig continue de les gérer, ils ont un impact sur la façon de définir ces configurations…
**Callback sur le cycle de vie des beans **
A l’origine Spring proposait deux méthodes pour notifier un bean lors de sa construction et lors de se destruction :
- implémenter l’interface InitializingBean (méthode afterPropertiesSet() ) ou l’interface DisposableBean (méthode destroy()) dans le bean (ce qui entraine une dépendance sur Spring)
- ou indiquer à Spring une init-method et une destroy-method dans la configuration XML du bean (la documentation Spring elle-même encourageant cette méthode afin de ne pas coupler les beans applicatifs à Spring)
Dans les deux cas, Spring appelle la méthode d’initialisation après l’instanciation du bean et l’injection de ces dépendances ; et appelle la méthode de destruction avant de détruire définitivement le bean.
Spring a ensuite ajouté le support des annotations Java @PostConstruct et @PreDestroy qui joue le même rôle que les interfaces Spring mais de façon standard.
JavaConfig supporte les interfaces InitializingBean et DisposableBean ainsi que les deux annotations standard mentionnées : si l’instance du bean renvoyée par une méthode annotée @Bean utilise ces mécanismes, ils seront correctement mis en oeuvre (attention toutefois, seul le bean renvoyé par la méthode @Bean est géré de cette façon ! Voir ci-dessous : la disparition des “inner bean”). Notons qu’il s’agit bien de la classe de l’objet renvoyé et non le type de retour de la méthode @Bean qui est pris en compte par Spring. Dans l’exemple suivant, la méthode myBean renvoie une instance de l’interface MyBeanInterface (qui n’étend ni InitializingBean ni DisposableBean) mais Spring attend d’obtenir la vraie classe de l’objet (MyBeanImplementation) pour prendre en compte les interfaces (ou les annotations)
## Le cadeau (empoisonné) de @Bean : la destroy method inferréeJavaConfig et @Bean viennent avec un cadeau bonus :
- si le bean renvoyé définit une méthode “public void close()” ou “public void shutdown()”, celle-ci est automatiquement prise en compte par Spring comme méthode de destruction du bean (ce point est documenté dans la javadoc de l’attribut destroyMethod de @Bean).
C’est sympa de la part de Spring, mais à mon avis ici, ils ont dépassé les limites entre les comportements par défaut bien choisis et l’introduction d’une part de “magie” difficile à maîtriser pour l’utilisateur.
Cela m’a récemment valu un bug où je ne voulais justement pas que la méthode close() de mon bean soit appelé : j’avais donc en toute connaissance de cause omis de configurer la destroyMethod sur l’annotation @Bean. Dans mon cas, il fallait malheureusement explicitement renseigner cet attribut avec une valeur vide pour empêcher cet appel…
*Les interfaces Aware
Certains beans ont besoin d’accéder à certains objets particuliers de l’environnement d’exécution (Spring, conteneur web …) pour fonctionner. Il s’agit la plupart du temps de beans spéciaux, souvent fournis par le framework spring lui-même, utilisés pour interagir intimement avec le container Spring.
Afin de rester modulaire et extensible, Spring propose un certains nombres d’interface *Aware, permettant de flagguer ces beans. Lorsqu’un bean implémente l’une de ces interfaces, Spring le détecte et injecte automatiquement les objets nécessaires dans les beans en question.
Un exemple est ServletContextAware issu du module spring-web :
On pourrait penser qu’elle peut être transposée en JavaConfig de cette façon : Mais ce n’est pas du tout le cas ici, car l’objet LazyInitTargetSourceCreator n’est alors pas correctement initialisé… Et le pire ici, c’est que cela ne génère pas d’erreur, le LazyInitTargetSourceCreator ne fait juste pas son travail, et les beans Lazy ne sont pas proxyfiés…Le problème est dû au fait que LazyInitTargetSourceCreator implémente l’interface BeanFactoryAware (on notera qu’elle implémente aussi DisposableBean) ce qui lui permet normalement de recevoir une référence au BeanFactory nécessaire à son bon fonctionnement. Dans la configuration XML, il est configuré via un “inner-bean” (un bean anonyme) qui reste malgré tout visible par Spring et bénéficie donc de l’injection du BeanFactory via BeanFactoryAware. En JavaConfig, le LazyInitTargetSourceCreator doit avoir sa propre méthode @Bean s’il veut recevoir le BeanFactory (et accessoirement s’il veut être détruit correctement à la fin du contexte via l’implémentation de DisposableBean) :