Aller au contenu principal

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:

ModeUsage
DEFAULTPas de proxy
NOPas de proxy
INTERFACESProxy JDK (interface)
TARGET_CLASSProxy 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:

  1. Beans stateless (pas de champs mutables)
  2. Champs immuables (final)
  3. Synchronisation si nécessaire
  4. Utiliser ThreadLocal pour 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):

  1. Arguments ligne de commande
  2. Variables d'environnement
  3. application-\{profile\}.yml externe
  4. application.yml externe
  5. application-\{profile\}.yml dans le JAR
  6. application.yml dans 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:

  1. Typer avec @ConfigurationProperties
  2. Valider avec @Validated
  3. Externaliser les secrets
  4. Documenter chaque propriété
  5. Valeurs par défaut sensées
  6. Profils pour les environnements
  7. Ne pas commiter les secrets
  8. 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​

ScoreNiveau
27-30Excellent - Scopes et configuration maîtrisés
21-26Bien - Quelques révisions
15-20Moyen - Relisez le chapitre
Moins de 15À revoir

Retour au Module 5