Quiz: Scopes et Configuration Avancée
Testez vos connaissances sur les scopes et les propriétés de configuration.
30 questions - Cliquez sur chaque question pour révéler la réponse.
Questions 1-15: Scopes
Question 1: Quel est le scope par défaut d'un bean Spring?
Réponse: Singleton - une seule instance par conteneur Spring.
@Component
public class MyService { } // Singleton par défaut
Question 2: Comment créer un bean prototype?
Réponse:
@Component
@Scope("prototype")
public class PrototypeBean { }
// Ou
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public MyService myService() { }
Nouvelle instance à chaque demande.
Question 3: Quand utiliser le scope prototype?
Réponse:
- Objets avec état mutable
- Objets non thread-safe
- Objets coûteux à créer mais à usage unique
- Workflows ou processus indépendants
Exemple: parser de fichier, processeur de commande.
Question 4: Qu'est-ce que le scope request?
Réponse: Une instance par requête HTTP:
@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestContext {
private User currentUser;
// Données spécifiques à la requête
}
Détruit à la fin de la requête.
Question 5: Qu'est-ce que le scope session?
Réponse: Une instance par session HTTP:
@Component
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class ShoppingCart {
private List<Item> items = new ArrayList<>();
}
Persiste entre les requêtes du même utilisateur.
Question 6: Pourquoi proxyMode est-il nécessaire pour request/session?
Réponse: Le singleton est créé au démarrage, avant toute requête. Le proxy permet d'accéder au bean scopé dynamiquement:
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestData { }
@Service // Singleton
public class MyService {
private final RequestData data; // Proxy, pas l'instance réelle
}
Question 7: Quels sont les types de proxyMode?
Réponse:
| Mode | Usage |
|---|---|
DEFAULT | Pas de proxy |
NO | Pas de proxy |
INTERFACES | Proxy JDK (interface) |
TARGET_CLASS | Proxy CGLIB (classe) |
Utiliser TARGET_CLASS pour les classes concrètes.
Question 8: Comment créer un scope personnalisé?
Réponse:
public class TenantScope implements Scope {
private final ThreadLocal<Map<String, Object>> beans = new ThreadLocal<>();
@Override
public Object get(String name, ObjectFactory<?> factory) {
Map<String, Object> scope = beans.get();
if (!scope.containsKey(name)) {
scope.put(name, factory.getObject());
}
return scope.get(name);
}
@Override
public Object remove(String name) {
return beans.get().remove(name);
}
// ...
}
@Configuration
public class ScopeConfig implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) {
factory.registerScope("tenant", new TenantScope());
}
}
Question 9: Comment gérer le thread-safety avec les singletons?
Réponse:
- Beans stateless (pas de champs mutables)
- Champs immuables (final)
- Synchronisation si nécessaire
- Utiliser
ThreadLocalpour l'état par thread
@Service
public class StatelessService {
// Pas de champs mutables
private final UserRepository repo; // Immutable
public User process(Long id) {
// Tout dans les variables locales
return repo.findById(id).orElseThrow();
}
}
Question 10: Comment vérifier le scope d'un bean?
Réponse:
@Autowired
private ConfigurableListableBeanFactory beanFactory;
public void checkScope() {
BeanDefinition definition = beanFactory.getBeanDefinition("myBean");
String scope = definition.getScope();
log.info("Scope: {}", scope); // singleton, prototype, etc.
}
Question 11: Comment utiliser @RefreshScope?
Réponse: Pour les beans qui doivent être recréés après un refresh de configuration:
@RefreshScope
@Component
public class DynamicConfig {
@Value("${app.feature.enabled}")
private boolean featureEnabled;
}
Après POST /actuator/refresh, le bean est recréé avec les nouvelles valeurs.
Question 12: Comment tester les beans scopés?
Réponse:
@SpringBootTest
@WebAppConfiguration // Pour request/session scope
class ScopedBeanTest {
@Autowired
private WebApplicationContext context;
@Test
void testRequestScope() {
MockHttpServletRequest request = new MockHttpServletRequest();
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
try {
RequestData data = context.getBean(RequestData.class);
assertThat(data).isNotNull();
} finally {
RequestContextHolder.resetRequestAttributes();
}
}
}
Question 13: Comment les beans prototype sont-ils détruits?
Réponse: Spring ne détruit pas les beans prototype automatiquement!
@Component
@Scope("prototype")
public class ExpensiveResource implements DisposableBean {
@Override
public void destroy() {
// Jamais appelé automatiquement!
}
}
Il faut gérer manuellement ou utiliser un BeanFactoryPostProcessor.
Question 14: Comment obtenir plusieurs beans du même type?
Réponse:
// Liste de tous les beans
@Autowired
private List<NotificationService> notificationServices;
// Map avec nom comme clé
@Autowired
private Map<String, NotificationService> notificationMap;
// ObjectProvider pour accès lazy
@Autowired
private ObjectProvider<NotificationService> provider;
public void notify() {
provider.forEach(service -> service.send(message));
}
Question 15: Comment utiliser @Primary vs @Qualifier?
Réponse:
// @Primary: bean par défaut
@Service
@Primary
public class EmailNotification implements NotificationService { }
@Service
public class SmsNotification implements NotificationService { }
// @Qualifier: choix explicite
public MyController(@Qualifier("smsNotification") NotificationService notif) { }
Questions 16-30: Configuration
Question 16: Comment lire une propriété avec @Value?
Réponse:
@Value("${app.name}")
private String appName;
@Value("${app.timeout:5000}") // Valeur par défaut
private int timeout;
@Value("${app.features}") // Liste: a,b,c
private List<String> features;
@Value("#{${app.map}}") // Map: {key1: 'val1', key2: 'val2'}
private Map<String, String> map;
Question 17: Comment utiliser SpEL dans @Value?
Réponse:
@Value("#{systemProperties['user.home']}")
private String userHome;
@Value("#{T(java.lang.Math).random()}")
private double randomValue;
@Value("#{myBean.property}")
private String fromBean;
@Value("#{${app.timeout} * 2}")
private int doubleTimeout;
Question 18: Comment grouper les propriétés avec @ConfigurationProperties?
Réponse:
# application.yml
app:
mail:
host: smtp.example.com
port: 587
username: user
password: secret
@ConfigurationProperties(prefix = "app.mail")
@Validated
public class MailProperties {
@NotBlank
private String host;
@Min(1)
@Max(65535)
private int port;
private String username;
private String password;
// Getters et setters
}
@Configuration
@EnableConfigurationProperties(MailProperties.class)
public class AppConfig { }
Question 19: Comment valider les propriétés?
Réponse:
@ConfigurationProperties(prefix = "app")
@Validated
public class AppProperties {
@NotNull
private String name;
@Min(1000)
@Max(65535)
private int port;
@Email
private String adminEmail;
@Valid // Valider les objets imbriqués
private DatabaseProperties database;
}
L'application échoue au démarrage si invalide.
Question 20: Comment configurer des objets imbriqués?
Réponse:
app:
database:
primary:
url: jdbc:postgresql://primary/db
username: user1
secondary:
url: jdbc:postgresql://secondary/db
username: user2
@ConfigurationProperties(prefix = "app.database")
public class DatabaseProperties {
private DataSourceConfig primary;
private DataSourceConfig secondary;
public static class DataSourceConfig {
private String url;
private String username;
// Getters/setters
}
}
Question 21: Comment configurer des listes?
Réponse:
app:
servers:
- host: server1.example.com
port: 8080
- host: server2.example.com
port: 8081
@ConfigurationProperties(prefix = "app")
public class AppProperties {
private List<ServerConfig> servers;
public static class ServerConfig {
private String host;
private int port;
}
}
Question 22: Comment utiliser les profils de configuration?
Réponse:
# application.yml (commun)
app:
name: My App
---
# application-dev.yml
spring:
config:
activate:
on-profile: dev
logging:
level:
root: DEBUG
---
# application-prod.yml
spring:
config:
activate:
on-profile: prod
logging:
level:
root: WARN
Activation:
java -jar app.jar --spring.profiles.active=prod
Question 23: Comment externaliser la configuration?
Réponse: Ordre de priorité (du plus prioritaire au moins):
- Arguments ligne de commande
- Variables d'environnement
application-\{profile\}.ymlexterneapplication.ymlexterneapplication-\{profile\}.ymldans le JARapplication.ymldans le JAR
java -jar app.jar --server.port=9090
export SERVER_PORT=9090
Question 24: Comment charger une configuration depuis un fichier externe?
Réponse:
# Fichier externe
java -jar app.jar --spring.config.location=file:/etc/myapp/config/
# Plusieurs emplacements
java -jar app.jar --spring.config.location=classpath:/,file:/config/
# Fichier spécifique
java -jar app.jar --spring.config.name=custom
Question 25: Comment utiliser des variables d'environnement?
Réponse:
spring:
datasource:
url: ${DB_URL:jdbc:h2:mem:default}
username: ${DB_USER:sa}
password: ${DB_PASSWORD:}
Convention de mapping:
app.my-property→APP_MY_PROPERTY.→_-→_
Question 26: Comment chiffrer les propriétés sensibles?
Réponse: Avec Jasypt ou Spring Cloud Config:
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
</dependency>
jasypt:
encryptor:
password: ${JASYPT_PASSWORD}
spring:
datasource:
password: ENC(encrypted_value_here)
Question 27: Comment créer un PropertySource personnalisé?
Réponse:
public class VaultPropertySource extends PropertySource<VaultClient> {
public VaultPropertySource(VaultClient client) {
super("vault", client);
}
@Override
public Object getProperty(String name) {
if (name.startsWith("vault.")) {
return source.getSecret(name.substring(6));
}
return null;
}
}
@Configuration
public class VaultConfig implements EnvironmentPostProcessor {
@Override
public void postProcessEnvironment(ConfigurableEnvironment env, SpringApplication app) {
env.getPropertySources().addFirst(new VaultPropertySource(client));
}
}
Question 28: Comment rafraîchir la configuration à chaud?
Réponse:
@RefreshScope
@Component
public class DynamicConfig {
@Value("${app.feature.enabled}")
private boolean enabled;
}
# Avec Spring Cloud Config
curl -X POST http://localhost:8080/actuator/refresh
Les beans @RefreshScope sont recréés.
Question 29: Comment tester les propriétés de configuration?
Réponse:
@SpringBootTest(properties = {
"app.name=Test App",
"app.timeout=1000"
})
class ConfigTest {
@Autowired
private AppProperties properties;
@Test
void propertiesLoaded() {
assertThat(properties.getName()).isEqualTo("Test App");
assertThat(properties.getTimeout()).isEqualTo(1000);
}
}
// Ou avec fichier de test
@SpringBootTest
@TestPropertySource("classpath:test-config.properties")
class ConfigTest { }
Question 30: Quelles sont les bonnes pratiques de configuration?
Réponse:
- Typer avec
@ConfigurationProperties - Valider avec
@Validated - Externaliser les secrets
- Documenter chaque propriété
- Valeurs par défaut sensées
- Profils pour les environnements
- Ne pas commiter les secrets
- Centraliser avec Config Server en microservices
@ConfigurationProperties(prefix = "app")
@Validated
public class AppProperties {
/**
* Nom de l'application affiché dans les logs.
*/
@NotBlank
private String name = "My Application";
}
Score
| Score | Niveau |
|---|---|
| 27-30 | Excellent - Scopes et configuration maîtrisés |
| 21-26 | Bien - Quelques révisions |
| 15-20 | Moyen - Relisez le chapitre |
| Moins de 15 | À revoir |