Saltar al contenido principal

Listeners del Ciclo de Vida 25.02

Abrir en ChatGPT

La interfaz AppLifecycleListener permite que el código externo observe y responda a los eventos del ciclo de vida de la aplicación. Al implementar esta interfaz, puedes ejecutar código en momentos específicos durante el inicio y la finalización de la aplicación sin modificar la clase App misma.

Los listeners del ciclo de vida se descubren y cargan automáticamente en tiempo de ejecución a través de archivos de configuración del proveedor de servicios. Cada instancia de aplicación recibe su propio conjunto de instancias de listeners, manteniendo la isolación entre las diferentes aplicaciones que se ejecutan en el mismo entorno.

Cuándo usar listeners del ciclo de vida

Utiliza listeners del ciclo de vida cuando necesites:

  • Inicializar recursos o servicios antes de que una aplicación se ejecute
  • Limpiar recursos cuando una aplicación termina
  • Agregar preocupaciones transversales sin modificar la clase App
  • Construir arquitecturas de plugins

La interfaz AppLifecycleListener

AppLifecycleListener.java
public interface AppLifecycleListener {
default void onWillCreate(Environment env) {} // Desde 25.03
default void onDidCreate(App app) {} // Desde 25.03
default void onWillRun(App app) {}
default void onDidRun(App app) {}
default void onWillTerminate(App app) {}
default void onDidTerminate(App app) {}
}
Aislamiento de la aplicación

Cada instancia de aplicación recibe su propio conjunto de instancias de listeners:

  • Los listeners están aislados entre diferentes aplicaciones
  • Los campos estáticos en los listeners no se compartirán entre aplicaciones
  • Las instancias de listeners se crean cuando la aplicación inicia y se destruyen cuando termina

Si necesitas compartir datos entre aplicaciones, utiliza mecanismos de almacenamiento externos como bases de datos o servicios compartidos.

Eventos del ciclo de vida

EventoCuándo se llamaUsos comunes
onWillCreate 25.03Después de la inicialización del entorno, antes de la creación de la aplicaciónModificar la configuración, fusionar fuentes de configuración externas
onDidCreate 25.03Después de la instanciación de la aplicación, antes de la inicializaciónConfiguración temprana a nivel de aplicación, registrar servicios
onWillRunAntes de que se ejecute app.run()Inicializar recursos, configurar servicios
onDidRunDespués de que app.run() se complete exitosamenteIniciar tareas en segundo plano, registrar inicio exitoso
onWillTerminateAntes de la terminación de la aplicaciónGuardar estado, prepararse para el cierre
onDidTerminateDespués de la terminación de la aplicaciónLimpiar recursos, registro final

Creando un listener del ciclo de vida

Implementación básica

StartupListener.java
import com.webforj.App;
import com.webforj.AppLifecycleListener;
import com.webforj.Environment;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;

public class StartupListener implements AppLifecycleListener {

@Override
public void onWillCreate(Environment env) {
// Modificar configuración antes de la creación de la aplicación
Config additionalConfig = ConfigFactory.parseString(
"myapp.feature.enabled = true"
);
env.setConfig(additionalConfig);
}

@Override
public void onDidCreate(App app) {
System.out.println("Aplicación creada: " + app.getId());
}

@Override
public void onWillRun(App app) {
System.out.println("Aplicación iniciando: " + app.getId());
}

@Override
public void onDidRun(App app) {
System.out.println("Aplicación iniciada: " + app.getId());
}
}

Registrando el listener

Crea un archivo de configuración del proveedor de servicios:

Archivo: src/main/resources/META-INF/services/com.webforj.AppLifecycleListener

com.example.listeners.StartupListener
Usando AutoService

Es fácil olvidar actualizar los descriptores de servicio. Utiliza AutoService de Google para generar automáticamente el archivo de servicio:

StartupListener.java
import com.google.auto.service.AutoService;

@AutoService(AppLifecycleListener.class)
public class StartupListener implements AppLifecycleListener {
// Implementación
}

Controlando el orden de ejecución

Cuando se registran múltiples listeners, puedes controlar su orden de ejecución utilizando la anotación @AppListenerPriority. Esto es particularmente importante cuando los listeners tienen dependencias entre sí o cuando ciertas inicializaciones deben ocurrir antes que otras.

Los valores de prioridad funcionan en orden ascendente - números más bajos se ejecutan primero. La prioridad predeterminada es 10, por lo que los listeners sin anotaciones de prioridad explícitas se ejecutarán después de aquellos con valores de prioridad más bajos.

SecurityListener.java
@AutoService(AppLifecycleListener.class)
@AppListenerPriority(1) // Se ejecuta primero - configuración de seguridad crítica
public class SecurityListener implements AppLifecycleListener {
@Override
public void onWillRun(App app) {
initializeSecurity();
}
}

@AutoService(AppLifecycleListener.class)
@AppListenerPriority(10) // Prioridad predeterminada - registro general
public class LoggingListener implements AppLifecycleListener {
@Override
public void onWillRun(App app) {
initializeLogging();
}
}

Flujo de ejecución con hooks de App

Más allá de controlar el orden entre múltiples listeners, es importante entender cómo los listeners interactúan con los propios hooks del ciclo de vida de la clase App. Para cada evento del ciclo de vida, el marco sigue una secuencia de ejecución específica que determina cuándo se ejecutan tus listeners en relación con los hooks integrados de la aplicación.

El diagrama a continuación ilustra este flujo de ejecución, mostrando el momento preciso en que se llaman los métodos AppLifecycleListener en relación con los correspondientes hooks de App:

Listeners de AppLifecycleListener VS hooks de App

Manejo de errores

Las excepciones lanzadas por los listeners se registran pero no evitan que otros listeners se ejecuten o que la aplicación se ejecute. Siempre maneja las excepciones dentro de tus listeners:

Ejemplo de manejo de errores
@Override
public void onWillRun(App app) {
try {
riskyInitialization();
} catch (Exception e) {
logger.error("La inicialización falló", e);
}
}