Saltar al contenido principal

Cuestionario: Ámbitos y configuración avanzada

Ponga a prueba sus conocimientos sobre ámbitos y propiedades de configuración.

30 preguntas - Haga clic en cada pregunta para revelar la respuesta.


Preguntas 1-15: Ámbitos​

Pregunta 1: ¿Cuál es el ámbito predeterminado de un bean Spring?

Respuesta: Singleton - una sola instancia por contenedor Spring.

@Component
public class MyService { } // Singleton por defecto

Pregunta 2: ¿Cómo crear un bean prototype?

Respuesta:

@Component
@Scope("prototype")
public class PrototypeBean { }

// O
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public MyService myService() { }

Nueva instancia en cada obtención.


Pregunta 3: ¿Cuándo usar el ámbito prototype?

Respuesta:

  • Objetos con estado mutable
  • Objetos no thread-safe
  • Objetos costosos de crear pero de uso único
  • Workflows o procesos independientes

Ejemplo: parser de archivos, procesador de comandos.


Pregunta 4: ¿Qué es el ámbito request?

Respuesta: Una instancia por solicitud HTTP:

@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestContext {
private User currentUser;
// Datos específicos de la solicitud
}

Se destruye al final de la solicitud.


Pregunta 5: ¿Qué es el ámbito session?

Respuesta: Una instancia por sesión HTTP:

@Component
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class ShoppingCart {
private List<Item> items = new ArrayList<>();
}

Persiste entre las solicitudes del mismo usuario.


Pregunta 6: ¿Por qué es necesario proxyMode para request/session?

Respuesta: El singleton se crea al inicio, antes de cualquier solicitud. El proxy permite acceder al bean con ámbito dinámicamente:

@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestData { }

@Service // Singleton
public class MyService {
private final RequestData data; // Proxy, no la instancia real
}

Pregunta 7: ¿Cuáles son los tipos de proxyMode?

Respuesta:

ModoUso
DEFAULTSin proxy
NOSin proxy
INTERFACESProxy JDK (interfaz)
TARGET_CLASSProxy CGLIB (clase)

Utilice TARGET_CLASS para clases concretas.


Pregunta 8: ¿Cómo crear un ámbito personalizado?

Respuesta:

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());
}
}

Pregunta 9: ¿Cómo manejar la thread-safety con los singletons?

Respuesta:

  1. Beans stateless (sin campos mutables)
  2. Campos inmutables (final)
  3. Sincronización si es necesario
  4. Usar ThreadLocal para estado por hilo
@Service
public class StatelessService {
// Sin campos mutables
private final UserRepository repo; // Inmutable

public User process(Long id) {
// Todo en variables locales
return repo.findById(id).orElseThrow();
}
}

Pregunta 10: ¿Cómo verificar el ámbito de un bean?

Respuesta:

@Autowired
private ConfigurableListableBeanFactory beanFactory;

public void checkScope() {
BeanDefinition definition = beanFactory.getBeanDefinition("myBean");
String scope = definition.getScope();
log.info("Scope: {}", scope); // singleton, prototype, etc.
}

Pregunta 11: ¿Cómo usar @RefreshScope?

Respuesta: Para los beans que deben ser recreados después de un refresh de configuración:

@RefreshScope
@Component
public class DynamicConfig {

@Value("${app.feature.enabled}")
private boolean featureEnabled;
}

Después de POST /actuator/refresh, el bean se recrea con los nuevos valores.


Pregunta 12: ¿Cómo probar los beans con ámbito?

Respuesta:

@SpringBootTest
@WebAppConfiguration // Para 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();
}
}
}

Pregunta 13: ¿Cómo se destruyen los beans prototype?

Respuesta: ¡Spring no destruye los beans prototype automáticamente!

@Component
@Scope("prototype")
public class ExpensiveResource implements DisposableBean {

@Override
public void destroy() {
// ¡Nunca se llama automáticamente!
}
}

Debe gestionarse manualmente o usar un BeanFactoryPostProcessor.


Pregunta 14: ¿Cómo obtener varios beans del mismo tipo?

Respuesta:

// Lista de todos los beans
@Autowired
private List<NotificationService> notificationServices;

// Mapa con el nombre como clave
@Autowired
private Map<String, NotificationService> notificationMap;

// ObjectProvider para acceso lazy
@Autowired
private ObjectProvider<NotificationService> provider;

public void notify() {
provider.forEach(service -> service.send(message));
}

Pregunta 15: ¿Cómo usar @Primary vs @Qualifier?

Respuesta:

// @Primary: bean por defecto
@Service
@Primary
public class EmailNotification implements NotificationService { }

@Service
public class SmsNotification implements NotificationService { }

// @Qualifier: elección explícita
public MyController(@Qualifier("smsNotification") NotificationService notif) { }

Preguntas 16-30: Configuración​

Pregunta 16: ¿Cómo leer una propiedad con @Value?

Respuesta:

@Value("${app.name}")
private String appName;

@Value("${app.timeout:5000}") // Valor por defecto
private int timeout;

@Value("${app.features}") // Lista: a,b,c
private List<String> features;

@Value("#{${app.map}}") // Map: {key1: 'val1', key2: 'val2'}
private Map<String, String> map;

Pregunta 17: ¿Cómo usar SpEL en @Value?

Respuesta:

@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;

Pregunta 18: ¿Cómo agrupar propiedades con @ConfigurationProperties?

Respuesta:

# 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 y setters
}

@Configuration
@EnableConfigurationProperties(MailProperties.class)
public class AppConfig { }

Pregunta 19: ¿Cómo validar las propiedades?

Respuesta:

@ConfigurationProperties(prefix = "app")
@Validated
public class AppProperties {

@NotNull
private String name;

@Min(1000)
@Max(65535)
private int port;

@Email
private String adminEmail;

@Valid // Validar objetos anidados
private DatabaseProperties database;
}

La aplicación falla al iniciar si es inválido.


Pregunta 20: ¿Cómo configurar objetos anidados?

Respuesta:

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
}
}

Pregunta 21: ¿Cómo configurar listas?

Respuesta:

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;
}
}

Pregunta 22: ¿Cómo usar perfiles de configuración?

Respuesta:

# application.yml (común)
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

Activación:

java -jar app.jar --spring.profiles.active=prod

Pregunta 23: ¿Cómo externalizar la configuración?

Respuesta: Orden de prioridad (de mayor a menor):

  1. Argumentos de línea de comandos
  2. Variables de entorno
  3. application-\{profile\}.yml externo
  4. application.yml externo
  5. application-\{profile\}.yml dentro del JAR
  6. application.yml dentro del JAR
java -jar app.jar --server.port=9090
export SERVER_PORT=9090

Pregunta 24: ¿Cómo cargar una configuración desde un archivo externo?

Respuesta:

# Archivo externo
java -jar app.jar --spring.config.location=file:/etc/myapp/config/

# Varios ubicaciones
java -jar app.jar --spring.config.location=classpath:/,file:/config/

# Archivo específico
java -jar app.jar --spring.config.name=custom

Pregunta 25: ¿Cómo usar variables de entorno?

Respuesta:

spring:
datasource:
url: ${DB_URL:jdbc:h2:mem:default}
username: ${DB_USER:sa}
password: ${DB_PASSWORD:}

Convención de mapeo:

  • app.my-property → APP_MY_PROPERTY
  • . → _
  • - → _

Pregunta 26: ¿Cómo cifrar las propiedades sensibles?

Respuesta: Con Jasypt o 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)

Pregunta 27: ¿Cómo crear un PropertySource personalizado?

Respuesta:

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));
}
}

Pregunta 28: ¿Cómo refrescar la configuración en caliente?

Respuesta:

@RefreshScope
@Component
public class DynamicConfig {

@Value("${app.feature.enabled}")
private boolean enabled;
}
# Con Spring Cloud Config
curl -X POST http://localhost:8080/actuator/refresh

Los beans @RefreshScope son recreados.


Pregunta 29: ¿Cómo probar las propiedades de configuración?

Respuesta:

@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);
}
}

// O con archivo de test
@SpringBootTest
@TestPropertySource("classpath:test-config.properties")
class ConfigTest { }

Pregunta 30: ¿Cuáles son las buenas prácticas de configuración?

Respuesta:

  1. Tipar con @ConfigurationProperties
  2. Validar con @Validated
  3. Externalizar los secretos
  4. Documentar cada propiedad
  5. Valores por defecto razonables
  6. Perfiles para los entornos
  7. No commitear los secretos
  8. Centralizar con Config Server en microservicios
@ConfigurationProperties(prefix = "app")
@Validated
public class AppProperties {

/**
* Nombre de la aplicación mostrado en los logs.
*/
@NotBlank
private String name = "My Application";
}

Puntaje​

PuntajeNivel
27-30Excelente - Ámbitos y configuración dominados
21-26Bien - Algunas revisiones
15-20Medio - Repase el capítulo
Menos de 15A revisar

Volver al Módulo 5