Implementación del patrón observador con eventos de Spring

Spring provee un mecanismo de eventos que permite la comunicación entre beans del contenedor. Cuando un bean completa una tarea y necesita que otro bean continúe el procesamiento, se puede utilizar el sistema de eventos de Spring para lograrlo.

Pasos para usar eventos en Spring

  1. Crear un evento personalizado heredando de ApplicationEvent
  2. Definir un listener implementando ApplicationListener
  3. Publicar el evento usando el contenedor de Spring

1. Definir un evento

public class PedidoEvent extends ApplicationEvent {
    private String cliente;
    private String accion;

    public PedidoEvent(Object source) {
        super(source);
    }

    public PedidoEvent(Object source, String cliente, String accion) {
        super(source);
        this.cliente = cliente;
        this.accion = accion;
    }

    public String getCliente() {
        return cliente;
    }

    public void setCliente(String cliente) {
        this.cliente = cliente;
    }

    public String getAccion() {
        return accion;
    }

    public void setAccion(String accion) {
        this.accion = accion;
    }
}

2. Crear el listener

@Component
public class PedidoListener implements ApplicationListener<PedidoEvent> {

    @Async
    @Override
    public void onApplicationEvent(PedidoEvent evento) {
        System.out.println("Cliente: " + evento.getCliente() + 
                           " - Acción: " + evento.getAccion());
    }
}

3. Publicar el evento

@Component
public class PublicadorEvento {

    @Autowired
    private ApplicationContext applicationContext;

    public void publicar(String cliente, String accion) {
        applicationContext.publishEvent(
            new PedidoEvent(this, cliente, accion));
    }
}

4. Configuración y prueba

@Configuration
@ComponentScan("com.ejemplo.eventos")
public class ConfigEvento {
}

public class Aplicacion {
    private static AnnotationConfigApplicationContext contexto;

    public static void main(String[] args) {
        iniciar();
    }

    private static void iniciar() {
        if (contexto == null) {
            contexto = new AnnotationConfigApplicationContext(
                ConfigEvento.class);
        }
        contexto.getBean(PublicadorEvento.class)
                .publicar("Juan Perez", "Devolución solicitada");
    }
}

Salida esperada: Cliente: Juan Perez - Acción: Devolución solicitada

¿Eventos de Application vs TaskExecutor?

Surge la pregunta: ¿qué diferencia hay entre usar eventos de Application y simplemente ejecutar tareas con un TaskExecutor? Ambos permiten ejecución asíncrona, pero el enfoque es distinto.

Ejemplo con AsyncExecutor:

@Configuration
@EnableAsync
public class ConfigAsync implements AsyncConfigurer {

    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(200);
        executor.setKeepAliveSeconds(60);
        executor.initialize();
        return executor;
    }
}

// Uso
@Autowired
private AsyncExecutor asyncExecutor;

Executor executor = asyncExecutor.getAsyncExecutor();
executor.execute(() -> {
    // Lógica del negocio
});

Ejemplo con TaskExecutor vía XML:

<bean id="taskExecutor" 
      class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
    <property name="corePoolSize" value="3" />
    <property name="maxPoolSize" value="10" />
    <property name="queueCapacity" value="25" />
    <property name="keepAliveSeconds" value="300" />
    <property name="rejectedExecutionHandler">
        <bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy" />
    </property>
</bean>

@Resource
private TaskExecutor taskExecutor;

taskExecutor.execute(() -> {
    // Lógica del negocio
});

La diferencia fundamental es que ApplicationEvent implementa el patrón observador, reduciendo el acoplamiento entre el publicader y el suscriptor. Aunque ambas alternativas pueden usar @Async para asincronía, los eventos siguen un diseño orientado a objetos más desacoplado, mientras que TaskExecutor simplemente ejecuta tareas en un hilo separado.

¿Observador vs Publicación/Suscripción (Pub/Sub)?

Esto lleva a otra pregunta: ¿es el patrón observador equivalente al modelo Pub/Sub usado en sistemas de mensajería (MQ)? La respuesta es que son similares pero no idénticos.

Diferencias clave:

  • En el patrón observador, el sujeto (publicador) conoce directamente a los observadores (suscriptores) y los notifica.
  • En Pub/Sub, existe un canal o broker intermedio que desacopla completamente al publicador del suscriptor.
  • El patrón observador opera dentro del mismo proceso, mientras que Pub/Sub suele ser cross-process (ej. RabbitMQ, Kafka).

Se puede decir que el patrón observador es una implementación más simple y acoplada, mientras que Pub/Sub representa una evolución más flexible y desacoplada, adecuada para sistemas distribuidos.

Etiquetas: Spring ApplicationEvent ObserverPattern PubSub AsyncExecutor

Publicado el 7-10 02:18