Configuration d’une application¶
Le fait de dépendre du conteneur IoC pour la création de notre application va avoir certains avantages : notre application va être plus facilement configurable. En effet, le Spring Framework va pouvoir injecter pour nous des valeurs extraites de fichiers de configuration.
Nous allons également utiliser dans ce chapitre pour la première fois la classe SpringApplication qui est fournie spécifiquement par Spring Boot. Il est recommandé d’utiliser cette classe pour démarrer une application Spring Boot afin de bénéficier des mécanismes d’auto-configuration.
Lancer une application Spring Boot¶
Comme nous l’avons précisé dans notre chapitre d’introduction, Spring Boot n’est pas une évolution ou une nouvelle version du Spring Framework. Il s’agit d’une extension destinée à simplifier la création d’application en se basant sur des mécanismes de configuration automatique.
Une application Spring Boot utilise la classe SpringApplication. Cette classe
fournit la méthode statique run
.
package dev.gayerie;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
La méthode run
attend en paramètres la classe qui va servir de base pour
la création du contexte d’application et la liste des paramètres de lancement
de l’application. La méthode run
a pour responsabilité de créer un
contexte d’application mais elle a aussi la charge de deviner le comportement
attendu. Par exemple, nous verrons dans le chapitre Les applications Web avec Spring Web MVC que
si nous activons le support pour une application Web, l’appel à la méthode
run
va également lancer un serveur Web embarqué.
Cette méthode run
retourne une instance de ApplicationContext. Contrairement
à une application Spring classique, il n’est pas nécessaire de fermer le contexte
d’application ainsi créé, Spring Boot s’en chargera à la fin de l’exécution de
notre méthode main
. Ainsi, la classe de lancement d’une application Spring
Boot se limite souvent au code donné ci-dessus.
L’annotation @SpringBootApplication est équivalente à @Configuration et
@ComponentScan (plus d’autres choses). Il n’est donc pas nécessaire d’ajouter
ces annotations. Si vous souhaitez configurer la détection de beans, l’annotation
@SpringBootApplication fournit les attributs scanBasePackages
et
scanBasePackageClasses
qui sont équivalents aux attributs basePackages
et
basePackagesClasses
de l’annotation @ComponentScan.
Spring Boot et le fichier application.properties¶
Une application Spring Boot dispose également d’un fichier de configuration
par défaut nommé application.properties
. Ce fichier est présent dans
le chemin de classe (classpath) de l’application. Pour un projet géré avec
Maven, cela signifie que le fichier application.properties
se trouve
dans src/main/resources
. Ce fichier est utilisé pour paramétrer le
comportement par défaut de l’application. En fonction des dépendances déclarées
dans notre projet et en fonction de la valeur des propriétés présentes dans ce
fichier, Spring Boot va adapter la création du contexte d’application. Nous
verrons que cela simplifie considérablement le développement d’une application
Web et l’interaction avec les bases de données.
La documentation complète des propriétés disponibles avec Spring Boot est disponible à cette adresse :
Injection des propriétés avec @Value¶
Nous avons vu au chapitre précédent qu’il est
possible d’utiliser l’annotation @Value pour injecter une valeur dans une
propriété. Dans la valeur à injecter, nous pouvons utiliser la syntaxe ${
}
pour donner le nom d’une propriété. Alors c’est la valeur extraite du fichier de
propriétés qui sera injectée. Il est donc facile d’extraire n’importe quelle
valeur de configuration pour notre application.
Si nous reprenons notre exemple d’un fournisseur de connexion à une base de données
en utilisant l’API JDBC, nous pouvons très facilement extraire la configuration
nécessaire dans le fichier application.properties
.
package dev.gayerie.db;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.function.Supplier;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class SimpleConnectionProvider implements Supplier<Connection> {
@Value("${database.uri}")
private String databaseUri;
@Value("${database.login}")
private String login;
@Value("${database.password}")
private String password;
private Connection connection;
@PostConstruct
public void openConnection() throws SQLException {
connection = DriverManager.getConnection(databaseUri, login, password);
}
@PreDestroy
public void closeConnection() throws SQLException {
if(connection != null) {
connection.close();
}
}
@Override
public Connection get() {
return connection;
}
}
database.uri = jdbc:mariadb://localhost:3306/db
database.login = root
database.password = r00t
Le type de la valeur à injecter ne se limite pas à une chaînes de caractères.
Le Spring Framework est capable de réaliser une conversion de type. Ainsi,
on peut injecter des primitives (nombres, valeurs booléennes, caractères). On
peut également injecter un objet de n’importe quel type. Il suffit que la classe
du type possède un constructeur qui accepte en paramètre une chaîne de caractères
ou une méthode statique de fabrique valueOf
qui accepte en paramètre une chaîne de
caractères. Par exemple, la classe standard Java URL possède un constructeur
avec un paramètre de type String. Nous pouvons donc injecter une propriété
dans un attribut de ce type à l’aide de @Value :
package dev.gayerie;
import java.net.URL;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class RemoteServerAccess {
@Value("${remote.server.url}")
private URL url;
// ...
}
remote.server.url = http://localhost/access
Si la propriété n’existe pas, la valeur correspond à la chaîne de caractères donnée directement dans @Value. Cependant, il est possible de fournir une valeur par défaut avec la syntaxe :
${propriete : valeur}
package dev.gayerie;
import java.net.URL;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class RemoteServerAccess {
@Value("${remote.server.url}")
private URL url;
@Value("${remote.server.timeout : 1000}")
private int timeout;
// ...
}
Ajout de fichiers de propriétés avec @PropertySource¶
Une application Spring Boot supporte par défaut l’utilisation d’un fichier
application.properties
. Mais si vous ne souhaitez pas utiliser
Spring Boot ou que vous voulez ajouter des fichiers de configuration supplémentaires,
vous pouvez utiliser l’annotation @PropertySource pour désigner l’emplacement
du fichier ou des fichiers de propriétés.
package dev.gayerie;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.PropertySource;
@SpringBootApplication
@PropertySource("classpath:config.properties")
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
L’emplacement du fichier est donné sous la forme d’une URI. Le Spring Framework
introduit le schéma classpath
pour désigner l’emplacement d’une fichier
dans le chemin de classe. Mais il est également possible d’utiliser file
pour
désigner un chemin dans le système de fichiers ou même http
et https
pour
télécharger un fichier de configuration depuis le Web.
Pour notre application Spring Boot, le fichier config.properties
s’ajoute
au fichier application.properties
. Donc si la même propriété est déclarée dans
les deux fichiers alors c’est la valeur déclarée dans application.properties
qui sera utilisée.
Il est possible de déclarer plusieurs fichiers en même temps :
package dev.gayerie;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.PropertySource;
@SpringBootApplication
@PropertySource({"classpath:config.properties", "file:config.properties"})
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
Cela permet de créer un ordre d’évaluation. Dans notre exemple ci-dessus, quand
une propriété doit être injectée, sa valeur est recherchée d’abord dans le fichier
application.properties
puis dans le fichier config.properties
qui
se trouve dans le répertoire courant du lancement de l’application et enfin
dans le fichier config.properties
qui se trouve dans le classpath.
Les fichiers les plus à droite dans la liste sont prioritaires.
Attention, les fichiers de propriétés doivent exister ou sinon la création du
contexte d’application échouera. Si vous voulez rendre la présence d’un fichier
optionnelle, vous devez passer l’attribut ignoreResourceNotFound
à la valeur
true
:
package dev.gayerie;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.PropertySource;
@SpringBootApplication
@PropertySource(value = {"classpath:config.properties", "file:config.properties"},
ignoreResourceNotFound = true)
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
Astuce
Pour une application Spring Boot, le fichier application.properties
est recherché dans plusieurs emplacements et il peut donc exister plusieurs
fichiers nommés application.properties
:
Dans le chemin des classes (classpath)
Dans le package
config
du chemin des classes (classpath)Dans le répertoire de lancement de l’application
Dans le sous répertoire
config
depuis le répertoire de lancement de l’applicationDans n’importe quel sous répertoire du sous répertoire
config
S’il existe plusieurs fichiers disponibles, la priorité est donnée à celui qui est le plus bas dans cette liste.
De plus, il est possible de passer des propriétés en paramètres du lancement de l’application. Dans ce cas, ces valeurs sont prioritaires sur les valeurs présentes dans les fichiers de configuration. Une fois le projet sous la forme d’un fichier jar, on peut utiliser en ligne de commandes :
$ java -jar myapplication.jar --remote.server.timeout=20000
Il est ainsi possible de positionner les propriétés que nous avons déclarées mais également les propriétés standards prises en charge par Spring Boot.
Les variables d’environnement¶
L’annotation @Value ne permet pas uniquement d’injecter des propriétés, nous
pouvons également injecter des variables d’environnement. Par exemple, sous un
système Linux, il existe la variables d’environnement USER
qui donne le
login de la session en cours. Pour récupérer sa valeur par injection :
@Value("${USER}")
private String user;
Notez qu’il existe également des variables standards à l’environnement d’exécution
Java (et donc portables entre les systèmes). Le login de session est également
disponible avec la variable user.name
:
@Value("${user.name}")
private String user;
Notez que les variables d’environnement sont prioritaires sur les propriétés du même nom déclarées dans les fichiers de configuration. Il est donc possible de redéfinir la valeur d’une propriété dans un fichier de configuration en déclarant une variable d’environnement.
La classe Environment¶
Si vous avez besoin de réaliser des traitements plus complexes à partir des propriétés, vous pouvez demander à injecter un bean de type Environment. Cette interface définie par le Spring Framework, vous permet d’accéder à la valeur des propriétés de votre application en appelant une de ses méthodes (telles que getProperty).
package dev.gayerie.db;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
@Component
public class DemoProperty {
@Autowired
private Environment env;
@PostConstruct
public void display() {
System.out.println(env.getProperty("user.name"));
}
}
Beans de propriétés avec @ConfigurationProperties (Spring Boot)¶
Spring Boot propose une version avancée de la configuration d’une application. Il s’agit de représenter les données présentes dans un fichier de propriétés sous la forme d’un bean avec les annotations @Configuration et @ConfigurationProperties.
Reprenons notre exemple de configuration à l’accès d’une base de données. Si
dans notre fichier application.properties
nous avons :
database.uri = jdbc:mariadb://localhost:3306/db
database.login = root
database.password = r00t
Alors nous pouvons créer un bean représentant cette configuration.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | package dev.gayerie;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "database")
public class DatabaseConfig {
private String uri;
private String login;
private String password;
public String getUri() {
return uri;
}
public void setUri(String uri) {
this.uri = uri;
}
public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
|
À la ligne 7, l’annotation @ConfigurationProperties signale que les attributs
du bean doivent être remplis à partir des propriétés de configuration.
L’annotation précise que le préfixe est database
. Donc Spring va rechercher
la valeur pour les propriétés database.url
, database.login
et
database.password
qui sont bien présentes dans le fichier application.properties
.
Attention, la présence des setters est obligatoire pour signaler à Spring quelles propriétés doivent être injectées.
On peut ensuite revoir la déclaration du bean de connexion en injectant ce nouveau bean de configuration :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | package dev.gayerie;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.function.Supplier;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class SimpleConnectionProvider implements Supplier<Connection> {
@Autowired
private DatabaseConfig databaseConfig;
private Connection connection;
@PostConstruct
public void openConnection() throws SQLException {
connection = DriverManager.getConnection(databaseConfig.getUri(),
databaseConfig.getLogin(),
databaseConfig.getPassword());
}
@PreDestroy
public void closeConnection() throws SQLException {
if(connection != null) {
connection.close();
}
}
@Override
public Connection get() {
return connection;
}
}
|
Une classe annotée avec @ConfigurationProperties peut également profiter de la validation déclarative en utilisant l’API standard Bean Validation.
Introduction à Bean Validation¶
L’API standard Bean Validation permet d’ajouter des contraintes sur les attributs d’une classe afin de réaliser une validation sur leur valeur. Elle peut être utilisée dans différents contextes et nous verrons qu’elle peut être très utile pour valider les données envoyées par un utilisateur dans une application Web développée avec Les applications Web avec Spring Web MVC.
Spring Boot permet de valider les attributs d’une classe annotée avec @ConfigurationProperties. Cela permet de rendre l’application plus robuste en détectant, dès le lancement, les valeurs qui ne sont pas conformes.
À partir de la version 2.4 de Spring Boot, vous devez activer le support de
Bean Validation en déclarant une dépendance. Pour un projet Maven, il suffit
d’ajouter dans fichier pom.xml
:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Ensuite nous pouvons faire évoluer notre classe DatabaseConfig
pour préciser
les règles de validation :
package dev.gayerie;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.annotation.Validated;
@Configuration
@ConfigurationProperties(prefix = "database")
@Validated
public class DatabaseConfig {
@Pattern(regexp = "jdbc:.*", message = "Database JDBC URI must start with jdbc:")
private String uri;
@NotBlank(message = "login cannot be blank")
private String login;
@NotNull(message = "password is mandatory but can be left empty")
private String password;
public String getUri() {
return uri;
}
public void setUri(String uri) {
this.uri = uri;
}
public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
Dans cet exemple, nous imposons que l’URI de la base de données commence par
jdbc:
grâce à une expression régulière. Nous indiquons que le login ne
peut pas être vide (ou ne contenir que des espaces). Enfin, le mot de passe
doit être présent (même s’il est vide). L’attribut message
des annotations
permet de fournir une erreur explicite qui sera affichée en cas d’erreur de
validation au démarrage.
Avertissement
Notez que la classe porte elle-même l’annotation @Validated pour activer le processus de validation.
Note
La liste des annotations de Bean Validation est disponible dans la documentation Java EE.
Pour en savoir plus¶
Les auteurs du Spring Framework et de Spring Boot ont voulu laisser une grande liberté aux développeurs sur la façon de configurer leurs applications. Nous n’avons fait que présenter les principes fondamentaux. Par exemple, il est possible de stocker la configuration de son application non pas dans un fichier de propriétés mais dans un fichier YAML. Il est également possible de gérer des profils d’exécution pour mieux contrôler l’instanciation des beans et les paramètres de configuration.
Pour les applications Spring Boot, vous pouvez vous référer à la documentation officielle pour une présentation complète de ces possibilités.