Le format XML
Au tout de début de Spring, les beans utilisés devaient être définis dans un fichier XML. Par exemple
<?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.xsd"> <import resource="example1bContext.xml"/> <bean name="authorService" class="com.javamind.springconfig.domain.AuthorService"/> <bean name="articleService" class="com.javamind.springconfig.domain.ArticleService"> <property name="authorService" ref="authorService"/> </bean> </beans>
Le format XML avait l'avantage d'être simple et d'être utilisé un peu partout. Mais le XML ne fait pas de vérification de type et nous avons au final des fichiers très verbeux.
Les namespaces
Pour être un peu moins verbeux Spring a mis en place des namespaces pour alléger les fichiers XML (util, aop, context...). Par exemple
<?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:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"> <util:map id="maMapExemple"> <entry key="key1" value="value1" /> <entry key="key2" value="value2" /> </util:map> </beans>
Ceci a permis de simplifier les déclarations des beans mais c'est un peu de la magie noire. Il est difficile de connaître toutes les possibilités sans lire la doc officielle et ce n'est pas extensible.
Les annotations
A partir de la version 2.5, Spring a introduit
- plusieurs annotations pour définir les beans Spring directement dans le code : @Autowired, @Component, @Service,
- la possibilité de scanner votre classpath pour rechercher ces classes annotées.
@Service public class ArticleService { @Autowired private AuthorService authorService; }
Cette injection via les annotations permet encore de simplifier la configuration. Mais nous ne pouvons pas démarrer notre application sans écrire du XML et la configuration se retrouve répartie entre des fichiers XML et du code Java.
Java config
Ces limites ont été corrigées à partir de la version 3.0 avec les annotations @Configuration et @Bean. Au fur et à mesure des évolutions du framework les versions suivantes n'ont fait qu'enrichir l'API de configuration
Java config
Ces limites ont été corrigées à partir de la version 3.0 avec les annotations @Configuration et @Bean. Au fur et à mesure des évolutions du framework les versions suivantes n'ont fait qu'enrichir l'API de configuration
Dans l'exemple ci dessous je reprends le cas exposé précédemment où un bean a besoin d'une Map.
@Configuration permet de dire que le bean sert à configurer Spring
@Import permet d'importer d'autres beans de configuration
@ComponentScan permet de définir les packages à scanner pour trouver les beans annotés
@Bean permet de définir un nouveau bean Spring
Avec l'API Java, nous bénéficions de tous les avantages liés à un langage de programmation (type checking, completion, héritage...). La manipulation des beans est plus simple car nous ne sommes pas toujours obligés de passer par le cycle de vie du container. Dans l'exemple ci dessus je n'ai pas besoin de créer un bean particulier pour créer ma Map.
Dépendances entre beans
Regardons maintenant comment déclarer un service ArticleService qui nécessite un AuthorService pour être créer. Si les deux beans sont définis dans la même classe de configuration vous pouvez définir
authorService() appelle la méthode présente dans le même bean qui instancie le bean AuthorService
Prenons maintenant le cas où le bean AuthorService est défini dans un autre fichier de configuration ou via une annotation de définition de bean comme @Service. Nous allons utiliser @Autowired (ou @Inject) pour injecter cette dépendance à notre bean de configuration qui est un bean Spring et qui accède au contexte général
Une autre syntaxe est aussi possible en spécifiant le bean à injecter dans les paramètres de la fonction.
Identification des beans
Vous pouvez bien sûr spécifier un nom à votre bean
La définition d'alias est aussi très simple
Charger la configuration Spring
Vous pouvez le faire à partir de votre classe principale
Mais aussi dans vos tests via l'annotation @ContextConfiguration si vous utilisez spring-test
Utilisation de paramètres
Nous avons souvent besoin d'utiliser des fichiers de paramètres dans une application et nous allons voir maintenant comment les utiliser dans un bean de configuration. Le fichier application.properties contient la clé suivante
blogname=javamind
L'importation du ou des fichiers se fait via l'annotation @PropertySource et Environnement qui propose des méthodes pour parcourir les propriétés définies au niveau du système ou dans le contexte Spring (les variables systèmes étant prioritaires).
Par exemple
Une méthode très utile permet de définir une valeur par défaut si une variable n'est pas trouvée.
String getProperty(String key, String defaultValue)
Mais encore...
Il y aurait encore beaucoup de choses à dire sur la configuration d'une application Spring en Java (transaction, AOP, profiles...), mais je le ferai au cas par cas dans d'autres articles. J'espère vous avoir donné une bonne vision des possibilités.
Alors que faire ?
Pour ma part j'ai tranché. Je suis un développeur et écrire des fichiers XML n'est pas mon fort. Je suis plus à l'aise lorsque je manipule un langage de programmation. Par conséquent j'essaye sur tous mes projets de me passer de la configuration XML.
Le passage d'un mode à un autre demande du temps mais personnellement je trouve qu'il y a un intérêt. Dans mon cas cette migration a permis d'unifier les manières de paramétrer nos applications et de proposer une convention avec toute une batterie de paramétrage par défaut.
Si vous voulez en savoir plus consulter la doc de Spring
Les exemples ayant permis d'illustrer cet article sont disponibles sous Github
@Configuration @Import(Example2Config.class) @ComponentScan("com.javamind.springconfig.domain") public class Example1Config { @Bean public AuthorService authorService(){ AuthorService service = new AuthorService(); Map<String, String> map = new HashMap<>(); map.put("key1", "value1"); map.put("key2", "value2"); service.setMaMapExemple(map); return service; } }
@Configuration permet de dire que le bean sert à configurer Spring
@Import permet d'importer d'autres beans de configuration
@ComponentScan permet de définir les packages à scanner pour trouver les beans annotés
@Bean permet de définir un nouveau bean Spring
Avec l'API Java, nous bénéficions de tous les avantages liés à un langage de programmation (type checking, completion, héritage...). La manipulation des beans est plus simple car nous ne sommes pas toujours obligés de passer par le cycle de vie du container. Dans l'exemple ci dessus je n'ai pas besoin de créer un bean particulier pour créer ma Map.
Dépendances entre beans
Regardons maintenant comment déclarer un service ArticleService qui nécessite un AuthorService pour être créer. Si les deux beans sont définis dans la même classe de configuration vous pouvez définir
@Bean public ArticleService articleService(){ return new ArticleService(authorService()); }
authorService() appelle la méthode présente dans le même bean qui instancie le bean AuthorService
Prenons maintenant le cas où le bean AuthorService est défini dans un autre fichier de configuration ou via une annotation de définition de bean comme @Service. Nous allons utiliser @Autowired (ou @Inject) pour injecter cette dépendance à notre bean de configuration qui est un bean Spring et qui accède au contexte général
@Autowired private AuthorService authorService; @Bean public ArticleService articleService(){ return new ArticleService(); }
Une autre syntaxe est aussi possible en spécifiant le bean à injecter dans les paramètres de la fonction.
@Bean public ArticleService articleService(AuthorService authorService){ return new ArticleService(authorService); }
Identification des beans
Vous pouvez bien sûr spécifier un nom à votre bean
@Bean(name = "articleService2") public ArticleService2 articleService2(AuthorService authorService){ return new ArticleService2(authorService); }
La définition d'alias est aussi très simple
@Bean(name = {"articleServName", "articleServAlias"}) public ArticleService articleService(AuthorService authorService){ return new ArticleService(authorService); }
Charger la configuration Spring
Vous pouvez le faire à partir de votre classe principale
public class Application { public static void main(String[] args) { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.register(Example1Config.class, Example2Config.class); ctx.refresh(); ArticleService articleService = (ArticleService2) ctx.getBean("articleServName"); } }
Mais aussi dans vos tests via l'annotation @ContextConfiguration si vous utilisez spring-test
@ContextConfiguration(classes = Example1Config.class) @RunWith(SpringJUnit4ClassRunner.class) public class Spring3Test { @Qualifier("articleServName") @Resource ArticleService name; }
Utilisation de paramètres
Nous avons souvent besoin d'utiliser des fichiers de paramètres dans une application et nous allons voir maintenant comment les utiliser dans un bean de configuration. Le fichier application.properties contient la clé suivante
blogname=javamind
L'importation du ou des fichiers se fait via l'annotation @PropertySource et Environnement qui propose des méthodes pour parcourir les propriétés définies au niveau du système ou dans le contexte Spring (les variables systèmes étant prioritaires).
Par exemple
@Resource private Environment env; @Bean(name = {"articleServName", "articleServAlias"}) public ArticleService2 articleService2(AuthorService authorService){ ArticleService2 service = new ArticleService2(authorService); service.setName(env.getProperty("blogname")); return service; }
Une méthode très utile permet de définir une valeur par défaut si une variable n'est pas trouvée.
String getProperty(String key, String defaultValue)
Mais encore...
Il y aurait encore beaucoup de choses à dire sur la configuration d'une application Spring en Java (transaction, AOP, profiles...), mais je le ferai au cas par cas dans d'autres articles. J'espère vous avoir donné une bonne vision des possibilités.
Alors que faire ?
Pour ma part j'ai tranché. Je suis un développeur et écrire des fichiers XML n'est pas mon fort. Je suis plus à l'aise lorsque je manipule un langage de programmation. Par conséquent j'essaye sur tous mes projets de me passer de la configuration XML.
Le passage d'un mode à un autre demande du temps mais personnellement je trouve qu'il y a un intérêt. Dans mon cas cette migration a permis d'unifier les manières de paramétrer nos applications et de proposer une convention avec toute une batterie de paramétrage par défaut.
Si vous voulez en savoir plus consulter la doc de Spring
Les exemples ayant permis d'illustrer cet article sont disponibles sous Github
Merci infiniment
RépondreSupprimerBonjour,
RépondreSupprimerMerci pour ces explications sur Spring.
J'ai essayé d'utiliser "AnnotationConfigApplicationContext" dans mon main, comme vous l'avez indiqué, mais à la compilation, j'ai un "java.lang.ClassNotFoundException: org.springframework.core.AliasRegistry"
Avez-vous déjà rencontré ce genre de problème ?
Merci
Carine
Quelle version de Spring utilisez vous? AnnotationConfigApplicationContext implémente AliasRegistry qui est dans Spring core
RépondreSupprimerMerci pour votre réponse.
SupprimerJ'ai ajouté Spring core 4.3.5 et maintenant mon erreur est devenue : "java.lang.NoClassDefFoundError: org/apache/commons/logging/LogFactory
at org.springframework.context.support.AbstractApplicationContext.(AbstractApplicationContext.java:161)
at org.springframework.context.support.GenericApplicationContext.(GenericApplicationContext.java:104)
at org.springframework.context.annotation.AnnotationConfigApplicationContext.(AnnotationConfigApplicationContext.java:60)
at Test.App.main(App.java:18)"
Carine
Spring a plusieurs dépendances (voir https://mvnrepository.com/artifact/org.springframework/spring-core/4.3.5.RELEASE) commons, aspectj, log4j... Il est préférable de passer par un Maven et un Gradle pour avoir une gestion automatiue et transitive de vos dépendances
SupprimerJ'utilise Maven et je me suis effectivement dit que je devais avoir un problème de dépendance. je suis en train de chercher d'où ça pourrait venir.
SupprimerMerci pour votre aide.