CodeGym /Cursos /Spring Core /Perfiles de Spring: active/ default/ expr

Perfiles de Spring: active/ default/ expr

Spring Core
Nivel 14, Lección 2
Disponible

1. Perfiles y el momento refresh()

Si alguna vez intentaste «cambiarte sobre la marcha» y activar un perfil después del inicio del contexto, ya conoces esa sensación: parece que configuraste el perfil, pero los beans siguen igual. Aquí es importante captar la imagen correcta. Los perfiles afectan el registro y filtrado de BeanDefinition, y esto ocurre durante refresh() (o dentro del constructor del contexto, que llama a refresh() automáticamente). Después del inicio, el contenedor no está obligado a reensamblarse de nuevo — no es un constructor LEGO bajo demanda, es más bien un «robot ensamblado» que ya está corriendo.

Para ver este momento «en vivo», es útil tener en mente un esquema muy corto del arranque de AnnotationConfigApplicationContext:

flowchart TD
    A["Contexto creado"] --> B["Configuramos Environment (profiles, properties)"]
    B --> C["Registramos la configuración (register)"]
    C --> D["refresh()"]
    D --> E["Filtrado de @Profile y registro de BeanDefinitions"]
    E --> F["Creación de singleton beans"]
    F --> G["Contexto listo"]

La idea clave aquí es simple: los perfiles deben estar definidos antes del paso refresh(). Si refresh() ya ha pasado, tus beans con @Profile("demo") ya han entrado en el contexto o ya han sido descartados — y no se pueden «activar» de nuevo con un simple setter.

A continuación, un pequeño ejemplo de un error típico. Es especialmente engañoso porque a simple vista parece lógico: «bueno, solo voy a establecer el perfil en el environment».

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class WrongProfileTiming {
    public static void main(String[] args) {
        // IMPORTANTE: este constructor levanta el contexto por sí mismo y llama a refresh() internamente
        try (AnnotationConfigApplicationContext ctx =
                     new AnnotationConfigApplicationContext(AppConfig.class)) {

            // Error: cambiamos el perfil demasiado tarde — BeanDefinition ya han sido filtrados por @Profile
            ctx.getEnvironment().setActiveProfiles("demo");
            // Comprobamos si el bean que debería estar solo en el perfil demo está presente
            System.out.println(ctx.containsBean("fileAuditWriter")); // false
        }
    }
}

El comentario sobre este código suena casi como un diagnóstico: AnnotationConfigApplicationContext(AppConfig.class) ya levantó el contexto, dentro del constructor se ejecutó refresh(). Sí, cambiaste el Environment dentro del objeto, pero el filtrado de @Profile ya ocurrió antes. Como resultado, fileAuditWriter (que registraste, digamos, en el perfil demo) no aparecerá, aunque luego digas cien veces «perfil demo, de verdad, de verdad».

2. Activación de perfiles

Mediante programación con setActiveProfiles(...)

Cuando estamos aprendiendo, la forma más comprensible es activar el perfil desde el código. No es una «estrategia de producción para toda la vida», pero es una excelente manera de asegurarse de que entiendes la mecánica. En Spring puro (sin Boot) la forma más directa es crear el contexto vacío, establecer el perfil activo en Environment, registrar la configuración y solo entonces llamar a refresh(). Es decir, literalmente hacemos a mano lo que Boot luego hará «por nosotros».

Aquí hay una plantilla mínima funcional. Observa: usamos el constructor vacío y llamamos a refresh() explícitamente.

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class ProfiledStartup {
    public static void main(String[] args) {
        // Creamos el contexto "vacío": aún no se ha hecho refresh, los beans aún no están filtrados
        try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext()) {
            // Primero establecemos el perfil activo (es importante hacerlo ANTES del refresh)
            ctx.getEnvironment().setActiveProfiles("demo");
            // Registramos la configuración (clases @Configuration / @ComponentScan)
            ctx.register(AppConfig.class);
            // Ahora ejecutamos refresh() — aquí es donde se aplica el filtrado de @Profile
            ctx.refresh();

            // Verificamos que el bean de demo realmente se incluyó en el contexto
            System.out.println(ctx.containsBean("fileAuditWriter")); // true (en demo)
        }
    }
}

En este ejemplo todo ocurre en el orden «correcto». El perfil se estableció antes de refresh(), por lo que @Profile("demo") realmente afecta qué BeanDefinition entrarán en el contenedor.

Además, es útil recordar que setActiveProfiles(...) acepta varios valores. Spring puede hacerlo, pero para ContextFlow de este día es mejor mantener un modo de alto nivel por ejecución: dev o demo o test. Así la configuración sigue siendo legible, y no se convierte en una combinatoria de combinaciones aleatorias.

