Niveau : | Supérieur |
Le coeur de Spring est composé de Spring Core : un conteneur qui implémente le motif de conception IoC (Inversion of Control). Ce conteneur prend en charge la création, la gestion du cycle de vie et les dépendances des objets qu'il gère.
La définition de ces objets est faite dans la déclaration du contexte de Spring dans un fichier de configuration XML ou partiellement réalisée en utilisant des annotations.
Pour mettre en oeuvre certaines fonctionnalités, Spring a recourt à la programmation orientée aspect (AOP : Aspect Oriented Programming).
Ce chapitre contient plusieurs sections :
La mise en oeuvre de Spring repose sur le motif de conception IoC et sur la programmation orientée aspects (AOP) pour développer des applications reposant sur des beans qui sont de simples POJO.
L'IoC est un principe abstrait qui définit un motif de conception dans lequel le flux de contrôle d'un système est inversé par rapport à un développement procédural.
L'injection de dépendances est un motif de conception qui propose un mécanisme pour fournir à un composant les dépendances dont il a besoin. C'est une forme particulière d'inversion de contrôle.
L'injection de dépendances permet à une application de déléguer la gestion du cycle de vie de ses dépendances et leurs injections à une autre entité. L'application ne crée pas directement les instances des objets dont elle a besoin : les dépendances d'un objet ne sont pas gérées par l'objet lui-même mais sont gérées et injectées par une entité externe à l'objet.
Dans le cas classique, l'objet invoque le constructeur de ses dépendances pour obtenir les instances requises en utilisant l'opérateur new. Cela induit un couplage fort entre l'objet et sa dépendance. Pour réduire ce couplage, il est possible par exemple de définir une interface et d'utiliser une fabrique pour créer une instance mais cela nécessite beaucoup de code.
Avec le motif de conception IoC, la gestion des objets est confiée à un objet dédié. Celui-ci se charge de créer les instances requises et de les fournir par injection. Cette injection peut concrètement se faire de plusieurs manières :
Les classes et les interfaces de base du conteneur Spring sont contenues dans les packages org.springframework.beans et org.springframework.context.
Une configuration permet de définir les objets qui sont gérés par le conteneur, généralement sous la forme d'un fichier XML : les informations fournies permettent au conteneur d'instancier et d'initialiser l'objet et ses dépendances.
L'AOP est utilisée par Spring pour fournir des fonctionnalités transverses (par exemple la gestion des transactions) ou spécifiques (par exemple l'injection de dépendances dans un bean non géré par Spring) de manières déclaratives dans la configuration XML ou grâce à des annotations.
Spring permet aussi d'utiliser l'AOP pour des besoins propres de l'application.
Historiquement, Spring propose l'utilisation de proxys avec Spring AOP : les proxys sont générés dynamiquement grâce à la bibliothèque CGLib. Ils interceptent dynamiquement les appels des méthodes et invoquent le code des greffons des aspects.
Depuis sa version 2.5, il est préférable d'utiliser AspectJ car il propose plus de fonctionnalités. Spring AOP ne peut être utilisé que sur des beans qui sont gérés par Spring.
Le terme bean est utilisé par Spring comme il aurait pu utiliser les termes component ou object. Le conteneur Spring est capable de gérer des JavaBeans mais aussi la plupart des classes.
Un bean doit obligatoirement être défini avec une classe dont le nom pleinement qualifié est précisé et possédant au moins un identifiant unique dans le conteneur. Les autres identifiants sont considérés comme des alias.
Chaque bean possède un nom qui sera déterminé par le conteneur si aucun n'est explicitement fourni. Le nom devrait suivre, par convention, la convention de nommage standard des instances (débuter par une minuscule puis utiliser la convention camel case).
Un bean peut avoir dans sa définition des informations relatives à sa configuration ce qui permet de préciser comment le bean sera géré dans le conteneur (singleton ou prototype, mode d'instanciation, paramètres du constructeur, valeurs des propriétés, dépendances, mode d'autowiring, méthodes à invoquer lors de l'initialisation ou de la destruction, ...)
Le conteneur se charge de créer les instances, de les configurer et de gérer les objets requis par l'application. Comme ces objets interagissent, généralement un objet possède des dépendances qui vont aussi être gérées par le conteneur.
Le conteneur peut donc être vu comme une fabrique évoluée qui gère le cycle de vie des objets et la gestion de leurs dépendances.
L'interface org.springframework.beans.factory.BeanFactory définit les fonctionnalités de base du conteneur.
Lors de sa création, le conteneur va vérifier la configuration qui lui est fournie pour par exemple détecter les références à des objets non définis, les dépendances circulaires, ...
Le conteneur essaie selon la configuration de créer les instances le plus tardivement possible : cependant, les singletons sont par défaut créés au lancement du conteneur.
Spring est capable de gérer n'importe quel bean. Seules quelques contraintes doivent être respectées pour permettre à Spring de réaliser l'injection de dépendances s'il y en a.
Chaque bean géré par Spring possède un ou plusieurs identifiants uniques.
La ou les instances d'un bean sont créées par Spring selon la configuration soit sous la forme d'un singleton (instance unique) ou de prototype (une instance est créée à chaque demande au conteneur)
L'interface BeanFactory décrit les fonctionnalités de base du conteneur.
Elle contient plusieurs méthodes :
Méthode |
Rôle |
boolean containsBean(String) |
Indiquer si le conteneur est capable de fournir une instance du bean dont le nom est fourni en paramètre |
Object getBean(String) |
Obtenir une instance d'un bean géré par le conteneur |
T getBean(String, Class<T>) |
Depuis Spring 3.0, obtenir une instance d'un bean géré par le conteneur. Le type du bean est défini grâce aux generics |
Class< ?> getType(String) |
Obtenir le type du bean dont le nom est fourni en paramètre |
boolean isSingleton(String) |
Indiquer si un bean dont le nom est fourni en paramètre est un singleton (true) ou un prototype (false) |
String[] getAliases(String) |
Obtenir les alias du bean dont le nom est fourni en paramètre |
Map<String, T> getBeansOfType(Class<T>) |
Depuis Spring 3.0, retourner une collection de type map qui contient les beans gérés par le conteneur dont le type est fourni en paramètre |
Spring fournit plusieurs implémentations de cette interface. Une implémentation de BeanFactory peut être vue comme une fabrique capable de gérer un ensemble de beans et leurs dépendances.
Une implémentation de BeanFactory permet de stocker des informations sur les JavaBeans qu'elle va gérer. La BeanFactory fournit une instance et gère le cycle de vie du Bean tout en stockant en interne les informations de la définition d'un bean dans une instance de type BeanDefinition.
La classe BeanDefinition encapsule toutes les informations utiles au BeanFactory pour créer une instance. Ces informations concernent la classe elle-même mais aussi ses dépendances.
L'interface ApplicationContext hérite des interfaces BeanFactory et MessageSource.
Elle ajoute des fonctionnalités permettant notamment l'accès aux ressources et une gestion d'événements.
La gestion d'événements est assurée grâce à la classe ApplicationEvent et à l'interface EventListener.
La mise en oeuvre des événements se fait en utilisant le motif de conception observateur. Lorsqu'un événement est émis, il est propagé à chaque bean qui implémente l'interface ApplicationListener. L'implémentation des méthodes de cette interface doit se charger des traitements à exécuter pour l'événement.
Spring propose plusieurs événements en standard :
Il est possible de définir ses propres événements qui doivent implémenter l'interface ApplicationEvent.
La méthode publishEvent() de l'interface ApplicationContext permet de demander l'émission synchrone d'un événement.
La méthode registerShutdownHook() permet de demander au conteneur d'être informé d'un arrêt de la JVM pour lui permettre d'exécuter correctement les traitements liés à la destruction des beans afin de gérer correctement leur cycle de vie. Ceci est particulièrement utile dans des applications non web.
Spring propose plusieurs classes qui encapsulent le conteneur selon la façon dont on souhaite le configurer et les fonctionnalités requises.
Il existe deux interfaces principales pour le conteneur selon les fonctionnalités souhaitées :
Pour ces deux interfaces, Spring propose plusieurs solutions pour charger son fichier de configuration.
L'utilisation d'une implémentation de BeanFactory est pratique pour une application exécutée dans un environnement possédant des ressources limitées.
La classe XmlBeanFactory permet d'instancier le contexte et de charger sa configuration à partir du contenu d'un fichier de configuration XML.
Exemple : |
BeanFactory factory = new XmlBeanFactory(new FileSystemResource("c:/beans.xml"));
Il est aussi possible d'utiliser la classe ClassPathResource pour rechercher le fichier de configuration dans le classpath de l'application.
Exemple : |
XMLBeanFactory factory = new XMLBeanFactory(new ClassPathResource ("appContext.xml"));
Spring propose trois implémentations de l'interface ApplicationContext :
Exemple : |
ApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"context.xml"});
Exemple : |
ApplicationContext context = new FileSystemXmlApplicationContext("appContext.xml");
La création d'un ApplicationContext pour une application web peut se faire de deux façons selon la version des spécifications de l'API Servlet implémentée par le conteneur.
Pour un conteneur implémentant les spécifications Servlet 2.3 et inférieure, il faut utiliser la servlet ContextLoaderServlet.
Exemple : |
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/servicesContext.xml /WEB-INF/daoContext.xml
/WEB-INF/applicationContext.xml</param-value>
</context-param>
<servlet>
<servlet-name>context</servlet-name>
<servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
La servlet ContextLoaderServlet, démarrée au lancement de la webapp, va charger le fichier de configuration. Par défaut, ce fichier doit se nommer WEB-INF/applicationContext.xml
Pour un conteneur implémentant les spécifications Servlet 2.4 et supérieures : il faut utiliser un listener de type ContextLoaderListener.
Exemple : |
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/daoContext.xml /WEB-INF/servicesContext.xml
/WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
La classe org.springframework.web.context.ContextLoaderListener est utilisée pour charger le ou les fichiers de configuration de l'application web.
Le paramètre contextConfigLocation permet de préciser le ou les fichiers de configuration à utiliser. Plusieurs fichiers peuvent être précisés en utilisant un espace, une virgule ou un point virgule comme séparateur. Il est aussi possible d'utiliser des motifs par exemple /WEB-INF/*Context.xml pour désigner tous les fichiers finissant par Context.xml dans le répertoire WEB-INF ou /WEB-INF/**/*Context.xml pour désigner tous les fichiers finissant par Context.xml dans le répertoire WEB-INF et tous ses sous-répertoires.
Si le paramètre contextConfigLocation n'est pas défini, le listener ou la servlet utilisent par défaut le fichier /WEB-INF/applicationContext.xml.
La méthode getBean() de l'interface BeanFactory qui attend en paramètre l'identifiant de l'objet permet d'obtenir une instance de cet objet selon la configuration fournie au conteneur.
Exemple : |
package com.jmdoudoux.test.spring;
import org.apache.log4j.Logger;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.jmdoudoux.test.spring.service.PersonneService;
public class MonApp {
private static Logger LOGGER = Logger.getLogger(MonApp.class);
public static void main(final String[] args) throws Exception {
LOGGER.info("Debut de l'application");
ClassPathXmlApplicationContext appContext = new ClassPathXmlApplicationContext(
new String[] { "appContext.xml" });
PersonneService personneService = (PersonneService) appContext
.getBean("personneService");
// ...
LOGGER.info("Fin de l'application");
}
}
Spring 3.0 utilise les generics de Java 5. Une surcharge de la méthode getBean() possède donc la signature :
<T> T getBean(Class<T> classType) throws BeansException;
Il n'est plus nécessaire de faire un cast lors de l'invocation de la méthode getBean()
MonBean monBean = context.getBean(MonBean.class) ;
Une autre surcharge permet de préciser l'identifiant du bean et sa classe.
<T> T getBean(String name, Class<T> classType) throws BeansException;
Ce fichier est un document au format XML dont le tag racine est le tag <beans> et qui respecte un schéma fourni par Spring.
Chaque objet géré par le conteneur est défini dans un tag fils <bean>.
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-2.5.xsd">
<bean id="..." class="...">
<!-- configuration et description des dependances -->
</bean>
<bean id="...' class="...">
<!-- configuration et description des dependances -->
</bean>
</beans>
Remarque : tous les objets utilisés dans une application ne doivent pas être gérés par le conteneur. Les objets gérés sont les principaux objets de chaque couche (contrôleurs, services, DAO, ...) ainsi que des composants techniques configurables (fabriques, connections à des ressources, ...).
Le nombre d'objets gérés pouvant être important selon la taille de l'application, il est possible de repartir la configuration dans plusieurs fichiers de configuration.
Il y a deux façons de préciser tous les fichiers de configuration au conteneur
1) Pour les préciser au constructeur qui va instancier le conteneur, utiliser le tag <import> dans le fichier de configuration principal pour chaque fichier à inclure
Exemple : |
<beans>
<import resource="services.xml"/>
<import resource="resources/persistance.xml"/>
<bean id="..." class="..."/>
</beans>
Le chemin du fichier précisé dans l'attribut resource peut être :
Le chemin complet du fichier peut être précisé en utilisant :
2) Il est possible d'utiliser le contenu d'une variable d'environnement de la JVM dans le chemin pour ne pas le gérer en dur : le marqueur ${nom_de_la_variable} sera remplacé par la valeur de la variable dont le nom est nom_de_la_variable.
La séparation de la configuration dans plusieurs fichiers est particulièrement utile si la taille du fichier de configuration devient importante.
Pour utiliser plusieurs fichiers de configuration, il y a plusieurs solutions :
Chaque objet géré par le conteneur est défini dans un tag fils <bean>.
La définition d'un objet doit contenir au minimum un identifiant et le nom pleinement qualifié de sa classe. Il peut aussi contenir : une portée (scope), les arguments à fournir au constructeur, les valeurs de propriétés, des méthodes d'initialisation et de destruction, des paramètres sur le mode de gestion du cycle de vie de l'objet, ...
Chaque objet géré par le conteneur doit avoir au moins un identifiant unique qui permet d'y faire référence notamment dans les dépendances.
Cet identifiant peut être fourni grâce à l'attribut id. Si aucun identifiant n'est défini pour un objet, le conteneur va lui en assigner un par défaut. Par convention, l'identifiant d'un objet commence par une minuscule.
Exemple : |
<bean id="monBean" class="com.jmdoudoux.test.spring.MonBean" />
L'attribut name permet de fournir plusieurs identifiants pour un objet, chacun étant séparé par une virgule ou un point virgule.
L'attribut class permet de préciser le nom pleinement qualifié de la classe qui sera utilisée pour créer une nouvelle instance de l'objet. Cet attribut est obligatoire si la configuration ne précise aucun autre moyen pour obtenir une instance.
Par défaut, le conteneur invoque un constructeur pour créer une nouvelle instance : le constructeur utilisé est celui dont la signature correspond aux paramètres fournis.
Il est possible de demander l'instanciation en invoquant une méthode statique de la classe qui agit comme une fabrique. Le nom de la méthode statique est précisé avec l'attribut factory-method. Cette méthode statique doit appartenir à la classe car le conteneur va l'invoquer pour obtenir une instance. Elle peut attendre des paramètres.
Exemple : |
<bean id="monBean" class="com.jmdoudoux.test.spring.MonBean" factory-method="createInstance"/>
Il est aussi possible de demander au conteneur d'invoquer une fabrique pour créer une nouvelle instance. Dans ce cas, l'attribut class ne doit pas être renseigné. L'attribut factory-bean doit préciser l'identifiant de l'objet géré par le conteneur qui encapsule la fabrique et l'attribut factory-method doit préciser le nom de la méthode à invoquer.
Exemple : |
<bean id="monBeanFactory" class="com.jmdoudoux.test.spring.MonBeanFactory"/>
<bean id="monBean" factory-bean="monBeanFactory" factory-method="creerInstance"/>
Il est possible de définir un alias sur le nom du bean en utilisant le tag <alias>
Exemple : |
<alias name="idDuBean" alias="aliasDuBean"/>
Un bean géré par le conteneur possède une portée (scope).
Pour un usage général, Spring propose deux portées :
Spring propose d'autres portées notamment celles dédiées aux applications web (request, session et global-session).
La portée est définie dans le fichier de configuration en utilisant l'attribut scope du tag <bean>. La valeur fournie est celle de la portée souhaitée (singleton ou prototype).
Remarque : avant la version 2.0 de Spring, il fallait utiliser l'attribut singleton qui attend une valeur booléenne.
Par défaut, les beans ont une portée singleton : la grande majorité des beans gérés dans un conteneur Spring sont généralement des singletons.
Le conteneur ne peut pas gérer la destruction d'un bean avec une portée prototype : c'est de la responsabilité de l'application qui seule peut savoir quand l'instance n'est plus utilisée.
Il est possible de définir des callbacks liés à l'initialisation et la destruction d'un bean qui seront invoqués par le conteneur.
Remarque : l'utilisation de ces callbacks n'est pas fortement recommandée par elle lie les beans à Spring puisque ceux-ci doivent implémenter des interfaces de Spring.
L'interface InitializingBean définit la méthode afterPropertiesSet(). Cette méthode peut contenir des traitements qui seront exécutés par le conteneur après l'initialisation d'une nouvelle instance du bean.
Remarque : il est préférable de préciser le nom d'une méthode contenant ces traitements comme valeur de l'attribut init-method du tag <bean>. Le résultat sera le même mais le bean ne sera pas lié à Spring.
L'interface DisposeableBean définit la méthode destroy(). Cette méthode peut contenir des traitements qui seront exécutés par le conteneur avant la suppression d'un bean du conteneur.
Remarque : il est préférable de préciser le nom d'une méthode contenant ces traitements comme valeur de l'attribut destroy-method du tag <bean>. Le résultat sera le même mais le bean ne sera pas lié à Spring.
Dans le fichier de configuration, il est possible de définir des méthodes d'initialisation et de destruction qui si elles sont présentes dans un bean seront automatiquement invoquées au moment opportun par le conteneur.
L'attribut default-init-method du tag <beans> permet de définir une méthode d'initialisation par défaut. Sa valeur doit contenir le nom de la méthode.
L'attribut default-destroy-method du tag <beans> permet de définir une méthode de destruction par défaut. Sa valeur doit contenir le nom de la méthode.
A partir de Spring 2.5, il est aussi possible d'utiliser les annotations @PostConstruct et @PreDestroy pour annoter respectivement les méthodes d'initialisation et de destruction.
Il est possible de combiner ces différentes solutions. Dans ce cas, le conteneur utilise un ordre précis :
Le comportement par défaut du conteneur est d'instancier les singletons lors de son initialisation. Il est possible de demander l'instanciation lors de la première utilisation en utilisant l'attribut lazy-init du tag <bean> avec la valeur true. Ceci n'empêchera pas le conteneur d'instancier le singleton si celui-ci est une dépendance d'un autre singleton qui est instancié au démarrage du conteneur.
Il est possible de préciser l'attribut lazy-init de chaque bean en utilisant l'attribut default-lazy-init du tag <beans>. Cet attribut est configuré au niveau du contexte et s'applique donc sur tous les beans gérés.
Exemple : |
<beans default-lazy-init="true">
<!-- ... -->
</beans>
Certaines valeurs de propriétés dans le fichier de configuration peuvent être extraites d'un fichier de properties. La résolution se fait en utilisant un placeholder ayant la forme ${xxx} où xxx est la clé dans le fichier de properties.
Exemple : |
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"
p:driverClassName="${jdbc.driverClassName}"
p:url="${jdbc.url}"
p:username="${jdbc.username}"
p:password="${jdbc.password}"/>
La résolution est effectuée par une instance particulière de BeanFactoryPostProcessor nommée PropertyPlaceholderConfigurer.
Exemple : |
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:jdbc.properties"/>
</bean>
Spring 2.5 propose l'espace de nommage context qui simplifie cette déclaration en utilisant le tag <property-placeholder>. Sa propriété location permet de préciser le chemin du fichier de properties.
Exemple : |
<context:property-placeholder location="classpath:jdbc.properties"/>
L'externalisation de certaines valeurs est très pratique notamment pour celles qui sont différentes selon l'environnement d'exécution.
Le but de ces espaces de nommage est de simplifier la définition du contexte.
Spring 2.0 propose plusieurs espaces de nommage (namespaces) : aop, jee, lang, tx et util.
Spring 2.5 ajoute les espaces de nommage context et jms.
Spring définit aussi plusieurs autres espaces de nommage pour des usages plus particuliers : oxm, sws, ...
Il est aussi possible de définir ses propres espaces de nommage.
L'espace de nommage beans est obligatoire et est utilisé comme espace de nommage par défaut dans le fichier de configuration.
L'uri du schéma correspondant est www.springframework.org/schema/beans
Le schéma est défini dans le fichier xsd www.springframework.org/schema/beans/spring-beans-x.x.xsd
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-2.0.xsd">
<!-- ... -->
</beans>
Le tag <beans> est l'élément racine du fichier de configuration du context Spring.
Le tag <beans> peut avoir plusieurs tags fils : <alias>, <bean>, <description> et <import>.
Le tag <bean> permet de configurer un bean. Il possède plusieurs attributs :
Attribut |
Rôle |
abstract |
Booléen qui précise si le bean est abstrait : la valeur true indique au conteneur de ne pas créer d'instance |
autowire |
Permet de préciser comment le bean sera injecté : byType, byName, constructor, autodetect, none (pas d'autowiring) |
autowire-candidate |
Booléen qui précise si l'instance du bean peut être utilisée lors de l'injection de dépendances |
class |
Le nom pleinement qualifié de la classe du bean |
dependency-check |
Préciser les dépendances qui seront valorisées par le conteneur : simple (pour les primitives), object (pour les objets), default, none, all |
depends-on |
Préciser un bean qui devra être initialisé avant que le bean soit instancié |
destroy-method |
Préciser une méthode qui sera invoquée lorsque le bean est déchargé du conteneur |
factory-bean |
Préciser un bean dont la méthode indiquée par l'attribut factory-method sera utilisée comme fabrique |
factory-method |
Préciser une méthode statique du bean indiqué par l'attribut factory-bean qui sera utilisée par le conteneur comme une fabrique |
id |
Identifiant du bean |
init-method |
Nom de la méthode d'initialisation qui sera invoquée une fois l'instance créée et les dépendances injectées |
lazy-init |
Booléen qui indique si l'instance sera initialisée tardivement |
name |
Nom du bean |
parent |
Préciser un bean dont la configuration sera héritée par le bean |
scope |
Permet de préciser la portée du bean : singleton par défaut, prototype, request, session |
Le tag <bean> peut avoir plusieurs tags fils : <constructor-arg>, <description>,<lookup-method>,<meta>,<property> et <replaced-method>.
Le tag <constructor-arg> permet d'utiliser l'injection par constructeur : il permet de fournir une valeur ou une référence sur un bean géré par le conteneur
Le tag <lookup-method> permet d'utiliser l'injection par getter : le getter est remplacé par une autre implémentation qui retourne une instance particulière.
Le tag <property> permet d'utiliser l'injection par setter pour fournir une valeur à une propriété. Cette valeur peut être une référence sur un autre bean géré par le conteneur.
Le tag <replaced-method> permet de remplacer les traitements d'une méthode du bean par une autre implémentation.
Le tag <alias> permet de définir un alias pour un bean.
Le tag <import> permet d'importer une autre partie de la définition du contexte de Spring. Ce tag est particulièrement utile pour définir le contexte dans plusieurs fichiers, chacun contenant des définitions de beans par thème fonctionnel ou technique (services, transactions, accès aux données, ...)
Le tag <description> permet de fournir une description de la définition du contexte ou d'un bean.
Spring 2.0 a introduit un espace de nommage particulier nommé p dont le but est de réduire la quantité de code à produire dans le fichier de configuration XML du contexte Spring.
L'uri du schéma correspondant est www.springframework.org/schema/p
L'espace de nommage p de Spring est une alternative pour définir les propriétés des beans dans le fichier de configuration : au lieu d'utiliser un tag fils <property>, il est possible d'utiliser l'espace de nommage p pour définir les propriétés sous la forme d'attributs du tag <bean>.
Exemple : |
<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-2.0.xsd">
<bean name="monBean" class="com.jmdoudoux.test.spring.MonBean">
<property name="maPropriete" value="maValeur"/>
</bean>
</beans>
Exemple : |
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
<bean name="monBean"
class="com.jmdoudoux.test.spring.MonBean"
p:maPropriete="maValeur"/>
</beans>
L'espace de nommage p permet donc de réduire la quantité de code XML.
L'espace de nommage ne possède pas de définition dans un schéma dédié, ce qui permet d'utiliser n'importe quel nom de propriété comme attribut.
Exemple : |
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"
p:driverClassName="oracle.jdbc.driver.OracleDriver"
p:url="jdbc:oracle:thin:@monServeur:1521:maBase"
p:username="root"
p:password="mdp"/>
Il est aussi possible de fournir une référence sur un autre bean comme valeur en utilisant le suffixe -ref
Exemple : |
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
<bean name="monAutreBean"
class="com.jmdoudoux.test.spring.MonAutreBean"/>
<bean name="monBean"
class="com.jmdoudoux.test.spring.MonBean"
p:maPropriete="maValeur"
p:maDependance-ref="monAutreBean"/>
</beans>
Spring 2.0 a introduit un espace de nommage particulier nommé jee qui permet de faciliter la configuration en utilisant un environnement Java EE par exemple en obtenant un objet d'un annuaire JNDI ou une référence à un EJB.
L'uri du schéma correspondant est www.springframework.org/schema/jee
Le schéma est défini dans le fichier xsd www.springframework.org/schema/beans/spring-jee-x.x.xsd
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:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee-2.0.xsd">
<!-- ... -->
</beans>
Le tag <jee:jndi-environment> définit des propriétés pour permettre un accès à l'annuaire JNDI.
Le tag <jee:jndi-lookup> permet d'obtenir une référence sur un objet configuré dans un annuaire en utilisant JNDI.
Exemple : |
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/MaDataSource"/>
Ce tag facilite l'utilisation d'un objet de type JndiObjectFactoryBean.
Le tag <jee:local-slsb> permet d'obtenir une référence sur un EJB Session Stateless local et de créer un proxy pour accéder à cet EJB.
Exemple : |
<jee:local-slsb id="monEJB" jndi-name="monEJB"
business-interface="com.jmdoudoux.test.ejb.MonEJB" />
Ce tag utilise un objet de type LocalStatelessSessionProxyFactoryBean.
Le tag <jee:remote-slsb> permet d'obtenir une référence sur un EJB Session Stateless distant et de créer un proxy pour accéder à cet EJB.
Ce tag utilise un objet de type RemoteStatelessSessionProxyFactoryBean.
Spring 2.0 a introduit l'espace de nommage lang qui permet de configurer dans le contexte des objets définis dans un langage de scripting comme par exemple Groovy.
L'uri du schéma correspondant est www.springframework.org/schema/lang
Le schéma est défini dans le fichier xsd www.springframework.org/schema/beans/spring-lang-x.x.xsd
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:lang="http://www.springframework.org/schema/lang"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/lang
http://www.springframework.org/schema/lang/spring-lang-3.0.xsd">
<!-- ... -->
</beans>
Plusieurs langages dynamiques sont supportés : Groovy, JRuby et BeanShell.
Exemple : |
<lang:groovy id="monBean" script-source="classpath:MonBean.groovy">
<lang:property name="message" value="Bienvenue"/>
</lang.groovy>
Le tag <defaults> permet de configurer certaines propriétés applicables pour les beans définis avec un langage dynamique.
Le tag <groovy> permet de configurer dans le contexte un bean défini en Groovy.
La propriété script-source permet de préciser le script qui contient la définition du bean.
La propriété refresh-check-delay permet de préciser une durée en millisecondes entre chaque vérification de changement dans le script avec son rechargement le cas échéant. Si cet attribut n'est pas valorisé alors aucune vérification n'est faite. Ce rafraîchissement permet de tenir compte d'une modification du script sans avoir à recharger la classe : c'est un des intérêts des langages dynamiques.
Exemple : |
<lang:groovy id="monAutreBean" script-source="classpath:MonAutreBean.groovy"
refresh-check-delay="60000" />
<bean id="monBean" class="com.jmdoudoux.test.spring.MonBean">
<property name="dependance" ref="monAutreBean" />
</bean>
Le tag fille <inline-script> permet de fournir un script Groovy dans son corps.
Le tag fille <property> permet de configurer une propriété du script Groovy
Le tag <jruby> permet de configurer dans le contexte un bean défini en JRuby. Son mode d'utilisation est similaire à celui du tag <groovy>.
L'attribut obligatoire <script-interfaces> doit contenir la ou les interfaces qui seront utilisées par Spring pour créer un proxy qui se chargera d'invoquer la classe JRuby.
Le tag <bsh> permet de configurer dans le contexte un bean défini en BeanShell.
Spring 2.5 a introduit l'espace de nommage context qui permet de faciliter la configuration des beans de Spring définis dans le contexte.
L'uri du schéma correspondant est www.springframework.org/schema/context
Le schéma est défini dans le fichier xsd www.springframework.org/schema/beans/spring-context-x.x.xsd
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:context="http://www.springframework.org/schema/lang"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!-- ... -->
</beans>
Le tag <property-placeholder> permet de demander l'activation du remplacement des marqueurs ${...} par leur valeur dans les fichiers de propriétés correspondantes.
Exemple : |
<context:property-placeholder location="file:////etc/monApp.properties"/>
Ce tag permet de définir un objet de type PropertyPlaceHolderConfigurer. Pour une configuration précise de cet objet, il faut définir sa propre instance explicitement.
L'utilisation de ce tag permet facilement d'externaliser certaines valeurs de configuration.
Exemple : |
<context:property-placeholder location="classpath:monApp.properties"/>
<bean id="monBean" class="com.jmdoudoux.test.spring.MonBean">
<property name="maValeur" ref="${monApp.maValeur}"/>
</bean>
Spring 2.5 propose le tag <annotation-config> qui va activer la recherche de plusieurs annotations pour configurer les beans dans le contexte.
Le tag <annotation-config> permet de définir simplement des beans de type CommonAnnotationBeanPostProcessor, AutowiredAnnotationBeanPostProcessor, RequiredAnnotationBeanPostProcessor et PersistenceAnnotationBeanPostProcessor. Ceux-ci se chargent de rechercher et d'utiliser des annotations pour configurer les beans dans le contexte.
Exemple : |
<context:annotation-config/>
Un bean de type CommonAnnotationBeanPostProcessor permet d'utiliser les annotations de la JSR 250 @Resource, @PostConstruct, @PreDestroy, @WebServiceRef et @EJB.
Un bean de type AutowiredAnnotationBeanPostProcessor permet d'utiliser l'annotation @Autowired
Un bean de type RequiredAnnotationBeanPostProcessor permet d'utiliser l'annotation @Required
Un bean de type PersistenceAnnotationBeanPostProcessor permet d'utiliser les annotations @PersistenceContext et @PersistenceUnit de JPA.
Le tag <component-scan> permet de préciser les packages qui devront être scannés à la recherche de classes annotées dans le but de configurer le contexte et d'activer cette recherche.
Exemple : |
<context:component-scan base-package="com.jmdoudoux.test.spring.services"/>
Dans l'exemple ci-dessus, les classes du packages com.jmdoudoux.test.sprint.services seront scannées à la recherche de beans annotés avec @Component, @Service, @Repository ou @Controller pour permettre leur définition dans le contexte.
Il est possible d'utiliser des tags fils <include-filter> et/ou <exclude-filter> pour filtrer les classes à scanner dans le ou les packages proposés.
Le tag <load-time-weaver> permet d'activer le tissage au runtime. L'utilisation de ce tag permet aussi d'activer l'injection des dépendances dans les instances de classes non gérées par Spring et annotées avec @Configurable. Dans ce cas, la classe AnnotationBeanConfigurerAspect incluse dans la bibliothèque spring-aspects.jar doit être dans le classpath.
Exemple : |
<context:load-time-weaver/>
Il est possible de préciser une implémentation particulière du type LoadTimeWeaver qui sera utilisée en la précisant comme valeur de l'attribut weaver-class. Si cet attribut n'est pas précisé l'instance de LoadTimeWeaver utilisée sera déterminée automatiquement.
L'attribut aspectj-weaving permet d'activer ou non le tissage au runtime en utilisant AspectJ : les valeurs possibles sont on (LTW activé), off (LTW désactivé), autodetect (valeur par défaut qui active le LTW si un fichier META-INF/aop.xml peut être trouvé par Spring).
Le tag <spring-configured> permet d'activer l'injection de dépendances dans des instances qui ne sont pas gérées par Spring : ceci concerne les classes qui sont annotées avec @Configurable.
Exemple : |
<context:spring-configured/>
Ce tag permet de définir un objet de type AnnotationBeanConfigurerAspect.
Le tag <mbean-export> permet d'activer l'exposition des MBeans définis dans le context avec l'annotation @ManagedResource en utilisant une instance de type MBeanExporter.
Ce tag remplace la définition d'un objet de type AnnotationMBeanExporter.
Exemple : |
<context:mbean-export/>
L'attribut server permet de préciser le nom du serveur de MBeans à utiliser.
L'attribut default-domain permet de préciser le domain par défaut qui sera utilisé.
Le tag <mbean-server> permet d'activer le serveur de MBeans de la plate-forme. Celui-ci est automatiquement détecté pour les JVM 1.5 et ultérieures, Weblogic à partir de la version 9 et Websphere à partir de la version 6.1.
Exemple : |
<context:mbean-server/>
L'attribut id permet de préciser le nom du serveur de MBeans qui par défaut est "mbeanServer".
Le tag <property-override> permet d'activer le remplacement des valeurs des propriétés de beans.
Ce tag remplace la définition d'un bean de type PropertyOverrideConfigurer.
Exemple : |
<context:property-override location="classpath:maconfig.properties"/>
<bean id="monBean" class="com.jmdoudoux.test.spring.MonBean">
<property name="maValeur" ref="0"/>
</bean>
La clé du fichier de properties est l'id du bean suivi d'un point suivi du nom de la propriété.
Exemple : |
monBean.maValeur=1234
Les valeurs contenues dans le fichier de properties sont toujours littérales : il n'est pas possible de faire référence à un autre bean.
Dans la définition du contexte, il n'est pas possible de voir que la valeur d'une propriété sera remplacée.
Spring 2.0 a introduit un espace de nommage particulier nommé util qui facilite certaines fonctionnalités de configuration comme la déclaration d'un bean dont la valeur est une constante ou une collection (list, map ou set).
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-2.0.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-2.0.xsd">
<!-- ... -->
</beans>
Le tag <util:constant> permet de définir un bean à partir d'une constante.
Exemple : |
<util:constant id="integerMaxValue" static-field="java.lang.Integer.MAX_VALUE"/>
L'attribut static-field permet de préciser la constante.
La propriété id permet de définir le nom du bean.
Exemple : |
<bean class="com.jmdoudoux.test.spring.MonBean">
<property name="maValeur" ref="integerMaxValue"/>
</bean>
Il est aussi possible d'imbriquer le tag <util:constant> dans la déclaration d'un bean
Exemple : |
<bean class="com.jmdoudoux.test.spring.MonBean">
<property name="maValeur">
<util:constant static-field="java.lang.Integer.MAX_VALUE"/>
</property>
</bean>
Le tag <util:property-path> permet de définir un bean à partir d'une propriété d'un autre bean
Exemple : |
<util:property-path id="maValeur" path="monBean.maValeur"/>
L'utilisation de ce tag peut remplacer la définition d'un bean en utilisant la classe org.springframework.beans.factory.config.PropertyPathFactoryBean.
Le tag <util:properties> permet de définir un bean de type Properties à partir du contenu d'un fichier .properties.
Exemple : |
<util:properties id="mesProperties" location="classpath:config.properties"/>
L'utilisation de ce tag peut remplacer la définition d'un bean en utilisant la classe org.springframework.beans.factory.config.PropertiesFactoryBean.
Exemple : |
<bean id="mesProperties"
class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="location" value=" classpath:config.properties"/>
</bean>
Le tag <util:list> permet de définir un bean qui est une collection de type List.
Exemple : |
<util:list id="prenoms">
<value>Beatrice</value>
<value>Michel</value>
<value>Thomas</value>
<value>Valerie</value>
</util:list>
L'attribut id permet de préciser le nom du bean.
L'attribut list-class permet de préciser le type de la collection de type List
Exemple : |
<util:list id="prenoms" list-class="java.util.LinkedList">
<value>Beatrice</value>
<value>Michel</value>
<value>Thomas</value>
<value>Valerie</value>
</util:list>
Chaque élément de la collection est précisé avec un tag fils value dont le corps contient la valeur.
L'utilisation de ce tag peut remplacer la création d'un bean en utilisant la classe org.springframework.beans.factory.config.ListFactoryBean.
Exemple : |
<bean id="prenoms" class="org.springframework.beans.factory.config.ListFactoryBean">
<property name="sourceList">
<list>
<value>Beatrice</value>
<value>Michel</value>
<value>Thomas</value>
<value>Valerie</value>
</list>
</property>
</bean>
Le tag <util:map> permet de définir un bean qui est une collection de type Map.
Exemple : |
<util:map id="nombres">
<entry key="1" value="Un"/>
<entry key="2" value="Deux"/>
<entry key="3" value="Trois"/>
<entry key="4" value="Quatre"/>
</util:map>
L'attribut id permet de préciser le nom du bean.
L'attribut map-class permet de préciser le type de la collection de type Map
Exemple : |
<util:map id="nombres" map-class="java.util.TreeMap">
<entry key="1" value="Un"/>
<entry key="2" value="Deux"/>
<entry key="3" value="Trois"/>
<entry key="4" value="Quatre"/>
</util:map>
L'utilisation de ce tag peut remplacer la création d'un bean en utilisant la classe org.springframework.beans.factory.config.MapFactoryBean.
Exemple : |
<bean id="nombres" class="org.springframework.beans.factory.config.MapFactoryBean">
<property name="sourceMap">
<map>
<entry key="1" value="Un"/>
<entry key="2" value="Deux"/>
<entry key="3" value="Trois"/>
<entry key="4" value="Quatre"/>
</map>
</property>
</bean>
Le tag <util:set> permet de définir un bean qui est une collection de type Set.
Exemple : |
<util:set id="prenoms">
<value>Beatrice</value>
<value>Michel</value>
<value>Thomas</value>
<value>Valerie</value>
</util:set>
L'attribut id permet de préciser le nom du bean.
L'attribut set-class permet de préciser le type de la collection de type Set.
Exemple : |
<util:set id="prenoms" set-class="java.util.TreeSet">
<value>Beatrice</value>
<value>Michel</value>
<value>Thomas</value>
<value>Valerie</value>
</util:set>
L'utilisation de ce tag peut remplacer la création d'un bean en utilisant la classe org.springframework.beans.factory.config.SetFactoryBean.
L'injection de dépendances est une mise en oeuvre de l'IoC : les instances des dépendances vont être injectées automatiquement par le conteneur selon la configuration lors de l'instanciation d'un objet.
L'implémentation de l'injection de dépendances proposée par Spring peut se faire de deux façons :
Il est aussi possible de fournir les instances des dépendances en paramètre de l'invocation d'une fabrique mais au final celle-ci va utiliser l'injection par le constructeur ou par les setters.
La classe à instancier doit proposer un constructeur qui attend en paramètre la ou les instances des dépendances requises.
Exemple : |
public class PersonneService {
private PersonneDao personneDao;
public PersonneService(PersonneDao personneDao) {
this.personneDao = personneDao;
}
// méthodes de la classe qui peuvent utiliser la dépendance
}
Dans le fichier de configuration, le tag <constructor-arg> permet de fournir un paramètre au constructeur.
L'attribut value permet de fournir une valeur au paramètre.
Exemple : |
<bean id="monBean" class="com.jmdoudoux.test.spring.MonBean">
<constructor-arg value="123"/>
<constructor-arg value="456"/>
</bean>
Le constructeur invoqué par le conteneur est celui dont la signature comprend les types des arguments configurés. Si les types ne sont pas identifiables ou ne suffisent pas pour déterminer de façon certaine le constructeur à utiliser, le conteneur utilise l'ordre des paramètres définit dans la configuration.
L'attribut type permet d'indiquer au conteneur le type de la valeur et ainsi facilite le travail du conteneur pour déterminer le constructeur à utiliser.
Exemple : |
<bean id="monBean" class="com.jmdoudoux.test.spring.MonBean">
<constructor-arg type="int" value="123"/>
<constructor-arg type="java.lang.String" value="456"/>
</bean>
Pour faciliter encore plus son travail, l'attribut index permet de préciser l'index du paramètre définit. Le premier paramètre possède l'index 0.
Exemple : |
<bean id="monBean" class="com.jmdoudoux.test.spring.MonBean">
<constructor-arg index="0" value="123"/>
<constructor-arg index="1" value="456"/>
</bean>
Dans ce cas, la classe doit proposer une propriété avec un setter respectant les conventions JavaBean pour chacune de ses dépendances.
Le conteneur va créer une instance en invoquant le constructeur par défaut puis va invoquer le setter de chaque dépendance en lui passant en paramètre celle définie dans la configuration.
Exemple : |
public class PersonneService {
private PersonneDao personneDao;
public void setPersonneDao(PersonneDao personneDao) {
this.personneDao = personneDao;
}
// méthodes de la classe qui peuvent utiliser la dépendance
}
Dans le fichier de configuration, le tag <property> permet de fournir un paramètre au setter d'une propriété.
Exemple : |
<bean id="monBean" class="com.jmdoudoux.test.spring.MonBean">
<property name="prop1" value="123"/>
<property name="prop2" value="456"/>
</bean>
L'attribut obligatoire name permet de préciser le nom de la propriété concernée.
L'attribut value permet de préciser une valeur statique.
L'attribut ref permet de préciser un bean géré par le conteneur comme valeur du paramètre : sa valeur doit contenir l'identifiant du bean concerné dans le contexte Spring.
Les tags <constructor-arg> et <property> possèdent plusieurs attributs pour assigner une valeur. Ils possèdent aussi plusieurs tags fils qui permettent de réaliser cette opération.
La valeur peut aussi être fournie en utilisant un tag fils <value> ou <bean>.
Le tag fils <value> permet de fournir une valeur dans sa représentation sous la forme d'une chaîne de caractères.
Le tag <bean> peut aussi être utilisé pour laisser le constructeur créer une instance : dans ce cas, il n'est pas nécessaire de fournir un identifiant.
Les tags fils <idref> et <ref> permettent de préciser un bean géré par le conteneur comme valeur du paramètre : son attribut bean doit contenir l'identifiant du bean concerné.
Les tags fils <list>, <maps>, <props> et <set> permettent de fournir des collections comme valeur du paramètre.
Le tag <null> permet de préciser la valeur null au paramètre.
Le tag <value> vide permet de préciser une chaîne de caractères vide.
Il n'y a pas de règle immuable dans la mesure où chacune des deux méthodes possède des avantages et des inconvénients.
L'avantage d'utiliser l'injection par constructeur est que l'instance obtenue est complètement initialisée suite à son instanciation.
Si le nombre de dépendances est important ou si certaines sont optionnelles, le choix d'utiliser l'injection par constructeur n'est peut être pas judicieux.
L'injection par constructeur peut aussi induire une dépendance circulaire : par exemple une classe A a une dépendance avec une instance de la classe B et la classe B a une dépendance avec une instance de la classe A. Dans ce cas, le conteneur lève une exception.
L'injection par setter peut permettre de modifier l'instance de la dépendance : cela n'est pas toujours souhaitable mais peut être utile dans certaines circonstances.
Le conteneur Spring permet aussi de mixer ces deux modes d'injection : une partie par constructeur et une autre par setter.
Le choix doit donc tenir compte du contexte d'utilisation.
L'autowiring laisse le conteneur déterminer automatiquement l'objet géré par le conteneur qui sera injecté comme dépendance d'un autre objet.
L'utilisation de l'autowiring permet de simplifier grandement le fichier de configuration car il devient superflu de définir les valeurs des paramètres fournis au setter ou au constructeur pour injecter les dépendances.
L'attribut autowire du tag bean permet de demander l'activation de l'autowiring pour le bean.
L'autowiring peut fonctionner selon plusieurs stratégies :
L'attribut default-autowire du tag <beans> permet de préciser le mode de fonctionnement par défaut de l'autowiring.
La définition explicite prend toujours le pas sur l'autowiring.
L'autowiring ne peut être utilisé que pour des objets : il n'est pas possible d'utiliser l'autowiring sur des propriétés de types primitif ou String ou des tableaux.
L'autowiring permet de simplifier la configuration des beans mais aussi de maintenir cette configuration à jour lors de l'ajout d'une nouvelle dépendance dans un bean qui utilise l'autowiring.
Avec l'autowiring, comme les dépendances ne sont plus explicitement définies dans la configuration, certains outils ne pourront plus les déterminer.
Pour que l'autowiring fonctionne correctement, le conteneur doit être en mesure de déterminer de façon non ambigüe l'instance qui sera injectée.
Il est possible de marquer des objets comme ne devant pas être pris en compte comme candidats à l'autowiring. Il suffit pour cela de donner la valeur false à l'attribut autowire-candidate.
Remarque : la mise en oeuvre de l'autowiring peut aussi se faire avec des annotations dédiées.
Spring 3.0 a introduit un langage d'expressions nommé Spring Expression Language (en abrégé Spring EL ou SpEL).
Spring EL est un langage d'expressions pour permettre l'interrogation et la manipulation d'objets au runtime : il permet de faciliter la configuration en utilisant un langage d'expressions.
Il existe déjà plusieurs langages d'expressions comme Unified EL ou JBoss EL mais Spring a décidé de développer son propre langage d'expressions notamment pour lui permettre d'être indépendant dans ses évolutions.
SpEL est un langage d'expressions, similaire à Unified EL utilisé dans les JSP, qui permet principalement d'accéder à des données.
Avec SpEL, la configuration de Spring n'est plus limitée à des valeurs fixes ou des références vers d'autres objets : il est possible de déterminer des valeurs dynamiquement.
SpEL fait partie de Spring Core : il est ainsi potentiellement utilisable dans tous les projets du portfolio Spring. SpEL est déjà utilisé par plusieurs projets du portfolio notamment Spring Security et Spring Batch.
La syntaxe utilisée par SpEL pour définir une expression est de la forme #{<expression>}.
SpEL supporte de nombreuses fonctionnalités : expressions littérales, assignations, opérateurs, expressions régulières, manipulations de collections, ...
SpEL propose aussi des fonctionnalités relatives aux classes : accès aux propriétés, invocation de méthodes, invocation de constructeurs, ...
SpEL peut être utilisé dans la configuration du contexte (dans le fichier de configuration XML ou avec certaines annotations) ou être évalué dynamiquement dans le code de l'application en utilisant l'API dédiée.
SpEL est contenu dans le package org.springframework.expression
Une exception de type SpelEvaluationException est levée si l'évaluation de l'expression échoue.
SpEL est utilisable :
SpEL peut être utilisé dans la déclaration du contexte.
L'exemple ci-dessous utilise SpEL dans le fichier de configuration XML pour mettre en oeuvre différentes fonctionnalités.
Exemple : |
<bean id="monBean" class="com.jmdoudoux.test.spring.MonBean">
<property name="nombreAleatoire" value="#{ T(java.lang.Math).random () * 100.0 }" />
<property name="defaultLocale" value="#{systemProperties['user.region'] }" />
<property name="monAutrePropriete" value="#{monAutreBean.propriete}" />
</bean>
<bean id="monAutreBean" class="com.jmdoudoux.test.spring.MonBean">
<property name="propriete" value="AutreValeur" />
</bean>
SpEL peut aussi être utilisé dans l'annotation @Value
L'exemple ci-dessous utilise SpEL pour obtenir une valeur d'une propriété système
Exemple : |
@Value("#{ systemProperties['user.name'] }")
public void setUserName(final String userName) {
this.userName = userName;
}
Dans l'exemple ci-dessous, un fichier de propriétés est déclaré dans le contexte.
Exemple : |
<util:properties id="appConfig" location="classpath:conf.properties"/>
Il est possible d'utiliser SpEL pour obtenir des valeurs du fichier de properties selon leurs clés.
Exemple : |
@Component
public class MonBean {
@Value("#{appConfig['db.Host']}")
private String dbHost;
private String dbUser;
@Value("#{appConfig['db.User']}")
public void setDbUser(final String dbUser) {
this.dbUser = dbUser;
}
}
SpEL peut aussi être utilisé pour créer une nouvelle instance.
Exemple : |
public class MonBean {
private Date dateDebut;
@Value("#{new java.util.Date()}")
public void setDateDebut(final Date dateDebut) {
this.dateDebut = dateDebut;
}
}
Pour utiliser l'API, il faut créer une instance de type ExpressionParser qui est le parseur d'expressions.
Spring propose une implémentation sous la forme de la classe SpelExpressionParser qui est un parseur d'expressions SpEL.
La méthode parseExpression() renvoie une instance de type Expression qui encapsule l'expression fournie en paramètre.
Exemple : |
package com.jmdoudoux.test.spring;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
public class ExpressionTest {
public static void main(final String[] args) {
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("'Hello world'");
String message = (String) expression.getValue();
System.out.println("Message = " + message);
}
}
La chaîne de caractères fournie en paramètre de la méthode parseExpression() doit être une expression valide de SpEL.
La méthode getValue() de la classe Expression permet d'obtenir le résultat de l'évaluation de l'expression.
Exemple : |
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello world'.substring(0, 5)");
String resultat = exp.getValue(String.class);
System.out.println("Resultat = " + resultat);
La méthode getValue() possède une version surchargée qui prend en paramètre le type de la valeur de retour sous la forme d'une instance de Class<T>.
Exemple : |
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("' Hello'.charAt(5)");
Character character = expression.getValue(Character.class);
System.out.println(character);
Le type de la valeur de retour peut utiliser les generics.
Exemple : |
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("new java.util.ArrayList()");
List liste = expression.getValue(List.class);
System.out.println(liste);
Le parser SpEL est threadsafe : il est donc possible de partager son instance.
Exemple : |
package com.jmdoudoux.test.spring;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
public class TestParser {
public final static ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
}
Le parser est utilisé pour évaluer des expressions au runtime. L'expression peut être évaluée dans un contexte particulier encapsulé dans un objet de type ParserContext.
Le contexte d'évaluation peut contenir un état ou un comportement qui sera pris en compte lors de l'évaluation de l'expression.
Il est par exemple possible d'enregistrer une variable dans le contexte pour que celle-ci puisse être utilisée dans l'expression. La valeur fournie dans le contexte est alors utilisée lors de l'évaluation de l'expression.
Exemple : |
package com.jmdoudoux.test.spring;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
public class TestParser {
public final static ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
public static void main(final String[] args) {
StandardEvaluationContext context = new StandardEvaluationContext();
context.setVariable("maVariable", "test");
String valeur = EXPRESSION_PARSER.parseExpression("#maVariable").getValue(
context, String.class);
System.out.println(valeur);
}
}
Dans l'exemple ci-dessous, un objet de type Double est déclarée en invoquant la méthode random() de la classe java.lang.Math. Cet objet est fourni en dépendance de la propriété maValeur d'un bean de type MonBean.
Exemple : |
<bean id="nombreAleatoire" class="java.lang.Math" factory-method="random" />
<bean id="monBean" class="com.jmdoudoux.test.spring.MonBean"
p:maValeur-ref="nombreAleatoire" />
Il est possible d'utiliser SpEL pour obtenir la valeur du bean.
Exemple : |
<bean id="nombreAleatoire" class="java.lang.Math" factory-method="random" />
<bean id="monBean" class="com.jmdoudoux.test.spring.MonBean"
p:maValeur="#{nombreAleatoire}" />
L'expression "#{nombreAleatoire}" sera évaluée pour faire référence au bean dont l'identifiant est nombreAleatoire.
L'exemple ci-dessous va utiliser SpEL dans la configuration des beans du contexte pour définir une valeur aléatoire et utiliser cette valeur en paramètre du constructeur d'un bean.
Exemple : |
<bean id=" nombreAleatoire" class="java.lang.Integer">
<constructor-arg value="#{T(java.lang.Math).random() " />
</bean>
<bean id="valeur" class="java.lang.Integer">
<constructor-arg value="#{nombreAleatoire.intValue()}" />
</bean>
L'exemple ci-dessous va récupérer une variable d'environnement de la JVM pour utiliser différents fichiers de configuration selon cette variable.
Exemple : |
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location"
value="#{systemProperties['environnement'] ?:'dev'}/conf.properties" />
</bean>
Des fichiers de configuration par environnement sont définis, chacun dans son sous-répertoire.
Résultat : |
src/main/resources/dev/conf.properties
src/main/resources/int/conf.properties
src/main/resources/prod/conf.properties
Au lancement de l'application, la variable d'environnement « environnement » est définie.
Exemple : |
java -jar monapp.jar -denvironnement=prod
L'exemple ci-dessous va utiliser SpEL avec l'annotation @Value
Exemple : |
package com.jmdoudoux.test.spring;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class MaClasse {
private final String valeur;
@Autowired
public MaClasse(@Value("#{systemProperties['valeur']}") final String valeur)
{
this.valeur = valeur;
}
public String getValeur() {
return this.valeur;
}
}
SpEL peut manipuler des chaînes de caractères.
Exemple : |
ExpressionParser parser = new SpelExpressionParser();
Expression exp =parser.parseExpression("'Test'");
System.out.println((String) exp.getValue());
System.out.println(exp.getValue(String.class));
exp = parser.parseExpression("new String('Test').toUpperCase()");
System.out.println((String) exp.getValue());
La syntaxe de SpEL est proche de celle d'Unified EL.
Les variables utilisables avec SpEL sont :
Une exception de type SpelParseException est levée lors de l'évaluation de l'expression si celle-ci n'est pas syntaxiquement correcte.
Les types de base se composent :
Les chaînes de caractères sont entourées par de simples quotes.
Exemple : |
Expression exp = EXPRESSION_PARSER.parseExpression("'Hello'");
String valeur = exp.getValue(String.class);
System.out.println(valeur);
Les valeurs primitives sont supportées comme dans tout autre langage d'expressions.
Exemple : |
System.out.println(EXPRESSION_PARSER.parseExpression("0").getValue(
byte.class));
System.out.println(EXPRESSION_PARSER.parseExpression("0").getValue(
short.class));
System.out.println(EXPRESSION_PARSER.parseExpression("0").getValue(
int.class));
System.out.println(EXPRESSION_PARSER.parseExpression("0L").getValue(
long.class));
System.out.println(EXPRESSION_PARSER.parseExpression("0.1F").getValue(
float.class));
System.out.println(EXPRESSION_PARSER.parseExpression("0.1D").getValue(
double.class));
System.out.println(EXPRESSION_PARSER.parseExpression("true").getValue(
boolean.class));
System.out.println(EXPRESSION_PARSER.parseExpression("'a'").getValue(
char.class));
SpEL permet un accès aux membres d'un bean.
L'exemple ci-dessous crée une nouvelle instance de MonBean et initialise sa propriété id.
Exemple : |
ExpressionParser parser = new SpelExpressionParser();
MonBean monBean = parser.parseExpression(
"new com.jmdoudoux.test.spring.MonBean()").getValue(MonBean.class);
StandardEvaluationContext context = new StandardEvaluationContext(monBean);
context.setVariable("monId", "1234");
parser.parseExpression("id=#monId").getValue(context);
La méthode setVariable() permet d'affecter une valeur à la variable monId dans le contexte.
L'expression "id=#monId" est évaluée en utilisant le contexte : la propriété id du bean est initialisée avec la valeur de la variable monId.
SpEL permet un accès aux propriétés d'un bean en utilisant la syntaxe avec un point.
Exemple : |
Expression exp = EXPRESSION_PARSER.parseExpression("'Hello'.bytes");
byte[] bytes = exp.getValue(byte[].class);
System.out.println(bytes);
L'accès aux propriétés peut être chaîné.
Exemple : |
Expression exp = EXPRESSION_PARSER.parseExpression("'Hello'.bytes.length");
int taille = exp.getValue(int.class);
System.out.println(taille);
SpEL offre un large éventail d'opérateurs. Il propose des opérateurs logiques, mathématiques et relationnels standards.
Exemple : |
System.out.println(EXPRESSION_PARSER.parseExpression("true and true")
.getValue(boolean.class));
System.out.println(EXPRESSION_PARSER.parseExpression("true or true")
.getValue(boolean.class));
System.out.println(EXPRESSION_PARSER.parseExpression("!false").getValue(
boolean.class));
System.out.println(EXPRESSION_PARSER.parseExpression("not false").getValue(
boolean.class));
System.out.println(EXPRESSION_PARSER.parseExpression("true and not false")
.getValue(boolean.class));
SpEL propose tous les opérateurs relationnels standard.
Exemple : |
System.out.println(EXPRESSION_PARSER.parseExpression("2==2").getValue(
boolean.class));
System.out.println(EXPRESSION_PARSER.parseExpression("2<3").getValue(
boolean.class));
System.out.println(EXPRESSION_PARSER.parseExpression("3>2").getValue(
boolean.class));
System.out.println(EXPRESSION_PARSER.parseExpression("0!=1").getValue(
boolean.class));
SpEL propose tous les opérateurs mathématiques standard.
Exemple : |
System.out.println(EXPRESSION_PARSER.parseExpression("2+2").getValue(
int.class));
System.out.println(EXPRESSION_PARSER.parseExpression("2-2").getValue(
int.class));
System.out.println(EXPRESSION_PARSER.parseExpression("2/2").getValue(
int.class));
System.out.println(EXPRESSION_PARSER.parseExpression("2*2").getValue(
int.class));
System.out.println(EXPRESSION_PARSER.parseExpression("2^2").getValue(
int.class));
System.out.println(EXPRESSION_PARSER.parseExpression("1e10").getValue(
double.class));
SpEL permet la concaténation de chaînes de caractères
Exemple : |
System.out.println(EXPRESSION_PARSER.parseExpression("'Hello'+' world'")
.getValue(String.class));
SpEL propose un support de l'opérateur ternaire
Exemple : |
System.out.println(EXPRESSION_PARSER.parseExpression(
"true ? 'vrai' : 'faux'").getValue(String.class));
SpEL propose un support de l'opérateur elvis qui est une forme simplifiée de l'opérateur ternaire.
Exemple : |
System.out.println(EXPRESSION_PARSER.parseExpression("null?:'sans valeur'")
.getValue(String.class));
SpEL peut accéder aux instances de java.lang.Class en utilisant l'opérateur T (T signifiant Type).
Il faut fournir à l'opérateur T le nom pleinement qualifié de la classe sauf pour les classes du package java.lang.
Résultat : |
T(java.util.Math)
T(Integer)
Avec l'opérateur T, il est possible d'accéder aux membres static d'une classe.
Exemple : |
<bean id="monBean3" class="com.jmdoudoux.test.spring.MonBean"
p:maValeur="#{T(java.util.Math).random()}"
p:monLibelle="#{T(String).format('Hello %s', 'world')}"
p:valeurMax="#{T(INTEGER).MAX_VALUE}"/>
L'opérateur instanceOf permet de tester si une variable est d'un certain type.
Exemple : |
<bean id="monBean" class="com.jmdoudoux.test.spring.MonBean" p:maChaine="Test" />
<bean id="monAutreBean" class="com.jmdoudoux.test.spring.MonAutreBean"
p:isString="#{monBean.maChaine instanceof T(String)}" />
L'opérateur instanceof permet de vérifier qu'un objet est d'un type donné : le type doit être précisé en utilisant l'opérateur T.
Exemple : |
boolean estUnEntier = EXPRESSION_PARSER.parseExpression(
"0 instanceof T(Integer)").getValue(boolean.class);
System.out.println(estUnEntier);
L'opérateur new permet de créer une nouvelle instance d'une classe.
Exemple : |
<bean id="monBean" class="com.jmdoudoux.test.spring.MonBean">
<property name="dateDebut" value="#{new java.util.Date()}" />
</bean>
L'exemple ci-dessous utilise SpEL pour créer une nouvelle instance d'un bean en invoquant un de ses constructeurs avec paramètres.
Exemple : |
ExpressionParser parser = new SpelExpressionParser();
MonBean monBean = parser.parseExpression(
"new com.jmdoudoux.test.spring.MonBean('test',1234,false)").getValue(
MonBean.class);
Il est obligatoire d'utiliser le nom pleinement qualifié de la classe à instancier.
SpEL permet l'invocation d'une méthode dans une expression, ce qui est une fonctionnalité intéressante pour un langage d'expressions.
Exemple : |
<bean id="monBean" class="com.jmdoudoux.test.spring.MonBean"
p:isFichierPresent="#{new java.io.File('/tmp/monFichier.tmp').exists()}" />
Dans cet exemple, la propriété isFichierPresent est initialisée avec le résultat de l'évaluation de la condition d'existence du fichier.
Exemple : |
Expression exp = EXPRESSION_PARSER
.parseExpression("'Hello'.concat(' World')");
String valeur = exp.getValue(String.class);
System.out.println(valeur);
SpEL propose un support des expressions régulières avec l'opérateur matches qui renvoie un booléen.
Exemple : |
<util:map id="regExpsExemples" value-type="java.lang.Boolean"
key-type="java.lang.String">
<entry key="cas1" value="#{'a demain' matches 'a.*'}" />
<entry key="cas2" value="#{'a demain' matches 'b.*'}" />
</util:map>
Exemple : |
package com.jmdoudoux.test.spring;
import java.util.Map;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestParser {
private static ClassPathXmlApplicationContext appContext;
public static void main(final String[] args) {
appContext = new ClassPathXmlApplicationContext("application-context.xml");
Map<String, Boolean> map = (Map) appContext.getBean("regExpsExemples");
}
}
L'opérateur matches permet d'appliquer une expression régulière : il renvoie un booléen qui précise si l'évaluation de l'expression correspond.
Exemple : |
boolean resultat = EXPRESSION_PARSER.parseExpression(
"+10.123e-4 matches '[-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?$'")
.getValue(boolean.class);
SpEL est capable de parser une date.
Exemple : |
Date date = EXPRESSION_PARSER.parseExpression("'2011/03/31'").getValue(
Date.class);
SpEL propose quelques fonctionnalités particulières concernant les collections notamment :
SpEL permet de faire une sélection dans une collection en appliquant un filtre pour créer une nouvelle collection qui est un sous-ensemble de la collection d'origine.
L'opérateur de sélection possède la syntaxe ?[selection-expression] à utiliser sur une collection.
Exemple : |
<util:list id="prenoms">
<value>Alain</value>
<value>Aurelie</value>
<value>Bruno</value>
<value>Charles</value>
<value>Laure</value>
<value>Maurice</value>
<value>Michel</value>
<value>Monique</value>
<value>Sylvie</value>
<value>Thierry</value>
<value>Veronique</value>
</util:list>
<util:map id="prenomsCommencantParM" value-type="java.lang.Object"
key-type="java.lang.String">
<entry key="M" value="#{prenoms.?[startsWith('M')]}" />
</util:map>
Dans l'exemple ci-dessus, SpEL est utilisé pour créer une collection des prénoms commençants par la lettre M.
Exemple : |
<util:map id="premierPrenomCommencantParM" value-type="java.lang.Object"
key-type="java.lang.String">
<entry key="M Premier" value="#{prenoms.^[startsWith('M')]}" />
<entry key="M Dernier" value="#{prenoms.$[startsWith('M')]}" />
</util:map>
Il est possible d'obtenir le premier élément correspondant à l'expression grâce à l'opérateur ^[selection-expression] ou le dernier élément grâce à l'opérateur $[selection-expression].
SpEL permet de faire une projection qui permet d'obtenir des données des éléments d'une collection pour créer une nouvelle collection.
L'opérateur de projection crée une nouvelle collection en évaluant une expression sur les éléments d'une collection source.
L'opérateur de projection possède la syntaxe ![expression] à utiliser sur une collection qui ne doit pas être de type Set.
SpEL permet la sélection dans une collection de type List.
Exemple : |
List<Integer> nombres = new ArrayList<Integer>(Arrays.asList(1, 2, 3, 4, 5,
6, 7, 8, 9, 10));
EvaluationContext context = new StandardEvaluationContext();
context.setVariable("list", nombres);
List<?> nombrePairs = EXPRESSION_PARSER.parseExpression(
"#list.?[#this%2==0]").getValue(context, List.class);
La variable #this fait référence à l'élément courant dans la liste durant son parcours.
SpEL permet la sélection dans une collection de type Map
SpEL permet d'obtenir une valeur d'une collection de type Map avec une syntaxe similaire à celle d'un tableau dans laquelle la valeur de la clé est fournie entre les crochets.
Exemple : |
Map<String, String> map = new HashMap<String, String>();
map.put("cle1", "valeur1");
EvaluationContext context = new StandardEvaluationContext();
context.setVariable("map", map);
Expression exp = EXPRESSION_PARSER.parseExpression("#map['cle1']");
String valeur = exp.getValue(context, String.class);
La projection de collections (Collection projection) permet d'extraire les éléments d'une collection en filtrant sur un motif commun aux différentes valeurs. Dans l'exemple ci-dessous, tous les nom* seront extraits.
Exemple : |
List<Personne> list = new ArrayList<Personne>(Arrays.asList(new Personne(
"noml"), new Personne("nom2"), new Personne("nom3")));
List<String> noms = (List<String>) EXPRESSION_PARSER.parseExpression(
"#root.![nom]").getValue(list);
A partir de la version 2.5 de Spring et en utilisant une version 5 ou supérieure de Java, il est possible d'utiliser des annotations pour réaliser une partie de la configuration des beans.
Comme lors de toutes utilisations d'annotations à la place d'un fichier XML, la configuration est décentralisée mais aussi grandement simplifiée tout en réduisant la taille du fichier de configuration.
Le conteneur doit être informé de l'utilisation d'annotations pour réaliser une partie de la configuration. L'espace de nommage context propose le tag <annotation-config> qui permet de préciser l'utilisation des annotations dans la configuration.
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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:annotation-config/>
</beans>
Spring 2.5 propose ses propres annotations et un support des annotations @Resource, @PostConstruct et @PreDestroy définies dans Java 5.
L'utilisation des annotations ne peut pas remplacer intégralement le fichier de configuration : il faut au moins y déclarer les PostBeanProssessor et certains composants techniques (DataSource, TransactionManager, ...) pour lesquels la déclaration n'est pas possible par annotations.
Il est aussi possible de mixer les déclarations en utilisant le fichier de configuration et les annotations. Cependant, pour faciliter la maintenance, il faut rester le plus cohérent possible, par exemple, en déclarant l'autowiring dans le fichier de configuration ou par annotations mais en éviter de panacher les deux.
Remarque : la configuration par le fichier XML est prioritaire sur la configuration faite avec les annotations. Ainsi, si un bean est déclaré comme étant un singleton dans le fichier de configuration et qu'il est annoté avec l'annotation @Scope("protype"), le bean sera géré comme un singleton par le conteneur.
Cette annotation introduite par Spring 2.5 permet de préciser le cycle de vie du bean. Elle s'applique sur une classe.
Les valeurs utilisables sont : singleton, prototype, session et request. Ces deux dernières ne sont utilisables que dans des applications web.
Comme pour la définition des beans dans le fichier de configuration, le scope par défaut est singleton pour un bean déclaré grâce aux annotations.
L'annotation @Scope permet de préciser une portée différente de singleton.
Exemple : |
@Controller
@Scope("prototype")
public class MonController {
// ...
}
L'annotation @Scope est prise en charge par le conteneur par un objet de type org.springframework.context.annotation.ScopeMetadataResolver.
Spring 2.5 propose la possibilité de réaliser une partie de la configuration relative à l'injection de dépendance grâce à des annotations.
Quatre annotations sont utiles dans ce but : @Autowired, @Configurable, @Qualifier et @Resource.
Il ne faut pas oublier de déclarer le namespace context et l'utilisation du schéma spring-context dans le fichier de configuration du contexte.
Le tag <annotation-config> va déclarer plusieurs BeanPostProcessor dont RequiredAnnotationBeanPostProcessor, AutowiredAnnotationBeanPostProcessor, CommonAnnotationBeanPostProcessor et PersistenceAnnotationBeanPostProcessor.
Il faut ensuite indiquer au conteneur les packages qu'il devra scanner pour rechercher les classes annotées. La balise <context:component-scan> n'est obligatoire que si les annotations concernant des stéréotypes sont utilisées.
Dans la configuration, il faut utiliser le tag <component-scan> de l'espace de nommage context. L'attribut base-package permet de préciser le nom du package : le contenu du package et de ses sous-packages seront scannés.
Exemple : |
<context:component-scan base-package="com.jmdoudoux.test.spring.services"/>
Il est possible d'avoir un contrôle très fin sur les classes à scanner en utilisant des filtres.
Exemple : |
<context:component-scan
base-package="com.jmdoudoux.test.spring.services" use-default-filters="false">
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Repository"/>
</context:component-scan>
L'annotation @Autowired permet de demander une injection automatique par type.
L'annotation @Qualifier permet de faciliter la détermination du bean à injecter grâce à l'annotation @Autowired dans le cas où plusieurs instances gérées par le conteneur correspondent au type souhaité.
L'annotation @Configurable permet de déclarer une classe dont les dépendances seront gérées par le conteneur alors que les instances ne le sont pas. Ces dépendances sont injectées lors de l'invocation du constructeur de la classe. Cette annotation requiert la mise en oeuvre d'AOP avec AspectJ.
L'annotation @Resource permet une injection automatique par nom : elle est définie dans la JSR 250.
L'annotation @org.springframework.beans.factory.annotation.Required introduite par Spring 2.0 permet de valider une injection de dépendances quelle que soit la méthode qui a permis cette injection.
Lors de la création d'une instance du bean, le conteneur va s'assurer que la propriété est valorisée à la fin de l'initialisation du bean soit explicitement soit via l'autowiring.
Si à la fin de l'initialisation du bean, la propriété n'est pas valorisée alors une exception de type org.springframework.beans.factory.BeanInitializationException est levée par le conteneur.
L'utilisation de cette annotation peut éviter d'avoir une exception de type NullPointerException levée lors de l'exécution des traitements de la classe.
L'annotation @org.springframework.beans.factory.annotation.Required s'utilise sur le setter d'une propriété car elle ne peut s'appliquer que sur une méthode.
Exemple : |
public class PersonneService {
private PersonneDao personneDao;
@Required
public void setPersonneDao(PersonneDao personneDao) {
this.personneDao = personneDao;
}
}
L'annotation @Required est traitée dans le conteneur par RequiredAnnotationBeanPostProcessor.
L'annotation @org.springframework.beans.factory.annotation.Autowired introduite par Spring 2.5 permet de faire de l'injection automatique de dépendances basée sur le type.
L'utilisation de l'espace de nommage context permet d'activer l'utilisation des annotations @Autowired en utilisant <context:annotation-config/>.
L'attribut required permet de préciser si l'injection d'une instance dans la propriété est obligatoire ou non. Par défaut, sa valeur est true.
Exemple : |
@Autowired(required=false)
private MaClasse maClasse;
Il est possible de préciser une valeur par défaut si l'injection automatique de la dépendance ne s'est pas faite.
Exemple : |
@Autowired(required=false)
private MaClasse maClasse = new MaClasseImpl;
L'annotation @Autowired s'utilise sur une propriété, un setter ou un constructeur.
Exemple d'injection sur un champ : |
public class PersonneService {
@Autowired
private PersonneDao personneDao;
public void setPersonneDao(PersonneDao personneDao) {
this.personneDao = personneDao;
}
// methodes de la classe qui peuvent utiliser la dependance
}
L'annotation @Autowired peut être utilisée sur une méthode ou un constructeur qui attend un ou plusieurs paramètres.
Exemple d'injection sur un constructeur : |
public class PersonneService {
private PersonneDao personneDao;
@Autowired
public PersonneService(PersonneDao personneDao) {
this.personneDao = personneDao;
}
}
Exemple d'injection sur un setter : |
public class PersonneService {
private PersonneDao personneDao;
@Autowired
public void setPersonneService(PersonneDao personneDao) {
this.personneDao = personneDao;
}
}
Exemple d'injection sur une méthode
Exemple : |
public class PersonneService {
private PersonneDao personneDao;
@Autowired
public void initialiser(PersonneDao personneDao, boolean utiliserCache) {
this.personneDao = personneDao;
}
}
L'annotation @Autowired peut être utilisée sur plusieurs constructeurs mais un seul constructeur peut être annoté avec @Autowired ayant l'attribut required à true (qui est la valeur par défaut).
@Autowired peut aussi être utilisée sur des tableaux et des collections typées (Collection, List, Set, Map). La collection sera alors automatiquement remplie avec les occurrences du type gérées par le conteneur.
Il est possible d'obtenir toutes les instances d'un type géré par le conteneur en appliquant @Autowired sur une propriété qui est un tableau ou une collection typée.
Exemple : |
public class MonService {
@Autowired
private BaseService[] services;
public void setServices(BaseService[] services) {
this.services = services;
}
}
Exemple : |
public class MonService {
private Set<BaseService> services;
@Autowired
public void setServices(Set<BaseService> services) {
this.services = services;
}
}
Il est aussi possible d'utiliser une collection de type Map avec des clés de type String qui seront les idenfiants des beans gérés par le conteneur, les beans eux-mêmes étant les valeurs associées.
Exemple : |
public class MonService {
private Map<String, BaseService> services;
@Autowired
public void setServices(Map<String, BaseService> services) {
this.services = services;
}
}
Par défaut, le conteneur va lever une exception s'il n'arrive pas à trouver une instance qui corresponde au type à auto injecter. L'attribut required de l'annotation @Autowired permet de modifier ce comportement : sa valeur par défaut est true. L'utilisation de cet attribut est préférable à l'utilisation de l'annotation @Required.
L'annotation @Autowired permet aussi l'injection d'une instance de plusieurs types d'objets de Spring notamment BeanFactory et ApplicationContext.
Exemple : |
public class MaClasse {
@Autowired
private ApplicationContext context;
}
L'annotation @Autowired est traitée dans le conteneur (BeanFactory ou ApplicationContext) par la classe AutowiredAnnotationBeanPostProcessor. Un AutowiredAnnotationBeanPostProcessor est automatiquement configuré en utilisant les annotations <context:component-scan> ou <context:annotation-config> dans le fichier de configuration.
Le conteneur va lever une exception de type BeanCreationException si :
L'attribut primary du tag <bean> permet de définir le bean qui sera utilisé prioritairement lors de l'injection de dépendance par type.
L'exemple ci-dessous est un exemple complet d'une petite application standalone qui demande un service au conteneur Spring et l'utilise pour afficher un message. Ce service possède une dépendance auto-injectée vers un DAO qui renvoie le message à afficher.
La fichier de configuration du contexte ne contient que deux tags.
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: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:annotation-config />
<context:component-scan base-package="com.jmdoudoux.test.spring"/>
</beans>
Le DAO est défini par une interface.
Exemple : |
package com.jmdoudoux.test.spring;
public interface MonDao {
public abstract String getMessage();
}
Le DAO implémente son interface.
Exemple : |
package com.jmdoudoux.test.spring;
import org.springframework.stereotype.Repository;
@Repository("monDao")
public class MonDaoImpl implements MonDao {
@Override
public String getMessage() {
return "Bonjour";
}
}
Le service est défini par une interface.
Exemple : |
package com.jmdoudoux.test.spring;
import org.springframework.stereotype.Service;
public interface MonService {
public void afficher();
}
Le service implémente son interface et possède une dépendance vers le DAO.
Exemple : |
package com.jmdoudoux.test.spring;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service("monService")
public class MonServiceImpl implements MonService {
@Autowired
private MonDaoImpl monDao;
@Override
public void afficher() {
System.out.println(monDao.getMessage());
}
}
Une classe de test permet de demander une instance au conteneur Spring et d'invoquer sa méthode afficher().
Exemple : |
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.jmdoudoux.test.spring.MonService;
public class Main {
private static ClassPathXmlApplicationContext appContext;
public static void main(final String[] args) {
appContext = new ClassPathXmlApplicationContext("application-context.xml");
try {
MonService monService = appContext.getBean(MonService.class);
monService.afficher();
} catch (final Exception e) {
e.printStackTrace();
System.exit(1);
}
}
}
Résultat : |
Bonjour
L'auto injection en utilisant les annotations a des avantages mais aussi des inconvénients.
L'injection est quelque chose qui doit être configuré mais l'utilisation des annotations nécessite une recompilation lors du changement ou de l'ajout d'une annotation. Généralement, ces changements imposent aussi des modifications dans le code, ce qui limite ce petit inconvénient.
L'annotation @Qualifier permet de qualifier le candidat à une injection automatique avec son nom. Ceci est particulièrement utile lorsque plusieurs instances sont du type à injecter.
Lors de la détermination d'une instance à injecter automatiquement par autowiring, se basant donc sur le type, il est possible que plusieurs instances gérées par le conteneur soit éligibles notamment lorsque le type à injecter est une interface ou une classe abstraite. Il faut alors aider le conteneur à choisir la bonne instance en utilisant l'annotation @Qualifier.
L'annotation org.springframework.beans.factory.annotation.Qualifier a été introduite par Spring 2.5. Elle peut s'appliquer sur un champ, une méthode ou un constructeur ou sur un paramètre d'un champ ou un constructeur.
Exemple sur un champ : |
@Autowired
@Qualifier("personne1")
private Personne personne;
Exemple sur un setter : |
@Autowired
public void setPersonne(@Qualifier("personne1") Personne personne) {
this.personne = personne;
}
Exemple sur un paramètre d'une méthode : |
@Autowired
public void initialiser(@Qualifier("personne1") Personne personne) {
this.personne = personne;
}
Exemple sur un constructeur : |
@Autowired
public PersonneService(@Qualifier("personne1") Personne personne) {
this.personne = personne;
}
L'annotation permet de préciser une valeur qui sera utilisée par le conteneur comme qualificateur pour déterminer le bean de l'instance à utiliser lors de l'injection.
L'annotation @Qualifier s'utilise en conjonction avec l'annotation @Autowired.
L'utilisation la plus facile de @Qualifier est de lui fournir en paramètre le nom du bean concerné et de l'appliquer sur la propriété qui sera injectée : dans ce cas l'injection se fait par nom et non par type, ce qui revient à utiliser l'annotation @Resource lorsqu'elle est utilisée sur un champ ou une méthode. Cependant, @Autowired et @Qualifier peuvent être utilisés sur un constructeur ou une méthode ayant plusieurs paramètres.
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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config />
<bean id="personneService" class="com.jmdoudoux.test.spring.service.PersonneServiceImpl" />
<bean id="personne1" class="com.jmdoudoux.test.spring.entite.Personne">
<property name="nom" value="nom1" />
<property name="prenom" value="prenom1" />
</bean>
<bean id="personne2" class="com.jmdoudoux.test.spring.entite.Personne">
<property name="nom" value="nom2" />
<property name="prenom" value="prenom2" />
</bean>
</beans>
Dans l'exemple ci-dessus, deux beans de type Personne sont déclarés. Leurs identifiants sont utilisés comme valeurs de l'attribut de l'annotation @Qualifier.
Exemple : |
package com.jmdoudoux.test.spring.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import com.jmdoudoux.test.spring.MonException;
import com.jmdoudoux.test.spring.entite.Personne;
public class PersonneServiceImpl implements PersonneService {
@Autowired(required = true)
@Qualifier("personne1")
private Personne personne1;
@Autowired(required = true)
@Qualifier("personne2")
private Personne personne2;
@Override
public void afficher() {
System.out.println(personne1);
System.out.println(personne2);
}
}
Il est aussi possible d'utiliser l'annotation @Qualifer sur la classe pour lui associer un qualificateur qui pourra être utilisé à la place du nom du bean.
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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.jmdoudoux.test.spring"/>
<bean id="personneService" class="com.jmdoudoux.test.spring.service.PersonneServiceImpl" />
</beans>
L'annotation @Qualifier est utilisée pour associer un qualificateur à la classe : c'est une sorte de marqueur qui va permettre de préciser lors d'une injection automatique par type que c'est une instance de ce type que l'on souhaite.
Exemple : |
package com.jmdoudoux.test.spring.entite;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Qualifier("pers1")
@Component("personne1")
public class Personne1 extends Personne {
}
Exemple : |
package com.jmdoudoux.test.spring.entite;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Qualifier("pers2")
@Component("personne2")
public class Personne2 extends Personne {
}
L'annotation @Qualifier est utilisée sur les propriétés à injecter pour préciser le qualificateur qui va permettre au conteneur de sélectionner l'instance à injecter.
Exemple : |
package com.jmdoudoux.test.spring.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import com.jmdoudoux.test.spring.entite.Personne;
public class PersonneServiceImpl implements PersonneService {
@Autowired(required = true)
@Qualifier("pers1")
private Personne personne1;
@Autowired(required = true)
@Qualifier("pers2")
private Personne personne2;
@Override
public void afficher() {
System.out.println(personne1);
System.out.println(personne2);
}
}
L'annotation @Autowired étant naturellement utilisée pour une injection par type, il est préférable de donner une valeur sémantique à un qualificateur.
Il est aussi possible de définir ses propres annotations qui seront des qualifiers : pour cela, il faut définir une annotation qui soit elle-même annotée avec @Qualifier.
Exemple : |
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MonQualifier {
String value();
}
Une fois défini, il est possible d'utiliser le nouveau qualifieur.
Exemple : |
@Autowired
@MonQualifier
private Personne personne;
Il est possible de définir des attributs pour un qualificateur personnalisé.
Exemple : |
@Qualifier
public @interface MonQualifier {
String indicateur();
int niveau();
}
Exemple : |
@Category(indicateur="employe", niveau="3")
public class Personne {
// ...
}
Il est aussi possible de définir un qualificateur dans le fichier de configuration donc sans utiliser d'annotation mais en déclarant un bean de type CustomAutowireConfigurer.
Exemple : |
<bean class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
<property name="customQualifierTypes">
<set>
<value>com.jmdoudoux.test.spring.MonQualifier</value>
<value>org.example.Offline</value>
</set>
</property>
</bean>
Pour qualifier un bean déclaré dans le fichier de configuration, il faut utiliser le tag <qualifier> fils du tag <bean>. Son attribut value permet de définir la valeur du qualificateur.
Exemple : |
<bean id="personne" class="com.jmdoudoux.test.spring.entite.Personne">
<qualifier type="MonQualifier" />
</bean>
L'annotation @javax.annotation.Resource permet de demander l'injection d'un bean par son nom.
Son support est proposé depuis la version 2.5 de Spring. La spécification de cette annotation est faite dans la JSR 250 (commons annotations).
L'annotation @Resource est fournie en standard avec Java SE 6 : pour l'utiliser avec Java 5, il faut ajouter le jar de l'implémentation de la JSR 250.
Elle s'utilise sur un champ ou une méthode.
Elle est prise en charge par le CommonAnnotationBeanPostProcessor.
Exemple : |
<bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor"/>
Sans attribut, cette annotation agit comme l'annotation @Autowired en permettant l'injection de beans. La différence est que @Resource propose la résolution par nom alors que @Autowired propose la résolution par type.
Exemple : |
public class PersonneDaoImpl implements PersonneDao {
@Resource
private DataSource dataSource;
}
Exemple : |
private DataSource dataSource;
@Resource
public void setDataSource(DataSource dataSource) {
this.dataSource = dtasource ;
}
L'annotation tente de déterminer le bean concerné en recherchant celui dont le nom correspond à l'attribut name de @Resource. Sinon, la recherche s'effectue sur le nom de la propriété s'il est fourni et, pour terminer, sur le type du bean puisque les recherches par noms ont échoué.
Pour demander l'injection d'un bean précis, il faut fournir son identifiant comme valeur de l'attribut name.
Exemple : |
public class PersonneDaoImpl implements PersonneDao {
@Resource(name="maDataSource")
private DataSource dataSource;
}
Exemple : |
private DataSource dataSource;
@Resource(name="maDataSource")
public void setDataSource(DataSource dataSource) {
this.dataSource = dtasource ;
}
Il est possible de désactiver la recherche par type si la recherche par nom échoue en passant la valeur false à la propriété fallbackToDefaultTypeMatch de l'instance de type CommonAnnotationBeanPostProcessor.
Exemple : |
<bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor">
<property name="fallbackToDefaultTypeMatch" value="false"/>
</bean>
La propriété alwaysUseJndiLookup est un booléen possédant la valeur false par défaut qui permet de demander la résolution de la dépendance annotée avec @Resource dans un annuaire JNDI.
Spring 2.0 permet d'utiliser l'AOP pour intercepter l'invocation d'un constructeur et réaliser l'injection des dépendances de la classe.
L'annotation @org.springframework.beans.factory.annotation.Configurable introduite par Spring 2.0 permet d'indiquer à Spring qu'il pourra injecter les dépendances d'un bean bien que son conteneur ne gère pas son cycle de vie.
L'annotation @Configurable permet de demander à Spring d'ajouter des aspects afin d'injecter les dépendances de la classe lors de l'invocation du constructeur. Ainsi, même si la classe n'est pas gérée par Spring, Spring sera en mesure d'injecter ses dépendances.
Typiquement cela concerne des objets qui ne sont pas gérés par le conteneur de Spring par exemple des objets du domaine ou une servlet.
Son exploitation se fait de façon transparente avec l'AOP : le bean est tissé avec des aspects qui vont assurer l'injection de dépendances notamment lors de l'invocation du constructeur.
Pour que l'annotation @Configurable soit prise en charge, il faut ajouter le tag <context:spring-configured/> dans la configuration du contexte Spring.
Exemple : |
<context:spring-configured />
Spring tisse les aspects qui vont se charger de réaliser l'injection des dépendances lors de l'invocation du constructeur de la classe. Ce tissage peut se faire de plusieurs manières :
Pour utiliser le tissage au runtime, il faut définir un agent au lancement de la JVM en lui ajoutant l'option "-javaagent:aspectjweaver-1.5.0.jar". Il est aussi nécessaire de rendre le fichier META-INF/aop.xml accessible dans le classpath
Exemple : |
<aspectj>
<weaver options="-verbose">
<include within="com.jmdoudoux.test.spring.*" />
<exclude within="*..*CGLIB*" />
</weaver>
</aspectj>
Les tags <include> et <exclude> permettent de limiter les classes qui seront concernées par le tissage. Cela améliore les performances de ce processus qui se limite aux classes concernées.
Dans l'exemple, les proxys générés par Hibernate sont exclus du tissage.
Chaque fois qu'une instance de la classe sera créée, Spring va assurer l'injection des dépendances qu'il gère. Ceci évite d'avoir à utiliser des paramètres de méthodes pour fournir les instances de ces dépendances.
Les dépendances qui seront à injecter peuvent être définies dans le fichier de configuration ou en utilisant l'annotation @Autowired.
Si les dépendances sont décrites dans le fichier de configuration, il faut fournir en attribut de l'annotation @Configurable l'id de la définition du bean à utiliser. Cette définition doit être faite dans le fichier de configuration du contexte Spring.
Exemple : |
<bean id="monBean" lazy-init="true">
<property name="maDependance" ref="MaDependanceImpl" />
</bean>
L'attribut de l'annotation @Configurable doit correspondre à la valeur de l'attribut id du tag <bean>.
L'exemple ci-dessous va définir un bean annoté avec @Configurable qui possède une dépendance vers une classe de type MonService qui sera injectée automatiquement par Spring grâce à l'annotation @Autowired.
Exemple : |
package com.jmdoudoux.test.spring.beans;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import com.jmdoudoux.test.spring.service.MonService;
@Configurable(dependencyCheck = true)
public class MonBean {
@Autowired
private MonService monService;
public boolean valider() {
return monService.validerDonnees(this);
}
}
Le service est décrit par une interface.
Exemple : |
package com.jmdoudoux.test.spring.service;
import com.jmdoudoux.test.spring.beans.MonBean;
public interface MonService {
boolean validerDonnees(MonBean bean);
}
Le service qui implémente l'interface définie est annoté avec @Service pour demander à Spring de gérer son cycle de vie.
Exemple : |
package com.jmdoudoux.test.spring.service;
import org.springframework.stereotype.Service;
import com.jmdoudoux.test.spring.beans.MonBean;
@Service
public class MonServicelmpl implements MonService {
public boolean validerDonnees(final MonBean bean) {
return true;
}
}
L'application crée une nouvelle instance de la classe MonBean et invoque sa méthode valider(). Spring va alors automatiquement injecter le service lors de l'invocation du constructeur.
Exemple : |
package com.jmdoudoux.test.spring;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.jmdoudoux.test.spring.beans.MonBean;
public class MonApp {
private static ClassPathXmlApplicationContext appContext;
public static void main(final String[] args) {
appContext = new
ClassPathXmlApplicationContext("conf/spring/context.xml");
try {
final MonBean monBean = new MonBean();
System.out.println(monBean.valider());
}
catch (final Exception e) {
e. printStackTrace();
System.exit(1);
}
System.exit(0);
}
}
Le fichier context.xml ne contient aucune description de beans.
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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.0.xsd"
<context:annotation-config />
<context:spring-configured />
<context:component-scan base-package="com.test.spring"/>
</beans>
Pour compiler et exécuter cet exemple, il faut que les jar requis soient dans le classpath :
spring-core-3.0.1.RELEASE.jar, spring-context-3.0.1.RELEASE.jar, spring-beans-3.0.1.RELEASE.jar, commons-logging-1.1.1. jar, spring-asm-3.0.1.RELEASE.jar, spring-expression-3.0.1.RELEASE.jar, spring-aop-3. 0. 1.RELEASE.jar, spring-aspects-3.0.1.RELEASE.jar, aspectjrt-1.6.8. jar, spring-tx-3.0.1.RELEASE.jar
A l'exécution, il faut utiliser l'option -javaagent de la JVM avec comme valeur le chemin vers la bibliothèque aspectjweaver.
exemple : -javaagent:lib/aspectjweaver-1.6.1.jar
Il est important que le tissage des aspects Spring soit réalisé au runtime comme dans l'exemple ou lors de la compilation pour que l'injection des dépendances soit réalisée.
L'utilisation de ce mécanisme reposant sur l'AOP, il faut tenir compte des contraintes de cette technologie :
L'exemple ci-dessous va définir une servlet qui utilise une classe ayant une dépendance vers un objet géré par le conteneur Spring.
Exemple : |
public class MaServlet extends HttpServlet {
private UserService userService = null;
@Override
protected void doGet(HttpServletRequest req,
HttpServletResponse resp) throws ServletException, IOException {
userService = new UserService();
String idUser = req.getParameter("idUser");
if (userService.isAdmin(idUser)) {
resp.sendRedirect("admin/index.jsp");
} else {
resp.sendRedirect("accueil.jsp");
}
}
}
La servlet utilise une classe MonService. Cette classe possède une dépendance vers une instance de la classe UserDao. L'injection sera réalisée par Spring lors de l'invocation du constructeur grâce à l'ajout d'aspects demandé par l'annotation @Configurable.
Exemple : |
package com.test.spring.services;
import com.test.spring.dao.UserDao;
@Configurable
public class UserServicelmpl implements UserService {
private UserDao userDao;
public boolean isAdmin(String idUser) {
User user = userDao.getById(idUser);
return user.isAdmin();
}
public UserDAO getUserDAO() {
return userDAO;
}
public void setUserDAO(UserDAO userDAO) {
this.userDAO = userDAO;
}
}
Le fichier de configuration contient la définition des beans et la déclaration de l'utilisation des annotations.
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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.0.xsd"
<bean id="userDao" class="com.test.spring.dao.UserDaoImpl" />
<bean class="com.test.spring.services.UserService">
<property name="userDao" ref="userDao"/>
</bean>
<context:spring-configured/>
<context:annotation-config/>
<context:component-scan base-package="com.test.spring"/>
<context:load-time-weaver/>
</beans>
Le tag <spring-configured> précise que les aspects seront tissés en fonction de la configuration de Spring.
Le tag <annotation-config> précise que Spring doit obtenir une partie de sa configuration à partir des annotations.
Le tag <load-time-weaver> permet de demander la mise en place d'un tisseur d'aspects qui va tisser les aspects au chargement des classes. L'utilisation de ce tag requière qu'un javaagent soit précisé au lancement de la JVM
Exemple .
-javaagent:/path/to/the/spring-agent. jar
Les bibliothèques de Spring 3.0.1 requises pour exécuter cet exemple sont : spring-core, spring-context, spring-beans, common-logging, spring-aop, spring-aspects, spring-expression, spring-tomcat-weaver, aspectjweaver, aspectjrt
Spring 2.5 propose un support des annotations standard définies dans la JSR 250.
Pour activer le support de ces annotations, il faut définir un bean de type CommonAnnotationBeanPostProcessor ou à partir de Spring 2.5, il est possible d'utiliser le tag <annotation-config> de l'espace de nommage context.
Cette annotation permet de marquer une méthode comme devant être exécutée à l'initialisation d'une nouvelle instance.
Exemple : |
public class MonBean {
@PostConstruct
public void initialiser() {
// ...
}
// ...
}
Elle est définie dans la JSR 250 et prise en charge par Spring en remplacement de l'utilisation de l'interface InitializingBean. L'utilisation de cette annotation est ainsi moins intrusive.
Elle peut aussi remplacer l'utilisation de la propriété init-method du tag <bean> dans le fichier de configuration.
Cette annotation permet de marquer une méthode comme devant être exécutée à la destruction d'une instance.
Exemple : |
public class MonBean {
@PreDestroy
public void detruire() {
// ...
}
// ...
}
Elle est définie dans la JSR 250 et prise en charge par Spring en remplacement de l'utilisation de l'interface DisposableBean. L'utilisation de cette annotation est ainsi moins intrusive.
Elle peut aussi remplacer l'utilisation de la propriété destroy-method du tag <bean> dans le fichier de configuration.
Spring propose plusieurs annotations qui permettent de marquer des classes avec des stéréotypes particuliers.
Le fait de marquer des classes avec une annotation relative à un stéréotype permet au framework d'effectuer des actions de définition dans la configuration Spring de ces classes.
Les différents stéréotypes possèdent chacun une annotation :
L'attribut par défaut name permet de préciser l'identifiant du bean.
Il est préférable d'utiliser une annotation plus spécifique telle que @Controller, @Service ou @Repository pour associer un stéréotype à une classe plutôt que d'utiliser @Component.
Tous les composants autodétectés sont implicitement nommés avec le nom de leur classe commençant par une minuscule.
Exemple : |
package com.jmdoudoux.test.spring.web.controller;
@Controller
public class MonController { ... }
L'exemple ci-dessous est équivalent à l'exemple précédent.
Exemple : |
<bean id="monController" class="com.jmdoudoux.test.spring.web.controller.MonController"/>
Il est possible de forcer explicitement le nom du bean en passant la valeur de ce nom en paramètre par défaut de l'annotation.
Exemple : |
@Controller("maPage")
public class MonController { ... }
L'exemple ci-dessus est équivalent à
Exemple : |
<bean id="maPage" class=" com.jmdoudoux.test.spring.web.controller.MonController"/>
Ainsi toute la partie relative à la déclaration de beans de type Service ou DAO peut être retirée du fichier de configuration et remplacée par l'utilisation des annotations. Le fichier de configuration est ainsi grandement allégé et la configuration est facilitée mais celle-ci n'est plus centralisée dans un ou plusieurs fichiers de configuration.
Spring 2.5 propose la classe ClassPathBeanDefinitionScanner qui se charge de rechercher les classes annotées avec des stéréotypes dans la liste de packages (et des sous-packages) fournie en paramètre.
L'utilisation de cette classe se fait dans le fichier de configuration en utilisant le tag <component-scan> du namespace context. Le tag <context:component-scan> du fichier de configuration demande au conteneur de rechercher des annotations particulières qui associent un stéréotype aux beans.
Exemple : |
<context:component-scan base-package="com.jmdoudoux.test.spring"/>
Il est possible de préciser plusieurs packages comme valeur de l'attribut base-package en les séparant par une virgule.
Exemple : |
<context:component-scan
base-package="com.jmdoudoux.test.spring.services, com.jmdoudoux.test.spring.dao"/>
Par défaut, toutes les classes annotées avec @Component, @Service, @Repository et @Controller sont détectées lors de la recherche.
Il est possible d'avoir un contrôle plus fin sur les packages à scanner en utilisant un filtre grâce aux tags <include-filter> ou <exclude-filter>.
Exemple : |
<context:component-scan base-package="com.jmdoudoux.test.spring">
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Service"/>
<context:include-filter type="assignable"
expression="com.jmdoudoux.test.spring.services.BaseService"/>
<context:include-filter type="aspectj"
expression="com.jmdoudoux.test.spring..*Service"/>
<context:include-filter type="custom"
expression=" com.jmdoudoux.test.spring.MonTypeFilterImpl"/>
<context:include-filter type="regex"
expression=" com.jmdoudoux.test.spring.services.*Service"/>
</context:component-scan>
L'attribut type permet de préciser le format du filtre à appliquer : il peut prendre plusieurs valeurs :
Valeur de l'attribut type |
Rôle |
Annotation |
Préciser un stéréotype |
Aspectj |
Utiliser une expression AspectJ |
Assignable |
Préciser une classe ou une interface qui peut être étendue ou implémentée |
Custom |
Une implémentation de l'interface org.springframework.core.type.TypeFilter |
Regex |
Utiliser une expression régulière |
La valeur du filtre est précisée avec l'attribut expression : la valeur doit être compatible avec le format précisé par l'attribut type.
Il est aussi possible de désactiver les filtres par défaut en passant la valeur false à l'attribut use-default-filters du tag <component-scan>
Exemple : |
<context:component-scan base-package="com.jmdoudoux.test.spring.web"
use-default-filters="false">
<context:include-filter type="assignable"
expression="com.jmdoudoux.test.spring.web.BaseController"/>
</context:component-scan>
Cette annotation introduite par Spring 2.5 permet de marquer une classe avec le stéréotype component.
Ce stéréotype désigne un composant générique géré par Spring qui sera autodétecté.
Cette annotation introduite par Spring 2.0 permet de marquer une classe avec le stéréotype repository. L'annotation @Repository est une spécialisation de l'annotation @Component.
Ce stéréotype désigne une classe qui est un DAO.
L'annotation @Repository permet au framework de traiter de façon générique les exceptions JDBC pour les transformer selon une hiérarchie de type DataAccessException. Ceci facilite la gestion des exceptions pour les services qui utilisent les DAO.
Cette annotation introduite par Spring 2.5 permet de marquer une classe avec le stéréotype service. L'annotation @Service est une spécialisation de l'annotation @Component.
Ce stéréotype désigne une classe qui est un service métier.
Cette annotation introduite par Spring 2.5 permet de marquer une classe avec le stéréotype @Controller. L'annotation @Controller est une spécialisation de l'annotation @Component.
Ce stéréotype désigne un contrôleur de Spring MVC.
Il faut annoter les Dao avec @Repository et les services avec @Service.
Il faut modifier le fichier de configuration pour ajouter la prise en charge des annotations et préciser les packages à scanner pour rechercher les annotations.
Il faut ajouter l'annotation @Autowired sur les attributs à injecter ou sur leurs setters publics. L'utilisation de setters facilite l'injection d'objets de type mock dans les tests unitaires.
Par défaut, l'injection automatique se fait en se basant sur le type. Pour que l'injection se base sur les noms, il faut fournir la valeur byName à l'attribut default-autowire dans la configuration.
Il faut supprimer la déclaration des beans annotés avec @Service et @Repository : attention les classes qui ne sont pas annotées avec ces deux annotations doivent toujours être définies dans la configuration (par exemple un DataSource, un TransactionManager ou une SessionFactory). Avant de les supprimer définitivement, il est préférable de réaliser les tests en mettant les définitions de ces beans en commentaires.
Spring 3.0 offre un support de la JSR 330 (Dependency Injection for Java) qui propose l'utilisation de plusieurs annotations standard pour l'injection de dépendances. La JSR 330 (Dependency Injection for Java) définit un ensemble d'annotations qui permet de définir des dépendances, leur fournisseur et leur portée.
L'API de la JSR 330 est très simple puisqu'elle contient une interface et 5 annotations. Les spécifications de la JSR ont été gérées par Google et Spring. Il est donc normal que Spring implémente cette JSR.
Les annotations proposées par la JSR 330 peuvent être utilisées à la place de celles équivalentes proposées par Spring. L'intérêt d'utiliser les annotations JSR 330 est qu'elles rendent le code moins dépendant de la solution d'injection de dépendance utilisée (Spring, CDI, Guice, ...).
Pour utiliser les annotations de la JSR 330, des beans de type BeanPostProcessor doivent être définis dans le contexte : le plus simple est d'utiliser le tag <context :annotation-config> ou le tag <context:component-scan>
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: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="com.jmdoudoux.test.spring" />
</beans>
Pour utiliser les annotations de la JSR 330 avec Spring, il faut aussi ajouter la bibliothèque de l'API dans le classpath par exemple celle fournie par Spring : com.springsource.javax.inject-1.0.0.jar.
L'annotation @Inject est similaire à l'annotation @Autowired.
L'annotation @Inject permet d'identifier une variable dont le type est une classe ou une interface et dont le contenu sera injecté par le conteneur.
Exemple : |
package com.jmdoudoux.test.spring;
import javax.inject.Inject;
import org.springframework.stereotype.Service;
@Service("monService")
public class MonServiceImpl implements MonService {
@Inject
private MonDao monDao;
@Override
public void afficher() {
System.out.println(monDao.getMessage());
}
}
La principale différence entre @Autowired et @Inject est que cette dernière ne possède pas d'attribut required qui permet d'indiquer que l'injection est optionnelle : l'injection avec @Inject doit toujours se faire sur une instance.
L'annotation @javax.inject.Qualifier permet de définir une métadonnée sur un bean. Cette annotation permet notamment de définir d'autres annotations qui permettront de qualifier des beans.
Son but est de préciser au conteneur l'instance qui sera injectée automatiquement notamment lorsque plusieurs types peuvent être choisis par le conteneur.
Spring possède sa propre annotation @Qualifier et la JSR 330 possède une annotation @Qualifier : il ne faut donc pas confondre l'annotation @javax.inject.Qualifer et l'annotation @Qualifier de Spring. Bien que leurs noms soient identiques, leurs rôles sont différents : elles ne peuvent donc pas être interverties.
L'annotation @Qualifier de Spring est utilisée comme discriminant pour déterminer le type à utiliser lorsque plusieurs candidats sont possibles pour être injectés. Le discriminant est la valeur de l'attribut qui lui est passé en paramètre.
L'annotation @javax.inject.Qualifier peut être utilisée de différentes manières avec Spring.
La première possibilité est de définir sa propre annotation personnalisée qui sera utilisée pour qualifier une dépendance injectée automatiquement et définie dans le fichier de configuration. Cette annotation utilise une valeur fournie en paramètre pour préciser le qualificateur.
Exemple : |
package com.jmdoudoux.test.spring;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.inject.Qualifier;
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MonQualifier {
String value();
}
L'annotation @MonQualifier est elle-même annotée avec @Qualifier. Elle peut être utilisée pour qualifier une dépendance.
Exemple : |
package com.jmdoudoux.test.spring;
import javax.inject.Inject;
public class DepartementServiceImpl implements DepartementService {
@Inject
@MonQualifier("directeurGeneral")
private Personne directeurGeneral;
@Inject
@MonQualifier("directeurAdjoint")
private Personne directeurAdjoint;
@Override
public void afficher() {
System.out.println("Directeur general : " + directeurGeneral.getNom());
System.out.println("Directeur adjoint : " + directeurAdjoint.getNom());
}
// ...
}
Les identifiants des beans doivent correspondre aux valeurs fournies en paramètre de l'annotation.
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: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:annotation-config />
<bean id="directeurGeneral" class="com.jmdoudoux.test.spring.Personne">
<constructor-arg name="nom" value="NomDG" />
</bean>
<bean id="directeurAdjoint" class="com.jmdoudoux.test.spring.Personne">
<constructor-arg name="nom" value="NomDA" />
</bean>
<bean id="departementService"
class="com.jmdoudoux.test.spring.DepartementServiceImpl" />
</beans>
La seconde possibilité utilise aussi une annotation @Qualifier personnalisée pour qualifier la dépendance mais aussi les beans eux-mêmes. Cette solution repose entièrement sur les annotations : le fichier de configuration est alors simpliste.
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: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="com.jmdoudoux.test.spring" />
</beans>
L'annotation est utilisée pour qualifier un bean dans sa définition.
Exemple : |
package com.jmdoudoux.test.spring;
import org.springframework.stereotype.Component;
@MonQualifier("directeurGeneral")
@Component
public class PersonneDirecteurGeneral extends Personne {
public PersonneDirecteurGeneral() {
super("NomDG");
}
public PersonneDirecteurGeneral(final String nom) {
super(nom);
}
// ...
}
Exemple : |
package com.jmdoudoux.test.spring;
import org.springframework.stereotype.Component;
@MonQualifier("directeurAdjoint")
@Component
public class PersonneDirecteurAdjoint extends Personne {
public PersonneDirecteurAdjoint() {
super("NomDA");
}
public PersonneDirecteurAdjoint(final String nom) {
super(nom);
}
// ...
}
La troisième solution est de définir une annotation @Qualifier personnalisée pour chaque qualificateur.
Exemple : |
package com.jmdoudoux.test.spring;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.inject.Qualifier;
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface DirecteurGeneral {
}
Exemple : |
package com.jmdoudoux.test.spring;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.inject.Qualifier;
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface DirecteurAdjoint {
}
Ces deux annotations sont utilisées pour qualifier les beans dans leur définition.
Exemple : |
package com.jmdoudoux.test.spring;
import org.springframework.stereotype.Component;
@DirecteurGeneral
@Component
public class PersonneDirecteurGeneral extends Personne {
public PersonneDirecteurGeneral() {
super("NomDG");
}
public PersonneDirecteurGeneral(final String nom) {
super(nom);
}
// ...
}
Exemple : |
package com.jmdoudoux.test.spring;
import org.springframework.stereotype.Component;
@DirecteurAdjoint
@Component
public class PersonneDirecteurAdjoint extends Personne {
public PersonneDirecteurAdjoint() {
super("NomDA");
}
public PersonneDirecteurAdjoint(final String nom) {
super(nom);
}
// ...
}
Les annotations personnalisées sont aussi utilisées pour qualifier les dépendances.
Exemple : |
package com.jmdoudoux.test.spring;
import javax.inject.Inject;
import org.springframework.stereotype.Service;
@Service("departementService")
public class DepartementServiceImpl implements DepartementService {
@Inject
@DirecteurGeneral
private Personne directeurGeneral;
@Inject
@DirecteurAdjoint
private Personne directeurAdjoint;
@Override
public void afficher() {
System.out.println("Directeur general : " + directeurGeneral.getNom());
System.out.println("Directeur adjoint : " + directeurAdjoint.getNom());
}
// ...
}
Cette troisième solution nécessite la création de plusieurs annotations mais elle offre un typage fort qui peut éviter des erreurs liées à l'utilisation de chaînes de caractères.
L'annotation @javax.inject.Named est similaire à l'annotation @Qualifier de Spring.
L'annotation @javax.inject.Named est une version particulière de l'annotation @javax.inject.Qualifier : cette annotation peut être vue comme un qualificateur car @Named est annotée avec @Qualifier.
L'annotation @Named permet d'associer un nom à un bean. Si aucun nom n'est fourni en paramètre, le nom du bean sera le nom de la classe avec sa première lettre en minuscule.
Exemple : |
package com.jmdoudoux.test.spring;
import javax.inject.Inject;
import javax.inject.Named;
import org.springframework.stereotype.Service;
@Service("monService")
public class MonServiceImpl implements MonService {
@Inject
@Named("monDaoSecondaire")
private MonDao monDao;
@Override
public void afficher() {
System.out.println(monDao.getMessage());
}
}
Exemple : |
package com.jmdoudoux.test.spring;
import org.springframework.stereotype.Repository;
@Repository("monDaoPrimaire")
public class MonDaoImpl implements MonDao {
@Override
public String getMessage() {
return "Bonjour primaire";
}
}
Exemple : |
package com.jmdoudoux.test.spring;
import org.springframework.stereotype.Repository;
@Repository("monDaoSecondaire")
public class MonDaoImpl2 implements MonDao {
@Override
public String getMessage() {
return "Bonjour secondaire";
}
}
Résultat : |
15 mai 2011 16:36:17 org.springframework.context.support.AbstractApplicationContext
prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@15eb0a9:
startup date [Sun May 15 16:36:17 CEST 2011]; root of context hierarchy
15 mai 2011 16:36:17 org.springframework.beans.factory.xml.XmlBeanDefinitionReader
loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [application-context.xml]
15 mai 2011 16:36:17 org.springframework.context.annotation.
ClassPathScanningCandidateComponentProvider registerDefaultFilters
INFO: JSR-330 'javax.inject.Named' annotation found and supported for component scanning
15 mai 2011 16:36:17 org.springframework.beans.factory.annotation.
AutowiredAnnotationBeanPostProcessor <init>
INFO: JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
15 mai 2011 16:36:17 org.springframework.beans.factory.support.DefaultListableBeanFactory
preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.
DefaultListableBeanFactory@18f6235: defining beans
[org.springframework.context.annotation.internalConfigurationAnnotationProcessor,
org.springframework.context.annotation.internalAutowiredAnnotationProcessor,
org.springframework.context.annotation.internalRequiredAnnotationProcessor,
org.springframework.context.annotation.internalCommonAnnotationProcessor,
monDaoPrimaire,monDaoSecondaire,monService]; root of factory hierarchy
Bonjour secondaire
Spring permet une utilisation de ses propres annotations et des annotations de la JSR 330 : le choix d'utiliser les unes ou les autres dépend des besoins et de l'approche souhaitée. Les annotations de la JSR 330 rendent le code moins dépendant de la solution IoC utilisée mais les annotations de Spring proposent quelques petites fonctionnalités supplémentaires.
Il existe aussi quelques subtilités mineures entre des annotations équivalentes dont il faut tenir compte notamment dans le cas d'une migration.
Enfin il est possible de mixer l'utilisation de ces annotations dans une même application même si cela peut rendre la configuration moins homogène.
Il faut :
Exemple : |
package com.jmdoudoux.test.spring;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@Service("monService")
public class MonServiceImpl implements MonService {
@Autowired
@Qualifier("monDaoSecondaire")
private MonDao monDao;
@Override
public void afficher() {
System.out.println(monDao.getMessage());
}
}
Exemple : |
package com.jmdoudoux.test.spring;
import javax.inject.Inject;
import javax.inject.Named;
@Named("monService")
public class MonServiceImpl implements MonService {
@Inject
@Named("monDaoSecondaire")
private MonDao monDao;
@Override
public void afficher() {
System.out.println(monDao.getMessage());
}
}
Avec Spring 3.0, il est possible de configurer le contexte en utilisant des annotations au lieu du fichier de configuration XML.
Spring 3.0 propose plusieurs annotations issues du projet JavaConfig pour définir certaines fonctionnalités de la configuration dans le code Java notamment @Configuration et @Bean.
Une classe annotée avec @Configuration permet de demander au conteneur d'utiliser cette classe pour instancier des beans. L'annotation @Bean s'utilise sur une méthode d'une classe annotée avec @Configuration qui crée une instance d'un bean.
L'utilisation de ces annotations requiert que le tag <context:component-scan> soit utilisé dans la configuration XML du contexte de Spring.
Exemple : |
package com.jmdoudoux.test.spring;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.jmdoudoux.test.spring.dao.MonDao;
import com.jmdoudoux.test.spring.dao.MonDaoImpl;
import com.jmdoudoux.test.spring.service.MonService;
import com.jmdoudoux.test.spring.service.MonServiceImpl;
@Configuration
public class AppConfiguration {
@Bean
public MonService monService() {
return new MonServiceImpl();
}
@Bean
public MonDao monDao() {
return new MonDaoImpl();
}
}
La classe AnnotationConfigApplicationContext doit être utilisée pour créer le contexte Spring à partir des classes de l'application utilisant les annotations @Configuration et @Bean.
La classe AnnotationConfigApplicationContext possède plusieurs constructeurs.
Il est possible de fournir en paramètre les instances de type Class des classes à scanner.
Exemple : |
package com.jmdoudoux.test.spring;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class AnnotationConfigTest {
public void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfiguration.class);
MonServiceImpl monService = ctx.getBean(MonServiceImpl.class);
}
}
La classe AnnotationConfigApplicationContext est aussi capable de scanner des répertoires pour trouver les classes annotées avec @Configuration en les passant en paramètres du constructeur.
Exemple : |
package com.jmdoudoux.test.spring;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class AnnotationConfigTest {
public void main(String[] args) {
ApplicationContext ctx =
new AnnotationConfigApplicationContext("com.jmdoudoux.test.spring");
MonServiceImpl monService = ctx.getBean(MonServiceImpl.class);
}
}
Les méthodes register(Class< ?>...) et scan(String...) permettent respectivement de préciser un ensemble de classes et de pckages qui seront scannées à la recherche des annotations @Configuration et @Bean pour enrichir la configuration du context.
Il est important d'invoquer la méthode refresh() de la classe AbstractApplicationContext pour prendre en compte les informations de la configuration trouvées.
L'annotation @Configuration permet d'indiquer qu'une classe contient des méthodes qui seront utilisées par le conteneur Spring pour créer des instances : elle est donc à utiliser sur une classe dont le rôle est de créer des instances de beans.
Cette annotation est similaire au tag <beans> du fichier de configuration.
Exemple : |
package com.jmdoudoux.test.spring;
@Configuration
public class ServiceFactory {
@Bean
public MonService monService() {
return new MonServiceImpl();
}
}
Cet exemple est équivalent à la définition de contexte ci-dessous
Exemple : |
<beans>
<bean id="monService" class="com.jmdoudoux.test.spring.MonServiceImpl"/>
</beans>
Pour utiliser l'annotation @Configuration, il faut obligatoirement que les bibliothèques asm et CGLIB soient dans le classpath.
La bibliothèque asm est utilisée pour modifier ou générer du bytecode au runtime : elle était livrée avec Spring jusqu'à la version 2.5. A partir de la version 3.0, la bibliothèque asm n'est plus fournie avec Spring : elle doit être téléchargée séparément à l'url http://asm.ow2.org/download/index.phpl.
Si la bibliothèque asm n'est pas trouvée dans le classpath, une exception de type java.lang.ClassNotFoundException est levée pour le type org.objectweb.asm.Type
La bibliothèque cglib est aussi utilisée par Spring pour manipuler les classes au runtime. Comme asm, elle n'est aussi pas fournie dans la version 3.0 de Spring. Elle est téléchargeable à l'url http://cglib.sourceforge.net/.
Si la bibliothèque cglib n'est pas trouvée, alors une exception de type java.lang.IllegalStateException est levée avec le message « CGLIB is required to process @Configuration classes ».
Les classes annotées avec @Configuration doivent respecter certaines contraintes :
L'annotation @Configuration est elle-même annotée avec @Component : les classes annotées avec @Configuration sont scannées et peuvent donc utiliser l'annotation @Autowired sur des champs ou des méthodes.
Cette annotation est à utiliser sur une méthode qui se charge de créer une instance d'un bean.
L'annotation @Bean indique au conteneur que la méthode pour être utilisée pour créer une instance d'un bean. L'identifiant est fourni en tant qu'attribut name de l'annotation ou, à défaut, correspond au nom de la méthode avec la première lettre en minuscule. L'attribut name est un tableau de chaînes de caractères ce qui permet de préciser des alias.
Cette annotation est similaire au tag <bean> du fichier de configuration.
Elle possède plusieurs attributs :
Attribut |
Rôle |
String init-method |
Nom d'une méthode du bean qui sera invoquée lors de l'initialisation du bean (optionnel) |
String destroy-method |
Nom d'une méthode du bean qui sera invoquée lorsque le bean sera retiré du conteneur (optionnel) |
Autowire autowire |
Permet de préciser si les dépendances doivent être injectées automatiquement et si oui dans quel mode. Les valeurs possibles sont Autowire.BY_NAME, Autowire.BY_TYPE et Autowire.NO (valeur par défaut Autowire.NO) |
String[] name |
Identifiant et alias du bean |
L'annotation @Bean ne possède pas d'attribut qui permette de préciser les propriétés scope, lazy et primary. Pour cela, il faut utiliser les annotations @Scope, @Lazy et @Primary.
Par défaut, c'est le nom de la méthode annotée avec @Bean qui sera utilisé pour le nom du bean.
Exemple XML
Exemple : |
<beans>
<bean name="monBean" class="com.jmdoudoux.test.spring.MonBean">
</beans>
Exemple avec annotations
Exemple : |
@Configuration
public class AppConfig {
@Bean
public MonBean monBean() { return new MonBean();}
}
L'annotation @Scope peut être utilisée pour préciser la portée des instances créées. L'annotation @Scope s'utilise dans ce cas sur des méthodes annotées avec @Bean.
L'annotation @Bean peut aussi être utilisée sur un bean annoté avec @Component. Dans ce cas, l'annotation @Bean est équivalente à l'élément factory-method du fichier de configuration XML.
Cette annotation permet de préciser des beans dont dépend le bean annoté avec @DependsOn : le conteneur garantit que les beans précisés en paramètre de cette annotation seront créés avant le bean annoté.
Le rôle de cette annotation est similaire à l'attribut depends-on du tag <bean> dans le fichier de configuration.
L'annotation @DependsOn peut être utilisée sur une classe annotée avec @Component ou sur une méthode annotée avec @Bean.
L'annotation @Primary permet de préciser que cette classe doit être privilégiée lors d'une injection de type autowire et que plusieurs classes peuvent être injectées. Si une seule de ces classes est annotée avec @Primary, c'est une de ces instances qui sera injectée.
Le rôle de cette annotation est similaire à l'attribut primary du tag <bean> dans le fichier de configuration.
L'annotation @Primary peut être utilisée sur une classe annotée avec @Component ou sur une méthode annotée avec @Bean.
Cette annotation est à utiliser sur une classe et permet de demander l'initialisation tardive d'un bean de type singleton. Si cette annotation est utilisée avec la valeur true, l'instance du bean ne sera initialisée que lorsqu'un autre bean en aura besoin.
L'annotation @Lazy peut être utilisée sur une classe annotée avec @Component ou @Configuration ou sur une méthode annotée avec @Bean. Utilisée sur une classe avec @Configuration, elle permet de demander l'initialisation de tous les beans créés par les méthodes annotées avec @Bean.
L'annotation @Import permet de demander l'importation de classes annotées avec @Configuration : les beans créés par les méthodes annotées @Bean des classes fournies en paramètre pourront être injectées au moyen de l'annotation @Autowired.
L'annotation @Import est équivalente au tag <import> du fichier de configuration XML.
L'annotation @ImportResource permet de demander l'importation de ressources qui contiennent la définition de beans.
L'annotation @ImportResource est équivalente au tag <import> du fichier de configuration XML.
L'annotation @Value permet de préciser une valeur par défaut qui sera le résultat de l'évaluation de l'expression Spring EL fournie en paramètre.
L'annotation @Value peut être utilisée sur un champ ou un paramètre d'une méthode ou d'un constructeur.
Spring 3.0 propose une mise en oeuvre simplifiée du scheduling notamment en proposant un espace de nommage dédié et des annotations.
Le namespace task peut être utilisé pour planifier l'exécution de méthodes de beans. L'espace de nommage doit être déclaré dans la configuration du contexte.
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:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task-3.0.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
</beans>
L'espace de nommage comprend le tag <scheduler> qui définit un objet de type ThreadPoolTaskExecutor. Le tag <scheduler> possède plusieurs attributs :
Attributs |
Rôle |
id |
Identifiant du pool : sera utilisé comme préfixe pour le nom des threads du pool |
pool-size |
Permet de préciser le nombre de threads dans le pool. Par défaut, il n'y a qu'un seul thread |
Exemple : |
<task:scheduler id="monScheduler" pool-size="10" />
Le tag <scheduled-taks> permet d'associer des tâches au scheduler. Une tâche est définie par une méthode d'un bean géré par Spring dont l'exécution est planifiée.
Chaque tâche est définie avec un tag <scheduled> qui possède plusieurs attributs :
Attribut |
Rôle |
ref |
Préciser l'identifiant du bean qui sera utilisé par la tâche |
method |
Préciser le nom de la méthode qui sera exécutée par la tâche |
cron |
Planifier l'exécution grâce à une expression de type cron |
fixed-delay |
Planifier l'exécution grâce à une durée exprimée en millisecondes qui précise le temps d'attente entre deux exécutions. Ce temps démarre à la fin de l'exécution courante : une même tâche ne peut pas être exécutée plusieurs fois à un même instant. |
fixed-rate |
Planifier l'exécution grâce à une durée exprimée en millisecondes qui précise le temps d'attente entre deux exécutions. Si le temps d'exécution de la tâche est plus long que le temps précisé en paramètre alors la tâche aura plusieurs exécutions en cours |
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:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:task="http://www.springframework.org/schema/task"
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-3.0.xsd
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task-3.0.xsd">
<task:scheduled-tasks scheduler="monScheduler">
<task:scheduled ref="maTache" method="executer"
fixed-delay="5000" />
</task:scheduled-tasks>
<task:scheduler id="monScheduler" pool-size="10" />
<bean id="maTache" class="com.jmdoudoux.test.spring.task.MaTache"/>
</beans>
Le bean qui encapsule les traitements à exécuter doit respecter deux contraintes : la méthode exécutée ne doit rien retourner et ne doit pas avoir de paramètre.
Exemple : |
package com.jmdoudoux.test.spring.task;
import java.util.Date;
public class MaTache {
public void executer() {
System.out.println("Excution de la tache MaTache " + new Date());
}
}
Spring 3.0 propose de mettre en oeuvre le scheduling grâce à des annotations.
L'annotation @Scheduler permet de planifier l'exécution de la méthode annotée.
L'annotation @Async permet l'exécution de la méthode annotée de manière asynchrone.
L'utilisation de ces annotations n'est possible que si le tag <task:annotation-driven> est utilisé dans le fichier de configuration du contexte.
Il possède plusieurs attributs :
Attributs |
Rôle |
executor |
Permet de fournir une instance de TaskExecutor qui sera utilisée lors de l'exécution des méthodes de manière asynchrone |
Scheduler |
Permet de fournir une instance de TaskScheduler qui sera utilisée pour planifier les tâches définies avec @Scheduler |
Exemple : |
<task:annotation-driven executor="monExecutor" scheduler="monScheduler" />
<task:executor id="monExecutor" pool-size="10" />
<task:scheduler id="monScheduler" pool-size="10" />
Le tag <executor> permet de définir un pool de TaskExecutor pour prendre en charge l'invocation asynchrone des méthodes annotées avec @Async.
Le tag <scheduler> permet de définir un pool de ThreadPoolTaskExecutor pour prendre en charge l'exécution des tâches définies avec l'annotation @Scheduled.
La configuration peut se faire avec l'annotation @Scheduled sur une méthode qui ne doit rien retourner et ne posséder aucun paramètre.
L'attribut fixedDelay permet de définir la configuration avec une période fixe : il permet de définir le temps d'attente entre la fin de l'exécution et le lancement de la suivante.
Exemple : |
package com.jmdoudoux.test.spring.task;
import java.util.Date;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
@Service
public class MaTache {
public MaTache() {
System.out.println("instanciation de la classe MaTache");
}
@Scheduled(fixedDelay = 5000)
public void executer() {
System.out.println("Exécution de la tâche MaTache " + new Date());
}
}
L'attribut fixedRate permet de définir la configuration avec un délai fixe : il permet de définir le temps d'attente entre deux exécutions : ce temps démarre dès l'exécution courante.
Exemple : |
@Scheduled(fixedRate = 60000)
public void executer() {
System.out.println("Exécution de la tâche MaTache " + new Date());
}
L'attribut cron permet de définir la configuration avec une syntaxe proche de celle de la commande Unix cron
Exemple : |
@Scheduled(cron="0/10 * * * * ?")
public void executer() {
System.out.println("Exécution de la tâche MaTache " + new Date());
}
Il faut s'assurer qu'il n'y aura qu'une seule instance de la classe dont les méthodes sont annotées avec @Scheduled.
Il est nécessaire que les bibliothèques requises pour la mise en oeuvre de l'AOP soient présentes dans le classpath : org.springframework.aop.-3.0.5.jar et com.springsource.org.aopalliance-1.0.0.jar.
Le fichier de configuration du contexte est très simple :
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:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:task="http://www.springframework.org/schema/task"
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-3.0.xsd
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task-3.0.xsd">
<context:component-scan base-package="com.jmdoudoux.test.spring.task" />
<task:annotation-driven />
</beans>
L'annotation @Async permet de définir l'invocation d'une méthode de manière asynchrone : l'appel est délégué à un TaskExecutor.
La méthode annotée avec @Async peut avoir des paramètres et par défaut ne renvoie rien.
Exemple : |
package com.jmdoudoux.test.spring.task;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Component
public class MonTraitement {
@Async()
public void executer(final int numero) {
String nomThread = Thread.currentThread().getName();
System.out.println(" " + nomThread + " début du traitement " + numero);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println(" " + nomThread + " fin du traitement " + numero);
}
}
Une tâche avec une exécution périodique est planifiée pour lancer les traitements asynchrones.
Exemple : |
package com.jmdoudoux.test.spring.task;
import java.util.Date;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
@Service
public class MaTache {
@Autowired
private MonTraitement monTraitement;
public MaTache() {
System.out.println("instanciation de la classe MaTache");
}
@Scheduled(fixedDelay = 15000)
public void executer() {
System.out.println("Début exécution de la tâche MaTache " + new Date());
for (int i = 1; i < 6; i++) {
monTraitement.executer(i);
}
System.out.println("Fin exécution de la tâche MaTache " + new Date());
}
}
Lors de l'exécution de la tâche, 5 threads sont lancés pour exécuter les traitements.
Résultat : |
instanciation de la classe MaTache
Déebut exécution de la tâche MaTache Sun Jun 05 18:31:20 CEST 2011
Fin exécution de la tâche MaTache Sun Jun 05 18:31:20 CEST 2011
SimpleAsyncTaskExecutor-2 début du traitement 2
SimpleAsyncTaskExecutor-4 début du traitement 4
SimpleAsyncTaskExecutor-1 début du traitement 1
SimpleAsyncTaskExecutor-3 début du traitement 3
SimpleAsyncTaskExecutor-5 début du traitement 5
SimpleAsyncTaskExecutor-1 fin du traitement 1
SimpleAsyncTaskExecutor-2 fin du traitement 2
SimpleAsyncTaskExecutor-4 fin du traitement 4
SimpleAsyncTaskExecutor-3 fin du traitement 3
SimpleAsyncTaskExecutor-5 fin du traitement 5
Début exécution de la tâche MaTache Sun Jun 05 18:31:35 CEST 2011
Fin exécution de la tâche MaTache Sun Jun 05 18:31:35 CEST 2011
SimpleAsyncTaskExecutor-6 début du traitement 1
SimpleAsyncTaskExecutor-8 début du traitement 3
SimpleAsyncTaskExecutor-10 début du traitement 5
SimpleAsyncTaskExecutor-7 début du traitement 2
SimpleAsyncTaskExecutor-9 début du traitement 4
Par défaut le nom des threads commence par « SimpleAsyncTaskExecutor ». Il est possible de modifier ce préfixe en précisant un identifiant au TaskExecutor.
Exemple : |
<task:annotation-driven executor="monExecutor" scheduler="monScheduler" />
<task:executor id="monExecutor" pool-size="10" />
<task:scheduler id="monScheduler" pool-size="10" />
Résultat : |
instanciation de
la classe MaTache
Début exécution de la tâche MaTache Sun Jun 05 18:27:56 CEST 2011
Fin exécution de la tâche MaTache Sun Jun 05 18:27:56 CEST 2011
monExecutor-2 début du traitement 2
monExecutor-4 début du traitement 4
monExecutor-1 début du traitement 1
monExecutor-3 début du traitement 3
monExecutor-5 début du traitement 5
monExecutor-2 fin du traitement 2
monExecutor-4 fin du traitement 4
monExecutor-1 fin du traitement 1
monExecutor-3 fin du traitement 3
monExecutor-5 fin du traitement 5
Debut exécution de la tâche MaTache Sun Jun 05 18:28:11 CEST 2011
monExecutor-6 debut du traitement 1
Fin exécution de la tâche MaTache Sun Jun 05 18:28:11 CEST 2011
monExecutor-8 début du traitement 3
monExecutor-10 début du traitement 5
monExecutor-7 début du traitement 2
monExecutor-9 début du traitement 4
La méthode invoquée de manière asynchrone peut avoir une valeur de retour qui doit être du type Future<T>.
Exemple : |
package com.jmdoudoux.test.spring.task;
import java.util.Date;
import java.util.concurrent.Future;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Component;
@Component
public class MonTraitement {
@Async
public Future<Personne> rechercher(final long id) {
System.out.println(new Date()
+ " MonTraitement debut invocation rechercher");
Personne personne = new Personne("Nom" + id);
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println(new Date() + " MonTraitement fin invocation rechercher");
return new AsyncResult<Personne>(personne);
}
}
La valeur de retour doit être fournie en paramètre du constructeur d'une instance de type AsyncResult().
La méthode get() de l'interface Future permet d'obtenir la valeur de retour avec une attente éventuelle si le traitement asynchrone n'est pas encore terminé.
Exemple : |
package com.jmdoudoux.test.spring.task;
import java.util.Date;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class MaTache {
@Autowired
private MonTraitement monTraitement;
public MaTache() {
System.out.println("instanciation de la classe MaTache");
}
public void rechercher() {
System.out.println(new Date()
+ " MaTache Debut invocation methode rechercher()");
Future<Personne> resultat = monTraitement.rechercher(1234L);
try {
System.out.println(new Date() + " MaTache debut traitement");
Thread.sleep(5000);
System.out.println(new Date() + " MaTache fin traitement");
} catch (InterruptedException e) {
}
System.out.println(new Date() + " MaTache Obtention du resultat");
try {
System.out.println(new Date() + " MaTache Personne = " + resultat.get());
} catch (InterruptedException e) {
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println(new Date()
+ " MaTache Fin invocation methode rechercher()");
}
}
Si une exception est levée durant les traitements, elle est encapsulée dans une exception de type ExecutionException qui sera levée par la méthode get().
Pour pouvoir utiliser l'annotation @Async, la bibliothèque CGLib doit être présente dans le classpath : pour cela il faut par exemple ajouter la bibliothèque com.springsource.net.sf.cglib-2.2.0.jar.