From 49916bb7eb67d9cb8f20d524b4d09e093aab6fa2 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Thu, 10 Apr 2014 23:06:25 +0200 Subject: [PATCH] SimpleApplicationEventMulticaster supports a configurable ErrorHandler strategy Issue: SPR-11551 --- .../SimpleApplicationEventMulticaster.java | 74 ++++++++++++++++--- .../event/ApplicationContextEventTests.java | 56 ++++++++++++++ 2 files changed, 119 insertions(+), 11 deletions(-) diff --git a/spring-context/src/main/java/org/springframework/context/event/SimpleApplicationEventMulticaster.java b/spring-context/src/main/java/org/springframework/context/event/SimpleApplicationEventMulticaster.java index 6b73c9cfa46..c53475223a7 100644 --- a/spring-context/src/main/java/org/springframework/context/event/SimpleApplicationEventMulticaster.java +++ b/spring-context/src/main/java/org/springframework/context/event/SimpleApplicationEventMulticaster.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ import java.util.concurrent.Executor; import org.springframework.beans.factory.BeanFactory; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; +import org.springframework.util.ErrorHandler; /** * Simple implementation of the {@link ApplicationEventMulticaster} interface. @@ -32,7 +33,7 @@ import org.springframework.context.ApplicationListener; * *

By default, all listeners are invoked in the calling thread. * This allows the danger of a rogue listener blocking the entire application, - * but adds minimal overhead. Specify an alternative TaskExecutor to have + * but adds minimal overhead. Specify an alternative task executor to have * listeners executed in different threads, for example from a thread pool. * * @author Rod Johnson @@ -43,6 +44,8 @@ public class SimpleApplicationEventMulticaster extends AbstractApplicationEventM private Executor taskExecutor; + private ErrorHandler errorHandler; + /** * Create a new SimpleApplicationEventMulticaster. @@ -59,10 +62,11 @@ public class SimpleApplicationEventMulticaster extends AbstractApplicationEventM /** - * Set the TaskExecutor to execute application listeners with. - *

Default is a SyncTaskExecutor, executing the listeners synchronously - * in the calling thread. - *

Consider specifying an asynchronous TaskExecutor here to not block the + * Set a custom executor (typically a {@link org.springframework.core.task.TaskExecutor}) + * to invoke each listener with. + *

Default is equivalent to {@link org.springframework.core.task.SyncTaskExecutor}, + * executing all listeners synchronously in the calling thread. + *

Consider specifying an asynchronous task executor here to not block the * caller until all listeners have been executed. However, note that asynchronous * execution will not participate in the caller's thread context (class loader, * transaction association) unless the TaskExecutor explicitly supports this. @@ -74,30 +78,78 @@ public class SimpleApplicationEventMulticaster extends AbstractApplicationEventM } /** - * Return the current TaskExecutor for this multicaster. + * Return the current task executor for this multicaster. */ protected Executor getTaskExecutor() { return this.taskExecutor; } + /** + * Set the {@link ErrorHandler} to invoke in case of an exception thrown + * from a listener. + *

Default is none, with a listener exception stopping the current + * multicast and getting propagated to the publisher of the current event. + * In case of a {@link #setTaskExecutor task executor} specified, each + * individual listener exception will get propagated to the executor but + * won't necessarily stop execution of other listeners. + *

Consider setting an {@link ErrorHandler} implementation that catches + * and logs exceptions (a la + * {@link org.springframework.scheduling.support.TaskUtils#LOG_AND_SUPPRESS_ERROR_HANDLER}) + * or an implementation that logs exceptions while nevertheless propagating them + * ({@link org.springframework.scheduling.support.TaskUtils#LOG_AND_PROPAGATE_ERROR_HANDLER}). + * @since 4.1 + */ + public void setErrorHandler(ErrorHandler errorHandler) { + this.errorHandler = errorHandler; + } + + /** + * Return the current error handler for this multicaster. + * @since 4.1 + */ + protected ErrorHandler getErrorHandler() { + return this.errorHandler; + } + @Override - @SuppressWarnings({ "unchecked", "rawtypes" }) public void multicastEvent(final ApplicationEvent event) { - for (final ApplicationListener listener : getApplicationListeners(event)) { + for (final ApplicationListener listener : getApplicationListeners(event)) { Executor executor = getTaskExecutor(); if (executor != null) { executor.execute(new Runnable() { @Override public void run() { - listener.onApplicationEvent(event); + invokeListener(listener, event); } }); } else { - listener.onApplicationEvent(event); + invokeListener(listener, event); } } } + /** + * Invoke the given listener with the given event. + * @param listener the ApplicationListener to invoke + * @param event the current event to propagate + * @since 4.1 + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + protected void invokeListener(ApplicationListener listener, ApplicationEvent event) { + ErrorHandler errorHandler = getErrorHandler(); + if (errorHandler != null) { + try { + listener.onApplicationEvent(event); + } + catch (Throwable err) { + errorHandler.handleError(err); + } + } + else { + listener.onApplicationEvent(event); + } + } + } diff --git a/spring-context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java b/spring-context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java index a882ba8e9f1..92e8b1cffd5 100644 --- a/spring-context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java +++ b/spring-context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java @@ -18,6 +18,7 @@ package org.springframework.context.event; import java.util.HashSet; import java.util.Set; +import java.util.concurrent.Executor; import org.aopalliance.intercept.MethodInvocation; import org.junit.Test; @@ -33,6 +34,7 @@ import org.springframework.context.BeanThatListens; import org.springframework.context.support.AbstractApplicationContext; import org.springframework.context.support.StaticApplicationContext; import org.springframework.core.Ordered; +import org.springframework.scheduling.support.TaskUtils; import org.springframework.tests.sample.beans.TestBean; import static org.junit.Assert.*; @@ -59,6 +61,60 @@ public class ApplicationContextEventTests { verify(listener).onApplicationEvent(evt); } + @Test + public void simpleApplicationEventMulticasterWithTaskExecutor() { + @SuppressWarnings("unchecked") + ApplicationListener listener = mock(ApplicationListener.class); + ApplicationEvent evt = new ContextClosedEvent(new StaticApplicationContext()); + + SimpleApplicationEventMulticaster smc = new SimpleApplicationEventMulticaster(); + smc.setTaskExecutor(new Executor() { + @Override + public void execute(Runnable command) { + command.run(); + command.run(); + } + }); + smc.addApplicationListener(listener); + + smc.multicastEvent(evt); + verify(listener, times(2)).onApplicationEvent(evt); + } + + @Test + public void simpleApplicationEventMulticasterWithException() { + @SuppressWarnings("unchecked") + ApplicationListener listener = mock(ApplicationListener.class); + ApplicationEvent evt = new ContextClosedEvent(new StaticApplicationContext()); + + SimpleApplicationEventMulticaster smc = new SimpleApplicationEventMulticaster(); + smc.addApplicationListener(listener); + + RuntimeException thrown = new RuntimeException(); + doThrow(thrown).when(listener).onApplicationEvent(evt); + try { + smc.multicastEvent(evt); + fail("Should have thrown RuntimeException"); + } + catch (RuntimeException ex) { + assertSame(thrown, ex); + } + } + + @Test + public void simpleApplicationEventMulticasterWithErrorHandler() { + @SuppressWarnings("unchecked") + ApplicationListener listener = mock(ApplicationListener.class); + ApplicationEvent evt = new ContextClosedEvent(new StaticApplicationContext()); + + SimpleApplicationEventMulticaster smc = new SimpleApplicationEventMulticaster(); + smc.setErrorHandler(TaskUtils.LOG_AND_SUPPRESS_ERROR_HANDLER); + smc.addApplicationListener(listener); + + doThrow(new RuntimeException()).when(listener).onApplicationEvent(evt); + smc.multicastEvent(evt); + } + @Test public void orderedListeners() { MyOrderedListener1 listener1 = new MyOrderedListener1();