Mediante property: spring.profiles.active

Los perfiles son cosa del entorno, y el entorno la mayoría de las veces se quiere configurar desde fuera, no modificar el código ni recompilar la aplicación. Spring lo entiende y por eso puede leer los perfiles activos desde Environment mediante la clave spring.profiles.active. Para nosotros es conveniente: podemos ejecutar la misma aplicación en diferentes modos, simplemente cambiando una opción de VM o una variable de entorno. Y sí, funciona también en Spring puro, no solo en Spring Boot.

Desde un punto de vista práctico, existen varios «puntos de entrada» desde donde el valor puede llegar a Environment. Es conveniente verlos en una pequeña tabla:

Método Ejemplo Cuándo es conveniente
Propiedad del sistema JVM -Dspring.profiles.active=demo ejecución local desde IDE, scripts, Gradle run
Programáticamente mediante System.setProperty(...) System.setProperty("spring.profiles.active", "dev"); ejemplos educativos y demos (pero con cuidado)
Variable de entorno SPRING_PROFILES_ACTIVE=demo ejecución en CI, contenedores, configuración externa del SO

El ejemplo demostrativo más «honesto» es establecer la system property antes de crear el contexto. Entonces se puede incluso usar el constructor «conveniente» AnnotationConfigApplicationContext(AppConfig.class), porque durante su funcionamiento el valor ya estará disponible:

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class PropertyDrivenProfileStartup {
    public static void main(String[] args) {
        // IMPORTANTE: establecemos la system property antes de crear el contexto,
        // para que llegue al Environment antes del refresh()
        System.setProperty("spring.profiles.active", "dev");

        // Este constructor llamará a refresh() por sí mismo, y el perfil ya será visible para el contenedor
        try (AnnotationConfigApplicationContext ctx =
                     new AnnotationConfigApplicationContext(AppConfig.class)) {

            // En este ejemplo mantenemos un solo modo de alto nivel, por lo que imprimimos tranquilamente el primer elemento
            System.out.println(ctx.getEnvironment().getActiveProfiles()[0]); // dev
        }
    }
}

Aquí hay un matiz importante: System.setProperty(...) cambia el estado de toda la JVM. En una pequeña aplicación de consola es perdonable, pero en pruebas o en un inicio complejo se puede obtener el divertido efecto de «la primera prueba activó demo — las demás de repente también demo». Por lo tanto, en cuanto sientas que tienes más de un escenario de ejecución, el setActiveProfiles(...) programático en el contexto local se vuelve más seguro y predecible que el global System.setProperty(...).

3. Perfil predeterminado

Ahora analicemos algo que a menudo confunde a los principiantes: el perfil default. No significa «perfil de producción» ni significa «modo predeterminado que siempre está junto con todos». En sentido, es más bien un «modo de paracaídas de repuesto»: se usa solo si no se han establecido perfiles activos en absoluto. En cuanto hayas configurado al menos un perfil activo, default deja de participar en el juego.

Esto es fácil de comprobar con un fragmento de código muy corto. Muestra lo que muchos no esperan: puede haber cero perfiles activos, pero los perfiles default siguen existiendo.

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.util.Arrays;

public class DefaultProfilesDemo {
    public static void main(String[] args) {
        // Creamos el contexto sin perfiles ni configuración: solo miramos qué hay en Environment por defecto
        try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext()) {
            // Perfiles activos — vacío, porque no hemos definido nada
            System.out.println(Arrays.toString(ctx.getEnvironment().getActiveProfiles()));  // []
            // Los perfiles default existen siempre, a menos que los hayas redefinido explícitamente
            System.out.println(Arrays.toString(ctx.getEnvironment().getDefaultProfiles())); // [default]
        }
    }
}

Es decir, «perfiles activos» y «perfiles default» son dos entidades diferentes. La mecánica es más o menos así: si no se han establecido active, Spring al verificar @Profile se basa en default.

Entonces, ¿para qué sirve spring.profiles.default? Para renombrar o reemplazar el conjunto de perfiles que Spring considerará «predeterminados» si no se selecciona ningún perfil activo. Por ejemplo, si quieres que la ejecución local sin configuraciones adicionales se convierta automáticamente en dev, puedes hacerlo así:

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class CustomDefaultProfileStartup {
    public static void main(String[] args) {
        // Redefinimos el conjunto de perfiles default a nivel de JVM (para demo)
        System.setProperty("spring.profiles.default", "dev");

        // Creamos el contexto con el constructor "conveniente": se levantará de inmediato,
        // y el contenedor verá el perfil default al hacer refresh()
        try (AnnotationConfigApplicationContext ctx =
                     new AnnotationConfigApplicationContext(AppConfig.class)) {

            // Verificamos que el perfil dev se considera adecuado para la lógica de matching
            System.out.println(ctx.getEnvironment().matchesProfiles("dev")); // true
        }
    }
}

