De très nombreux projets Java J2EE intègrent Spring pour la configuration. Spring est un ensemble de librairies relativement indépendantes qui permettent de gérer différents aspects de la programmation Java. Les plus utilisées sont:
- l'injection des dépendances (Spring Inversion Of Context ou Dependency Injection)
- la gestion des transactions
- la configuration des bases de données (hibernate3)
- Spring Web
- Spring Security
- Spring Batch
- Spring Roo
Configuration XML pour Spring
Dans ce billet, je vais principalement décrire l'utilisation de l'injection de dépendances, un bien grand mot pour une utilisation courante relativement simple. L'injection de dépendances est un pattern qui permet de définir plusieurs implémentations d'une interface et de définir au moment de l'exécution celle qui l'on souhaite utilisée. L'apport de Spring est que la définition de la classe à utiliser au moment de l'exécution peut être faite dans un fichier XML externe. Il est ainsi possible en choisissant différentes implémentations de complètement redéfinir le fonctionnement d'une application. Cet facilité peut cependant se révéler être un inconvénient dans certains cas. Les principales critiques que l'on peut faire à Spring sont la taille des librairies utilisées (2 Mo pour une application de base) et la taille du fichier XML de configuration qui peut dans certains cas être relativement gros. Les dernières versions de Spring Framework permettent d'utiliser des annotations pour la configuration et permettent ainsi de faire une partie de la configuration en Java. Cela permet de limiter l'utilisation des fichiers XML aux seuls objets qui en ont vraiment besoin, mais peut réduire dans certains cas la flexibilité.
Voici un exemple simple d'utilisation. Je vais définir dans un premier temps une interface.
public interface ITestSpring {
public String getName();
public void setName(String name);
}
Je vais maintenant définir une implémentation tout aussi simple. Il s'agit d'une classe Java Bean classique qui implémente l'interface. Définissez également un constructeur par défaut, ce qui permettra à Spring de créer une instance vide et d'utiliser les méthodes "set" pour affecter les valeurs.
Définissez un fichier de configuration de la manière suivante. Avec le bouton droit, sélectionnez le menu "New", puis "Spring Bean Configuration File". Indiquez un nom de fichier "simplecontext.xml" par exemple. Vous pourrez ainsi bénéficier du plugin d'Eclipse pour ajouter de données dans le fichier. Vous pouvez également directement manipuler le fichier XML si vous le souhaitez.
Dans l'onglet "Beans", rajoutez les classes que vous souhaitez initialiser (TestSpringImpl dans mon cas).
Vous pouvez ainsi créer une instance de classe et l'initialiser soit en utilisant le constructeur avec les paramètres dans l'ordre de définition ou en utilisant les méthodes "set".
Le fichier XML résultant est le suivant:
Rajoutez les librairies suivantes :
L'appel à FileSystemXmlApplicationContext permet de charger le fichier XML contenant la configuration initiale. L'appel de "context.getBean(ITestSpring.class)" permet de retrouver la classe correspondante instanciée dans le fichier XML. Dans ce cas, il ne faut pas qu'il y ait d’autre instance de la même classe. En cas d'ambigüité, il est préférable de nommer les instances.
A l'exécution, vous devriez voir :
public class TestSpringImpl implements ITestSpring {
String name;
public TestSpringImpl() {
}
public TestSpringImpl(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public void setName(String name) {
this.name = name;
}
}
Définissez un fichier de configuration de la manière suivante. Avec le bouton droit, sélectionnez le menu "New", puis "Spring Bean Configuration File". Indiquez un nom de fichier "simplecontext.xml" par exemple. Vous pourrez ainsi bénéficier du plugin d'Eclipse pour ajouter de données dans le fichier. Vous pouvez également directement manipuler le fichier XML si vous le souhaitez.
Dans l'onglet "Beans", rajoutez les classes que vous souhaitez initialiser (TestSpringImpl dans mon cas).
Vous pouvez ainsi créer une instance de classe et l'initialiser soit en utilisant le constructeur avec les paramètres dans l'ordre de définition ou en utilisant les méthodes "set".
Le fichier XML résultant est le suivant:
<?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">
<bean class="TestSpringImpl">
<constructor-arg value="Test Spring3"></constructor-arg>
</bean>
</beans>
Rajoutez les librairies suivantes :
- spring-core-3.2.4.RELEASE.jar
- spring-beans-3.2.4.RELEASE.jar
- spring-context-3.2.4.RELEASE.jar
- spring-expression-3.2.4.RELEASE.jar
- commons-logging-1.1.1.jar
- log4j-1.2.15.jar
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
public class TestSpringMain {
public static void main(String[] args) {
ApplicationContext context = new FileSystemXmlApplicationContext("simplecontext.xml");
ITestSpring test = context.getBean(ITestSpring.class);
System.out.println("Test = " + test.getName());
System.exit(0);
}
}
L'appel à FileSystemXmlApplicationContext permet de charger le fichier XML contenant la configuration initiale. L'appel de "context.getBean(ITestSpring.class)" permet de retrouver la classe correspondante instanciée dans le fichier XML. Dans ce cas, il ne faut pas qu'il y ait d’autre instance de la même classe. En cas d'ambigüité, il est préférable de nommer les instances.
A l'exécution, vous devriez voir :
Test = Test Spring3
Annotations avec Spring 3
Spring permet de faire la configuration tout en Java en utilisant des annotations. Il est cependant possible d'utiliser les deux modes de configuration. Les modifications principales sont dans la classe d'implémentation TestSpringImpl, ainsi que dans la classe de configuration.
Dans la classe TestSpringImpl, nous allons simplement rajouter une annotation @Component ainsi que l'import correspondant.
Je vais faire la configuration dans la classe principale. Il est généralement préférable de la faire dans une classe séparée qui ne peut pas être instanciée.
L'annotation @Configuration indique que cette classe va être prise en compte lors de l'appel à AnnotationConfigApplicationContext qui comme son nom l'indique crée un contexte à partir des classes annotées.
L'annotation @Bean indique que la méthode renvoie une instance de la classe. Cette ci est récupérée lors de l'appel à context1.getBean("testSpring"). Il est possible de donner un nom différent en rajoutant un paramètre à l'annotation (ex : @Bean(name=spring1).
L'équivalent standard Java consiste à créer une classe Singleton. En rendant le constructeur non public, le seul moyen de récupérer une instance de cette classe est d'utiliser la commande TestSpringImpl.getInstance().
Si vous souhaitez pouvoir charger les classes dynamiquement, la classe utilitaire suivante peut vous être utile :
Vous pouvez ainsi charger la classe TestSpringImpl en appelant :
Vous avez maintenant le choix. En fonction de la taille de votre projet, vous devrez adapter le choix de la technologie que vous utiliserez.
Dans la classe TestSpringImpl, nous allons simplement rajouter une annotation @Component ainsi que l'import correspondant.
import org.springframework.stereotype.Component;
@Component
public class TestSpringImpl implements ITestSpring {
String name;
public TestSpringImpl() {
}
public TestSpringImpl(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public void setName(String name) {
this.name = name;
}
}
Je vais faire la configuration dans la classe principale. Il est généralement préférable de la faire dans une classe séparée qui ne peut pas être instanciée.
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.FileSystemXmlApplicationContext;
@Configuration
public class TestSpringMain {
@Bean
public ITestSpring testSpring() {
return new TestSpringImpl("Test Spring with annotations");
}
public static void main(String[] args) {
ApplicationContext context1 = new AnnotationConfigApplicationContext(TestSpringMain.class);
ITestSpring test1 = (ITestSpring)context1.getBean("testSpring");
System.out.println("Test1 = " + test1.getName());
System.exit(0);
}
}
L'annotation @Configuration indique que cette classe va être prise en compte lors de l'appel à AnnotationConfigApplicationContext qui comme son nom l'indique crée un contexte à partir des classes annotées.
L'annotation @Bean indique que la méthode renvoie une instance de la classe. Cette ci est récupérée lors de l'appel à context1.getBean("testSpring"). Il est possible de donner un nom différent en rajoutant un paramètre à l'annotation (ex : @Bean(name=spring1).
Equivalent Java POJO
Spring offre certes une grande flexibilité, mais le surpoids apporté par les librairies nécessaires à son utilisation le limitent aux applications qui nécessitent déjà un grand nombre de librairies. Il est donc préférable de ne l'utiliser que si votre projet pèse déjà 10 Mo ou si cette technologie est déjà utilisée dans votre projet.L'équivalent standard Java consiste à créer une classe Singleton. En rendant le constructeur non public, le seul moyen de récupérer une instance de cette classe est d'utiliser la commande TestSpringImpl.getInstance().
public class TestSpringImpl implements ITestSpring {
String name;
static ITestSpring instance = null;
public static ITestSpring getInstance() {
if (instance == null)
instance = new TestSpringImpl();
return instance;
}
TestSpringImpl() {
}
TestSpringImpl(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public void setName(String name) {
this.name = name;
}
}
Si vous souhaitez pouvoir charger les classes dynamiquement, la classe utilitaire suivante peut vous être utile :
public class ObjectFactory {
@SuppressWarnings("unchecked")
public static Object create(String name) throws Exception {
ClassLoader clazzLoader = ObjectFactory.class.getClassLoader();
Class clazz;
clazz = clazzLoader.loadClass(name);
return clazz.newInstance();
}
}
Vous pouvez ainsi charger la classe TestSpringImpl en appelant :
ITestSpring test2 = (ITestSpring)ObjectFactory.create("TestSpringImpl");
test2.setName("Hello Loaded dynamically");
System.out.println("Test2 = " + test2.getName());
Vous avez maintenant le choix. En fonction de la taille de votre projet, vous devrez adapter le choix de la technologie que vous utiliserez.
Commentaires
Enregistrer un commentaire