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:
| Modo | Uso |
|---|---|
DEFAULT | Sin proxy |
NO | Sin proxy |
INTERFACES | Proxy JDK (interfaz) |
TARGET_CLASS | Proxy 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:
- Beans stateless (sin campos mutables)
- Campos inmutables (final)
- Sincronización si es necesario
- Usar
ThreadLocalpara 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):
- Argumentos de línea de comandos
- Variables de entorno
application-\{profile\}.ymlexternoapplication.ymlexternoapplication-\{profile\}.ymldentro del JARapplication.ymldentro 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:
- Tipar con
@ConfigurationProperties - Validar con
@Validated - Externalizar los secretos
- Documentar cada propiedad
- Valores por defecto razonables
- Perfiles para los entornos
- No commitear los secretos
- 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
| Puntaje | Nivel |
|---|---|
| 27-30 | Excelente - Ámbitos y configuración dominados |
| 21-26 | Bien - Algunas revisiones |
| 15-20 | Medio - Repase el capítulo |
| Menos de 15 | A revisar |