Y de nuevo — esta es una solución «sobre el entorno». En la vida real, es más común que establezcas SPRING_PROFILES_DEFAULT=dev o una opción de VM, en lugar de hardcodearlo en main(). Pero para entender la mecánica, este ejemplo funciona muy bien: ves que el perfil «predeterminado» vive separado del «activo», y se activa solo en ausencia de active.

Otro matiz que vale la pena mencionar explícitamente. La anotación @Profile("default") significa: «este bean se registrará si no se han establecido perfiles activos y entre los perfiles default está default». Es una buena manera de mantener una configuración «base» sin modos especiales, pero no hay que convertir default en «otro perfil normal» al mismo nivel que dev/demo/test. Para un proyecto es mucho más legible cuando ejecutas explícitamente dev que cuando intentas adivinar qué hay en default.

4. Profile expressions

Los perfiles rápidamente se topan con una tarea cotidiana: «este bean es necesario en todas partes excepto en pruebas» o «este bean es necesario en dev o demo». Si solo haces esto enumerando perfiles, puedes empezar a crear copias. Por eso Spring admite profile expressions — pequeñas expresiones lógicas dentro de @Profile, por ejemplo !test. No es magia ni un lenguaje de programación aparte, sino simplemente una lógica compacta de selección.

Para no convertir el tema en memorización, basta recordar tres operadores y paréntesis. Los he resumido en una tabla, porque la mente de un principiante ama las tablas más que los párrafos largos (y lo entiendo):

Operador Significado Ejemplo
! NO !test — «todo excepto test»
& Y demo & debug — «solo cuando ambos están activos»
| O dev | demo — «cuando al menos uno está activo»

Spring puede hacer coincidir combinaciones más complejas, pero para ContextFlow el modo de alto nivel sigue siendo uno: dev o demo o test. Las expressions aquí son útiles principalmente para condiciones puntuales como !test, no para mezclar libremente los tres modos.

El ejemplo más frecuente y más útil para nuestro ContextFlow: en modo test queremos desactivar efectos secundarios reales, por ejemplo notificaciones «reales». Entonces el bean responsable de las notificaciones «de producción» se puede marcar así:

import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;

// Significa: este componente se creará en todos los perfiles, EXCEPTO test
@Profile("!test")
@Component
class ConsoleNotificationSender implements NotificationSender {
    @Override
    public void send(String message) {
        // Aquí dejamos intencionadamente una acción "real": salida/notificación,
        // que en pruebas normalmente se quiere desactivar
        System.out.println("NOTIFY: " + message); // NOTIFY: ...
    }
}

Parece claro: las pruebas son pruebas, allí no se necesitan notificaciones. En todos los demás modos, por favor.

Otro ejemplo — «dev o demo». A menudo se escribe como @Profile({"dev", "demo"}) o como expresión @Profile("dev | demo"). Para un principiante, la primera opción suele ser más legible porque tiene menos símbolos que parecen «matemáticas de secundaria». Pero la expresión es útil cuando quieres añadir negación o combinaciones.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

@Configuration
class NonTestAuditConfig {
    @Bean
    // Expresión: el bean se registrará si está activo dev O demo
    @Profile("dev | demo")
    AuditWriter consoleAuditWriter() {
        // Devolvemos una implementación concreta que queremos incluir solo en estos modos
        return new ConsoleAuditWriter();
    }
}

Aquí es importante no engañarse: la expresión de perfil se evalúa al iniciar el contenedor, no «en cada llamada al método». Es decir, no es una ramificación en tiempo de ejecución, sigue siendo un registro condicional. Si activaste el perfil, el bean estará o no estará. Nada de «hoy soy dev, y en un minuto demo» dentro de un mismo contexto. Y menos mal, de lo contrario la depuración se convertiría en un horror.

5. Impresión de perfiles active y default

Cuando los perfiles ya están viviendo en la configuración, es muy deseable una autocomprobación rápida: «¿estoy seguro de que inicié en el modo que creo?» Esto es especialmente útil en un proyecto educativo, donde cambias a menudo dev/demo/test y quieres ver que el contenedor realmente reunió diferentes implementaciones. La forma más sencilla es imprimir los perfiles en main() después de refresh().

