La notion de profile permet d’activer ou désactiver des beans en fonction du contexte de déploiement.
Un exemple courant est la spécification d’une datasource qui peut être différente entre les environnements de développement, de test et de déploiement.
Fonctionnement de base
@Profile
Un bean annoté avec @Profile
ne sera valable que si le profil spécifié est actif dans le contexte Spring.
@Component
@Profile("dev")
public class DevOnlyBean implements SomeBean {
...
}
@Component
@Profile("prod")
public class ProdOnlyBean implements SomeBean {
...
}
Active profiles
Il y a plusieurs façons d’activer des profils.
-
par propriété système
~# java -jar -Dspring.profiles.active=prod app.jar
-
par paramètre de programme
~# java -jar app.jar --spring.profiles.active=prod
-
par programmation, cf. API
-
par configuration, cf. Configuration
Par le passé, il était aussi possible de spécifier des profils additionnels avec la propriété ou le paramètre |
Default profiles
Si aucun profil n’est spécifié au démarrage, ce sont les beans et configurations marquée en "default" qui sont utilisés.
Il est aussi possible de changer ça avec la propriété ou le paramètre spring.profiles.default
.
~# java -jar -Dspring.profiles.default=none app.jar
Je ne sais pas trop à quoi ça peut servir, mais je le note au cas où…
Configuration
Avec Spring Boot, la configuration est dans un fichier application.properties
ou application.yml
.
Ajout de profil
Il est possible de spécifier les profils dans le fichier de configuration.
spring.profiles.active=cloud
...
Si des profils ont déjà été spécifié via la propriété spring.profiles.active
, ceux du fichier de configuration viennent s’ajouter.
Configuration spécifique
En plus de rattacher des beans à un profil, on peut aussi faire une configuration spécifique.
Celle-ci se fait dans un fichier nommé application-{profile}.properties
ou application-{profile}.yml
.
Par exemple, on peut configurer une datasource uniquement pour le développement.
spring:
datasource:
jdbc-url: jdbc:postgresql://localhost:5432/postgres
username: 'postgres'
password: 'pgpwd'
On ne peut pas activer d’autres profils dans un fichier de configuration dédié à un profil. |
Configuration multi-profils
Depuis Spring Boot 2.4, il est aussi possible de regrouper les configurations de plusieurs profils dans le même fichier. Pour ça, on utilise la notion de fichiers multi-documents de YAML. Et chaque document du fichier indique quel profil il configure.
...
---
spring:
config:
activate:
on-profile: dev
datasource:
jdbc-url: jdbc:postgresql://localhost:5432/postgres
username: postgres
password: pgpwd
Spring Boot supporte aussi ça avec des fichiers properties, avec le séparateur #---
.
...
#---
spring.config.activate.on-profile=dev
spring.datasource.jdbc-url=jdbc:postgresql://localhost:5432/postgres
spring.datasource.username=postgres
spring.datasource.password=pgpwd
API
On peut lire la liste des profils actifs via le bean d’environnement.
@Component
public class SomeBean {
private final Environment environment;
public MainBean(Environment environment) {
this.environment = environment;
}
@PostConstruct
public void init() {
var defaultProfiles = environment.getDefaultProfiles();
var activeProfiles = environment.getActiveProfiles();
}
}
On peut aussi modifier la liste de profils actifs par programmation, mais uniquement avant le démarrage du contexte Spring.
-
Démarrage d’une application Spring Boot
@SpringBootApplication
public class SpringExampleApplication {
public static void main(String[] args) {
new SpringApplicationBuilder()
.sources(SpringExampleApplication.class)
.profiles("profile-a", "profile-b")
.build()
.run(args);
}
}
-
Initialisation du contexte Spring (à déclarer au démarrage de l’application Spring Boot, dans web.xml ou dans la classe de test)
public class CommonProfileInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext context) {
context.getEnvironment()
.addActiveProfile("common");
}
}
-
Post-processeur
(à déclarer dans le fichierMETA-INF/spring.factories
)
public class CommonProfilePostProcessor
implements EnvironmentPostProcessor {
@Override
public void postProcessEnvironment(
ConfigurableEnvironment environment,
SpringApplication application) {
environment.addActiveProfile("common");
}
}
org.springframework.boot.env.EnvironmentPostProcessor=info.jtips.spring.CommonProfilePostProcessor
Tests automatisés
Utiliser les profils pour les tests JUnit passe évidemment par l’utilisation d’un profil "test" !
@ActiveProfiles
Pour choisir les profils actifs au démarrage d’un test, on utilise habituellement l’annotation @ActiveProfiles
.
@SpringBootTest
@ActiveProfiles("test")
class MainBeanTest {
...
}
Le fonctionnement de cette annotation est un peu particulier puisque les profils spécifiés ici remplacent tous les autres. Les propriétés système sont ignorées, ainsi que les profils activés dans la configuration application.properties
ou application.yml
.
Ce comportement est définit dans l'ActiveProfilesResolver
par défaut.
ActiveProfilesResolver
Pour qu’une autre source de profils soit prise en compte avec l’annotation @ActiveProfiles
, il faut adopter un ActiveProfilesResolver
personnalisé.
@SpringBootTest
@ActiveProfiles(profiles="test", resolver=EnhancedActiveProfileResolver.class)
class MainBeanTest {
...
}
Pour ce test, c’est dans la classe EnhancedActiveProfileResolver
qu’on définit les sources de profils.
Dans l’implémentation ci-dessous, on combine les profils de l’annotation @ActiveProfiles
avec ceux de la propriété système spring.profiles.active
.
public class EnhancedActiveProfileResolver
implements ActiveProfilesResolver {
public static final String PROPERTY_KEY = "spring.profiles.active";
private final DefaultActiveProfilesResolver defaultActiveProfilesResolver
= new DefaultActiveProfilesResolver();
@Override
public String[] resolve(Class<?> testClass) {
return Stream
.concat(
Stream.of(defaultActiveProfilesResolver.resolve(testClass)),
Stream.of(this.getPropertyProfiles())
)
.toArray(String[]::new);
}
private String[] getPropertyProfiles() {
return System.getProperties().containsKey(PROPERTY_KEY)
? System.getProperty(PROPERTY_KEY).split("\\s*,\\s*")
: new String[0];
}
}
Cette solution a encore des défauts.
En effet, cette classe n’a aucune information de contexte Spring, elle ne peut donc pas récupérer les profils qui seraient activés dans le fichier de configuration application.properties
ou application.yml
.
ApplicationContextInitializer
On a vu la possibilité ci-dessus la possibilité d’ajouter un profil dans une classe d’initialisation du contexte. Cette solution a l’avantage de conserver tous les autres profils.
Il est possible de déclarer cette initialisation dans le test.
@ContextConfiguration(initializers = ProfileInitializer.class)
class MainBeanWithInitializerTest {
//...
}
EnvironmentPostProcessor
On a aussi vu la possibilité d’ajouter un profil dans une classe post-processeur. Cette solution conserve aussi tous les autres profils.
Pour activer le post-processeur, il faut l’activer dans un fichier META-INF/spring.factories
.
Pour ne l’activer qu’en test, il suffit de le déclarer dans le META-INF/spring.factories
de test.
ContextCustomizerFactory
Une autre solution passe par le développement d’un ContextCustomizerFactory
.
Cette solution a aussi l’avantage de conserver tous les autres profils.
Elle a l’autre avantage d’être globale, et évite d’ajouter une annotation à chaque test.
public class CustomContextCustomizerFactory
implements ContextCustomizerFactory {
@Override
public ContextCustomizer createContextCustomizer(
Class<?> testClass,
List<ContextConfigurationAttributes> configAttributes) {
return (context, mergedConfig) -> {
context.getEnvironment().addActiveProfile("test");
};
}
}
Cette fabrique doit être déclarée dans META-INF/spring.factories
.
org.springframework.test.context.ContextCustomizerFactory=info.jtips.spring.profiles.CustomContextCustomizerFactory
Si toutefois on continue d’utiliser l’annotation @ActiveProfiles
, le personnalisateur de contexte ajoute son profil à ceux de l’annotation.
Et si c’est un profil identique, il n’a pas d’effet, puisque les doublons sont éliminés.
Synthèse
Dans cette page, nous avons vu les cas d’usage suivants :
-
profils pour un processus :
spring.profiles.active
en propriété système ou paramètre du processus, -
profils pour tous les processus (hors tests) :
springApplicationBuilder.profiles(…)
, -
profils pour les tests :
@ActiveProfiles
ouCustomContextCustomizerFactory
, -
profils pour tous les processus (hors tests) :
application.yml
et tests avec@ActiveProfiles
, -
profils pour tous les processus (tests compris) :
application.yml
et tests avecCustomContextCustomizerFactory
.
Spring Boot
Parmi les techniques citées, les suivantes sont spécifiques à Spring Boot :
-
paramètre du processus
-
springApplicationBuilder.profiles(…)
-
application.yml
Références
-
Exemples de code, avec Spring 5.3, Spring Boot 2.6 et JUnit 5.8