SimpleApplicationEventMulticaster supports a configurable ErrorHandler strategy
Issue: SPR-11551
This commit is contained in:
@@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.beans.factory.BeanFactory;
|
||||||
import org.springframework.context.ApplicationEvent;
|
import org.springframework.context.ApplicationEvent;
|
||||||
import org.springframework.context.ApplicationListener;
|
import org.springframework.context.ApplicationListener;
|
||||||
|
import org.springframework.util.ErrorHandler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simple implementation of the {@link ApplicationEventMulticaster} interface.
|
* Simple implementation of the {@link ApplicationEventMulticaster} interface.
|
||||||
@@ -32,7 +33,7 @@ import org.springframework.context.ApplicationListener;
|
|||||||
*
|
*
|
||||||
* <p>By default, all listeners are invoked in the calling thread.
|
* <p>By default, all listeners are invoked in the calling thread.
|
||||||
* This allows the danger of a rogue listener blocking the entire application,
|
* 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.
|
* listeners executed in different threads, for example from a thread pool.
|
||||||
*
|
*
|
||||||
* @author Rod Johnson
|
* @author Rod Johnson
|
||||||
@@ -43,6 +44,8 @@ public class SimpleApplicationEventMulticaster extends AbstractApplicationEventM
|
|||||||
|
|
||||||
private Executor taskExecutor;
|
private Executor taskExecutor;
|
||||||
|
|
||||||
|
private ErrorHandler errorHandler;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new SimpleApplicationEventMulticaster.
|
* Create a new SimpleApplicationEventMulticaster.
|
||||||
@@ -59,10 +62,11 @@ public class SimpleApplicationEventMulticaster extends AbstractApplicationEventM
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the TaskExecutor to execute application listeners with.
|
* Set a custom executor (typically a {@link org.springframework.core.task.TaskExecutor})
|
||||||
* <p>Default is a SyncTaskExecutor, executing the listeners synchronously
|
* to invoke each listener with.
|
||||||
* in the calling thread.
|
* <p>Default is equivalent to {@link org.springframework.core.task.SyncTaskExecutor},
|
||||||
* <p>Consider specifying an asynchronous TaskExecutor here to not block the
|
* executing all listeners synchronously in the calling thread.
|
||||||
|
* <p>Consider specifying an asynchronous task executor here to not block the
|
||||||
* caller until all listeners have been executed. However, note that asynchronous
|
* caller until all listeners have been executed. However, note that asynchronous
|
||||||
* execution will not participate in the caller's thread context (class loader,
|
* execution will not participate in the caller's thread context (class loader,
|
||||||
* transaction association) unless the TaskExecutor explicitly supports this.
|
* 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() {
|
protected Executor getTaskExecutor() {
|
||||||
return this.taskExecutor;
|
return this.taskExecutor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the {@link ErrorHandler} to invoke in case of an exception thrown
|
||||||
|
* from a listener.
|
||||||
|
* <p>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.
|
||||||
|
* <p>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
|
@Override
|
||||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
|
||||||
public void multicastEvent(final ApplicationEvent event) {
|
public void multicastEvent(final ApplicationEvent event) {
|
||||||
for (final ApplicationListener listener : getApplicationListeners(event)) {
|
for (final ApplicationListener<?> listener : getApplicationListeners(event)) {
|
||||||
Executor executor = getTaskExecutor();
|
Executor executor = getTaskExecutor();
|
||||||
if (executor != null) {
|
if (executor != null) {
|
||||||
executor.execute(new Runnable() {
|
executor.execute(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
listener.onApplicationEvent(event);
|
invokeListener(listener, event);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else {
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ package org.springframework.context.event;
|
|||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
import org.aopalliance.intercept.MethodInvocation;
|
import org.aopalliance.intercept.MethodInvocation;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@@ -33,6 +34,7 @@ import org.springframework.context.BeanThatListens;
|
|||||||
import org.springframework.context.support.AbstractApplicationContext;
|
import org.springframework.context.support.AbstractApplicationContext;
|
||||||
import org.springframework.context.support.StaticApplicationContext;
|
import org.springframework.context.support.StaticApplicationContext;
|
||||||
import org.springframework.core.Ordered;
|
import org.springframework.core.Ordered;
|
||||||
|
import org.springframework.scheduling.support.TaskUtils;
|
||||||
import org.springframework.tests.sample.beans.TestBean;
|
import org.springframework.tests.sample.beans.TestBean;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
@@ -59,6 +61,60 @@ public class ApplicationContextEventTests {
|
|||||||
verify(listener).onApplicationEvent(evt);
|
verify(listener).onApplicationEvent(evt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void simpleApplicationEventMulticasterWithTaskExecutor() {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
ApplicationListener<ApplicationEvent> 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<ApplicationEvent> 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<ApplicationEvent> 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
|
@Test
|
||||||
public void orderedListeners() {
|
public void orderedListeners() {
|
||||||
MyOrderedListener1 listener1 = new MyOrderedListener1();
|
MyOrderedListener1 listener1 = new MyOrderedListener1();
|
||||||
|
|||||||
Reference in New Issue
Block a user