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.

La base d’une application Spring Boot
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.

Implémentation d’un fournisseur de connexion à une base de données
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;
  }
}
Le fichier application.properties
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 :

Utilisation de @Value pour injecter une URL
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;

  // ...

}
Le fichier application.properties
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}
Utilisation de @Value avec une valeur par défaut
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.

Utilisation de @PropertySource
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 :

Utilisation de @PropertySource pour déclarer plusieurs fichiers
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 :

  1. Dans le chemin des classes (classpath)

  2. Dans le package config du chemin des classes (classpath)

  3. Dans le répertoire de lancement de l’application

  4. Dans le sous répertoire config depuis le répertoire de lancement de l’application

  5. Dans 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).

Un exemple d’injection d’une instance de Environment
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 :

Le fichier application.properties
database.uri = jdbc:mariadb://localhost:3306/db
database.login = root
database.password = r00t

Alors nous pouvons créer un bean représentant cette configuration.

Un 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
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 :

Injection et utilisation du 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 :

Dépendance Spring Boot pour Spring Validation
<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 :

Validation du bean de configuration avec bean 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.