A continuación, un ejemplo mínimo para ejecutar ContextFlow con un perfil tomado de los argumentos de línea de comandos. No convierte el proyecto en una combinación CLI, pero proporciona un control honesto sobre la configuración.

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class ContextFlowMain {
    public static void main(String[] args) {
        // Tomamos el perfil de los argumentos de línea de comandos, de lo contrario usamos dev como "predeterminado obvio" para ejecución educativa
        String profile = (args.length > 0) ? args[0] : "dev";

        // Creamos el contexto vacío para poder establecer el perfil ANTES de refresh()
        try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext()) {
            // El perfil debe establecerse antes del registro/refresh, para que @Profile pueda filtrar los beans
            ctx.getEnvironment().setActiveProfiles(profile);
            // Registramos la configuración de la aplicación
            ctx.register(AppConfig.class);
            // Levantamos el contexto
            ctx.refresh();

            // Autocomprobación rápida: en qué modo realmente nos ejecutamos
            System.out.println("Active profile = " + profile); // Active profile = dev
            // Ejecutamos el escenario que utiliza el conjunto de beans recopilado
            ctx.getBean(ScenarioRunner.class).run();
        }
    }
}

Observa un punto metodológico: imprimimos lo que elegimos, no intentamos «adivinar» por signos indirectos. Sí, se puede imprimir ctx.getEnvironment().getActiveProfiles(), pero en código educativo a veces es incluso más fácil imprimir la variable profile para que el ojo se fije de inmediato. Cuando la aplicación se haga más grande, por supuesto, lo registrarás de forma más ordenada, pero ahora simplemente hacemos observable el comportamiento del contenedor.

Si quieres un poco más formal, puedes imprimir ambos arrays — active y default. Solo no te asustes cuando active esté vacío: es normal si ejecutas sin un perfil explícito y confías en default.

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.util.Arrays;

public class ProfilesPrint {
    public static void main(String[] args) {
        // Aquí usamos el constructor "conveniente" — levantará el contexto de inmediato
        try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class)) {
            // Perfiles activos: lo que se activó explícitamente (puede estar vacío)
            System.out.println(Arrays.toString(ctx.getEnvironment().getActiveProfiles()));  // []
            // Perfiles default: conjunto de respaldo que se usa si active está vacío
            System.out.println(Arrays.toString(ctx.getEnvironment().getDefaultProfiles())); // [default]
        }
    }
}

6. Errores típicos al trabajar con perfiles

Error nº1: activar el perfil después de refresh() y esperar que el contexto se «reensamble».
Es la trampa más popular. El perfil afecta el registro de los beans, y el registro ocurre antes de la creación de objetos. Cuando el contexto ya está levantado, cambiar el profile en Environment es como cambiar el plano de la casa después de haberla construido. Se puede, por supuesto, demoler y construir de nuevo, pero eso ya es otro contexto, no un «cambio de modo».

Error nº2: pensar que default funciona simultáneamente con cualquier perfil activo.
La mecánica de default es un fallback en ausencia de active. En cuanto hayas establecido dev, demo o test, el perfil default deja de tenerse en cuenta en las comprobaciones de @Profile. Por eso un bean con @Profile("default") desaparece de repente, y el principiante piensa: «Spring se rompió». No, simplemente está diseñado así.

Error nº3: poner System.setProperty("spring.profiles.active"...) en pruebas y olvidar limpiar.
Las propiedades del sistema son globales en la JVM. Una prueba estableció demo, otra prueba esperaba test o un active vacío — y obtienes pruebas inestables sin una sola línea de concurrencia. En ejemplos educativos System.setProperty es aceptable, pero en la realidad es mejor gestionar los perfiles a través del Environment del contexto específico o mediante la configuración de inicio del contexto de prueba.

Error nº4: convertir los perfiles en «configuraciones para cada pequeñez».
El perfil debe describir el modo de funcionamiento de la aplicación, no el valor de un solo número. Si hiciste perfiles limit-10, limit-20, limit-30, entonces construiste un sistema imposible de mantener: hay demasiadas combinaciones, el significado de los perfiles se difumina. Para números y cadenas tenemos properties; los perfiles son para seleccionar un conjunto de implementaciones.

Error nº5: escribir expresiones de perfil de manera que solo las entienda el autor y su gato (y a veces ni eso).
@Profile("dev & (demo | !test) & (blue | green)") parece inteligente justo hasta la primera revisión de código. Las expresiones de perfil son útiles, pero es mejor mantenerlas simples: !test, dev | demo, como máximo algo como demo & debug. Si la expresión es más compleja, generalmente gana la división de la configuración en módulos claros y perfiles explícitos.

1
Tarea
Spring Core, nivel 14, lección 2
Bloqueada
Perfil predeterminado mediante `spring.profiles.default`
Perfil predeterminado mediante `spring.profiles.default`
1
Tarea
Spring Core, nivel 14, lección 2
Bloqueada
Profile expression `!test` y activación mediante `spring.profiles.active`
Profile expression `!test` y activación mediante `spring.profiles.active`
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION