Introduction
Après avoir passé 10 ans dans le monde du développement Java et avoir pratiqué Spring sous toutes ses coutures (IOC, DI, templates…), j’ai eu l’occasion de rentrer dans le monde JEE au sens puriste du terme. Car oui il faut le dire Spring ce n’est pas du JEE standard comme beaucoup le croit. Certes on peut s’étonner que le standard JEE CDI ne soit pas Spring, mais la faute à l’éditeur qui s’est positionné en marge des spécifications de CDI, pour des raisons technique et ou politique mais peu importe, ce n’est pas la statégie de l’éditeur qui nous interesse. Certes beaucoup de concept actuels sont arrivés et se sont démocratisés avec Spring. Ce dernier avait aussi la part belle car les serveurs JEE d’il y a quelques années n’offrait pas de solutions simple, rappelez-vous les EJB 1 et 2. Spring a donc offert des solutions plus simples et pus faciles à développer, à maintenir et à tester. Ce qui semblait quand même bizarre c’est que du coup on ne profitait plus vraiment des outils offerts par le container car Spring en tant que conteneur léger apportait lui-même tous les outils.
Le temps passant, les spécifications JEE avançant, les choses se sont améliorées et simplifiées du côté de JEE. Harmonisation et simplification des standards (CDI, JPA, EJB…). Mais entre-temps Spring s’est installé chez de très nombreux clients et la majorité des développeurs Java ont pris pour habitude/reflexe de chercher des solutions à leur problème dans l’écosysteme de Spring. Et il faut dire que cela fait très bien le job.
Il ne s’agit pas de dénigrer Spring et de dévaloriser ce qu’il apportait et apporte toujours, mais plutôt de mettre en lumière sur la stack standard JEE et plus particulièrement dans ce post sur CDI. Ce qui ressort de mes premières expériences, c’est plus de simplicité et plus de légèreté. Car à présent on embarque plus dans nos livrables toute une stack redondante avec celle du container, on évite les éventuels conflits. On profite des fonctionnalités sans surcouche. C’est évidemment une vision un peu puriste, mais pourquoi embarquer trop de chose inutile voir redondante? Allons au plus simple!
Autre point pertinent sur l’intérêt à porter à CDI est lié à la pérennité de l’outil sur le long terme, l’approche du standard s’appuie sur une grosse communauté car même si Spring est devenu un standard de facto en raison de sa forte utilisation actuelle, il se trouve que seuls quelques experts continuent de travailler sur le sujet cela semble moins pertinent que l’approche standard donc universelle de CDI. Voici un autre argument pour ne pas s’intéresser exclusivement à Spring
Donc je vais comparer Spring DI et CDI et essayer de faire ressortir la simplicité de mise en œuvre de CDI. Je m’appuierai sur Spring 4 et CDI 1.1, dont les implémentations de ce dernier sont soit Weld (Weblogic, Wildfly…) soit Openwebbeans(TomEE).
La configuration
Spring offre trois possibilités pour configurer un projet, la configuration xml, le scan des annotations et une configuration purement programmatique en Java. Nous retiendrons ici le scan des annotations afin de rester cohérent sur la comparaison de Spring et CDI.
Le fichier de configuration de Spring est le « Application-context.xml », sachant que ce fichier peut être renommé bien évidemment.
Ce fichier permet de définir quels packages devront être scannés afin de trouver les beans que Spring devra manager :
1 2 3 4 5 6 7 8 9 10 11 12 |
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="org.example"/> </beans> |
Le fichier de configuration de CDI est le fichier Beans.xml. Celui-ci doit être positionné dans le répertoire WEB-INF d’un WAR ou bien dans le répertoire META-INF d’un jar :
1 2 3 4 5 6 7 |
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd" bean-discovery-mode="all"> </beans> |
Il faut noter que ce fichier est obligatoire dans JEE6 pour activer le « CDI scanning ». Cela est corrigé dans JEE7 avec CDI 1.1, le scanning est activé par défaut donc hormis besoin de configuration particulier on peut ne pas avoir de beans.xml.
Une première différence entre Spring et CDI c’est que CDI scan tous les classes au moment du déploiement. Ainsi n’importe quel POJO, sans annotations ni définition dans un fichier xml sera managé par CDI. Et donc injectable. Avec CDI on ne se pose pas de question, tous les objets d’un projet sont pris en compte, c’est un détail mais cela peut éviter des erreurs de démarrage et une perte de temps à comprendre pourquoi certains beans ne seraient pas pris en compte. C’est un petit détail mais CDI est plus simple à installer.
Déclaration et injection des Beans
Avec Spring pour qu’un bean soit managé par le conteneur il faut que celui-ci soit annoté par :
- @Component
- @Controller
- @Service
- @Repository
A partir du moment où un bean est pris en compte par Spring celui-ci peut être injecté avec les annotations :
- @Autowired
- @Inject
Avec CDI c’est plus simple un bean est managé par défaut comme expliqué ci-dessus, pas besoin d’annotation particulière. Donc les POJOs le sont mais aussi les beans JPA, EJB, JSF…
Et donc ceux-ci seront simplement injectés avec @Inject. Easy !
Cycle de vie des beans
CDI comme Spring peuvent agir sur certaines phases du cycle vie des beans. Tel que ils peuvent tous les deux mettre en place des actions particulières avant l’instanciation (initialisation de propriétés…) et avant la destruction d’un bean (action sur d’autres beans…).
Ce sont les annotations @PostConstruct et @PreDestroy.
Scope
Le scope d’un bean permet de définir le cycle de vie dans le contexte applicatif. En somme on peut définir la durée de vie d’un bean, instanciation/destruction à chaque appel, durée de vie liée à l’application etc…
Dans Spring le scope d’un bean peut être défini avec l’annotation @Scope :
1 2 3 4 5 |
@Scope("prototype") @Repository public class ExampleImpl implements Example { // ... } |
Dans l’exemple ci-dessus le scope du bean est prototype, les scopes de Spring sont les suivants :
- Singleton (scope par défaut)
- Prototype (instanciation à chaque appel)
- Request (Web app)
- Session (Web app)
- Global session (Web app)
Dans CDI l’approche est similaire sauf que chaque scope correspond à une annotation particulière et non à une propriété de l’annotation Scope comme Spring. Les annotations sont :
- @Dependent (scope par défaut)
- @RequestScoped (web app)
- @ApplicationScoped (web app)
- @SessionScoped (web app)
- @Conversation (JSF)
Exemple:
1 2 3 4 5 6 7 8 |
@RequestScoped public class Example { @Inject Property property; ... } |
L’utilisation semble similaire, mais avec Spring l’approche est moins naturelle et des spécificités comme les @Controller qui sont des singletons (qu’on ne peut changer) existe.
Gestion de l’injection des différentes implémentations
Lorsqu’on définit un bean qui correspond à une interface, Spring ou CDI cherchera l’implémentation correspondante. Seulement dans certains cas plusieurs implémentations peuvent exister et cela pose un problème d’ambiguïté, Spring ou CDI ne sont pas en mesure de déterminer l’implémentation souhaitée pour l’instanciation du bean.
CDI introduit deux concepts permettant de gérer ce cas de figure, @Alternative et le @Qualifier. L’alternative permet d’activer ou de désactiver une implémentation. Et le Qualifier permet de choisir directement lors de l’injetion quelle implémentation on souhaite injecter.
@Alternative
Exemple :
Une implémentation dite Alternative de Example :
1 2 3 4 |
@Alternative public class TestExampleImpl implements Example { // ... } |
Une deuxième implémentation d’Example :
1 2 3 |
public class ExampleImpl implements Example { // ... } |
Lorsqu’on injecte :
1 2 |
@Inject Example example; |
Si aucune alternative n’est définie dans le fichier beans.xml, alors l’implémentation choisit sera ExampleImpl, par contre si on définit dans le beans.xml :
1 2 3 4 5 |
<beans ... > <alternatives> <class>org.example.TestExampleImpl</class> </alternatives> </beans> |
Alors l’injection s’appuiera sur TestExampleImpl. C’est très pratique si nous avons à gérer des implémentations différentes suivant les besoins (environnements, période etc…)
Avec Spring la gestion n’est pas aussi souple car on spécifie l’implémentation de référence avec une annotation
Exemple :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
@Component public class ExampleService { @Autowired private ExampleRepository exampleRepository; } @Component public class JdbcExampleRepository implements ExampleRepository { public JdbcExampleService(DataSource dataSource) { // ... } } @Primary @Component public class HibernateFooRepository implements ExampleRepository { public HibernateExampleService(SessionFactory sessionFactory) { // ... } } |
Dans ce cas c’est HibernateExampleRepository qui sera choisi pour l’injection.
La manipulation de plusieurs implémentations est moins souple avec Spring.
@Qualifier
Avec CDI on peut aussi choisir de manière fixe dans le code quel bean on injecte, pour cela on se sert de @Qualifier :
1 2 3 4 5 6 7 8 9 10 11 12 |
@Qualifier @Retention(RUNTIME) @Target({TYPE, METHOD, FIELD, PARAMETER}) public @interface ExampleQualifier {} @ExampleQualifier public class ExampleImpl implements Example { // ... } public class DefaultExampleImpl implements Example { // ... } |
Donc si à présent on veut injecter Example, pour une implémentation ExampleImpl il suffira d’écrire :
1 2 3 |
@Inject @ExampleQualifier Example example; |
Par contre si je veux injecter l’implémentation DefaultExample, je ferai :
1 2 3 |
@Inject @Default Example example; |
Note sur ma classe DefaultGreeting, si je ne précise pas de Qualifier c’est comme si ma classe était écrite :
1 2 3 4 |
@Default public class DefaultExampleImpl implements Example { // ... } |
CDI nous offre des possibilités de gérer les différentes implémentations de manière simple et plutôt élégante.
Bean Factory
Un dernier point pour finir ce post, les producteurs de Beans. Spring et CDI offre la possibilité d’instancier des beans de manière automatique lors d’injection en leur donnant une durée de vie précise.
Prenons une classe token, et l’instanciation du token doit correspondre à une session utilisateur :
1 2 3 4 5 |
Public class Token { // ... } |
Avec CDI je peux faire en sorte de produire un Token unique par session utilisateur de la manière suivante :
1 2 3 4 5 6 7 |
Public class TokenFactory{ @Produces @SessionScoped Public Token createToken(){ // ... } } |
Ainsi pour injecter ce Token dans un compte utilisateur par exemple il ne me reste plus qu’à écrire le code suivant :
1 2 3 4 5 6 7 8 9 |
Public class UserAccount{ @Inject Private Token token; // ... } |
Avec Spring ma Factory sera:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
@Configuration Public class TokenFactory{ @Bean @Scope(value=”session”) Public Token createToken(){ //... } } |
Conclusion
Les deux containers d’injection de dépendance sont évidemment très similaires, mais cela n’est pas surprenant étant donné que beaucoup de principe ont été explorés et inspirés par Spring. Mais on peut constater que CDI, ayant surement été plus réfléchie en amont au niveau de la spécification et s’appuyant sur les expériences développeur de Spring entre autre, se trouve être un peu plus simple d’utilisation. Sachant que dans ce post nous n’avons abordé que les cas simple d’utilisation mais on peut aller beaucoup plus loin. Ce qui pourra être l’objet d’un prochain post.