提交 f0fca890 编写于 作者: S Stephane Nicoll

Annotation-based event listeners

Add support for annotation-based event listeners. Enabled automatically
when using Java configuration or can be enabled explicitly via the
regular <context:annotation-driven/> XML element. Detect methods of
managed beans annotated with @EventListener, either directly or through
a meta-annotation.

Annotated methods must define the event type they listen to as a single
parameter argument. Events are automatically filtered out according to
the method signature. When additional runtime filtering is required, one
can specify the `condition` attribute of the annotation that defines a
SpEL expression that should match to actually invoke the method for a
particular event. The root context exposes the actual `event`
(`#root.event`) and method arguments (`#root.args`). Individual method
arguments are also exposed via either the `a` or `p` alias (`#a0` refers
to the first method argument). Finally, methods arguments are exposed via
their names if that information can be discovered.

Events can be either an ApplicationEvent or any arbitrary payload. Such
payload is wrapped automatically in a PayloadApplicationEvent and managed
explicitly internally. As a result, users can now publish and listen
for arbitrary objects.

If an annotated method has a return value, an non null result is actually
published as a new event, something like:

@EventListener
public FooEvent handle(BarEvent event) { ... }

Events can be handled in an aynchronous manner by adding `@Async` to the
event method declaration and enabling such infrastructure. Events can
also be ordered by adding an `@order` annotation to the event method.

Issue: SPR-11622
上级 6d6422ac
......@@ -19,13 +19,9 @@ package org.springframework.cache.interceptor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.springframework.aop.support.AopUtils;
import org.springframework.context.expression.AnnotatedElementKey;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.ObjectUtils;
/**
* Cache specific evaluation context that adds a method parameters as SpEL
......@@ -44,32 +40,14 @@ import org.springframework.util.ObjectUtils;
* @author Stephane Nicoll
* @since 3.1
*/
class CacheEvaluationContext extends StandardEvaluationContext {
private final ParameterNameDiscoverer paramDiscoverer;
private final Method method;
private final Object[] args;
private final Class<?> targetClass;
private final Map<AnnotatedElementKey, Method> methodCache;
class CacheEvaluationContext extends MethodBasedEvaluationContext {
private final List<String> unavailableVariables;
private boolean paramLoaded = false;
CacheEvaluationContext(Object rootObject, Method method, Object[] args,
ParameterNameDiscoverer paramDiscoverer) {
CacheEvaluationContext(Object rootObject, ParameterNameDiscoverer paramDiscoverer, Method method,
Object[] args, Class<?> targetClass, Map<AnnotatedElementKey, Method> methodCache) {
super(rootObject);
this.paramDiscoverer = paramDiscoverer;
this.method = method;
this.args = args;
this.targetClass = targetClass;
this.methodCache = methodCache;
super(rootObject, method, args, paramDiscoverer);
this.unavailableVariables = new ArrayList<String>();
}
......@@ -93,47 +71,7 @@ class CacheEvaluationContext extends StandardEvaluationContext {
if (this.unavailableVariables.contains(name)) {
throw new VariableNotAvailableException(name);
}
Object variable = super.lookupVariable(name);
if (variable != null) {
return variable;
}
if (!this.paramLoaded) {
loadArgsAsVariables();
this.paramLoaded = true;
variable = super.lookupVariable(name);
}
return variable;
}
private void loadArgsAsVariables() {
// shortcut if no args need to be loaded
if (ObjectUtils.isEmpty(this.args)) {
return;
}
AnnotatedElementKey methodKey = new AnnotatedElementKey(this.method, this.targetClass);
Method targetMethod = this.methodCache.get(methodKey);
if (targetMethod == null) {
targetMethod = AopUtils.getMostSpecificMethod(this.method, this.targetClass);
if (targetMethod == null) {
targetMethod = this.method;
}
this.methodCache.put(methodKey, targetMethod);
}
// save arguments as indexed variables
for (int i = 0; i < this.args.length; i++) {
setVariable("a" + i, this.args[i]);
setVariable("p" + i, this.args[i]);
}
String[] parameterNames = this.paramDiscoverer.getParameterNames(targetMethod);
// save parameter names (if discovered)
if (parameterNames != null) {
for (int i = 0; i < parameterNames.length; i++) {
setVariable(parameterNames[i], this.args[i]);
}
}
return super.lookupVariable(name);
}
}
......@@ -21,6 +21,7 @@ import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.aop.support.AopUtils;
import org.springframework.cache.Cache;
import org.springframework.context.expression.AnnotatedElementKey;
import org.springframework.context.expression.CachedExpressionEvaluator;
......@@ -98,8 +99,9 @@ class ExpressionEvaluator extends CachedExpressionEvaluator {
CacheExpressionRootObject rootObject = new CacheExpressionRootObject(caches,
method, args, target, targetClass);
Method targetMethod = getTargetMethod(targetClass, method);
CacheEvaluationContext evaluationContext = new CacheEvaluationContext(rootObject,
this.paramNameDiscoverer, method, args, targetClass, this.targetMethodCache);
targetMethod, args, this.paramNameDiscoverer);
if (result == RESULT_UNAVAILABLE) {
evaluationContext.addUnavailableVariable(RESULT_VARIABLE);
}
......@@ -121,5 +123,18 @@ class ExpressionEvaluator extends CachedExpressionEvaluator {
return getExpression(this.unlessCache, methodKey, unlessExpression).getValue(evalContext, boolean.class);
}
private Method getTargetMethod(Class<?> targetClass, Method method) {
AnnotatedElementKey methodKey = new AnnotatedElementKey(method, targetClass);
Method targetMethod = this.targetMethodCache.get(methodKey);
if (targetMethod == null) {
targetMethod = AopUtils.getMostSpecificMethod(method, targetClass);
if (targetMethod == null) {
targetMethod = method;
}
this.targetMethodCache.put(methodKey, targetMethod);
}
return targetMethod;
}
}
......@@ -21,6 +21,7 @@ package org.springframework.context;
* Serves as super-interface for ApplicationContext.
*
* @author Juergen Hoeller
* @author Stephane Nicoll
* @since 1.1.1
* @see ApplicationContext
* @see ApplicationEventPublisherAware
......@@ -38,4 +39,14 @@ public interface ApplicationEventPublisher {
*/
void publishEvent(ApplicationEvent event);
/**
* Notify all <strong>matching</strong> listeners registered with this
* application of an event.
* <p>If the specified {@code event} is not an {@link ApplicationEvent}, it
* is wrapped in a {@code GenericApplicationEvent}.
* @param event the event to publish
* @see PayloadApplicationEvent
*/
void publishEvent(Object event);
}
/*
* Copyright 2002-2015 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context;
import org.springframework.util.Assert;
/**
* An {@link ApplicationEvent} that carries an arbitrary payload.
* <p>
* Mainly intended for internal use within the framework.
*
* @param <T> the payload type of the event
* @author Stephane Nicoll
* @since 4.2
*/
@SuppressWarnings("serial")
public class PayloadApplicationEvent<T>
extends ApplicationEvent {
private final T payload;
public PayloadApplicationEvent(Object source, T payload) {
super(source);
Assert.notNull(payload, "Payload must not be null");
this.payload = payload;
}
/**
* Return the payload of the event.
*/
public T getPayload() {
return payload;
}
}
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 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.
......@@ -30,6 +30,7 @@ import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.event.EventListenerMethodProcessor;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
......@@ -48,6 +49,7 @@ import org.springframework.util.ClassUtils;
* @author Juergen Hoeller
* @author Chris Beams
* @author Phillip Webb
* @author Stephane Nicoll
* @since 2.5
* @see ContextAnnotationAutowireCandidateResolver
* @see CommonAnnotationBeanPostProcessor
......@@ -103,6 +105,11 @@ public class AnnotationConfigUtils {
private static final String PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME =
"org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor";
/**
* The bean name of the internally managed @EventListener annotation processor.
*/
public static final String EVENT_LISTENER_PROCESSOR_BEAN_NAME =
"org.springframework.context.event.internalEventListenerProcessor";
private static final boolean jsr250Present =
ClassUtils.isPresent("javax.annotation.Resource", AnnotationConfigUtils.class.getClassLoader());
......@@ -183,6 +190,12 @@ public class AnnotationConfigUtils {
beanDefs.add(registerPostProcessor(registry, def, PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME));
}
if (!registry.containsBeanDefinition(EVENT_LISTENER_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(EventListenerMethodProcessor.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_PROCESSOR_BEAN_NAME));
}
return beanDefs;
}
......
/*
* Copyright 2002-2015 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.event;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.UndeclaredThrowableException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.PayloadApplicationEvent;
import org.springframework.context.expression.AnnotatedElementKey;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.expression.EvaluationContext;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
* {@link GenericApplicationListener} adapter that delegates the processing of
* an event to an {@link EventListener} annotated method.
*
* <p>Unwrap the content of a {@link PayloadApplicationEvent} if necessary
* to allow method declaration to define any arbitrary event type.
*
* <p>If a condition is defined, it is evaluated prior to invoking the
* underlying method.
*
* @author Stephane Nicoll
* @since 4.2
*/
public class ApplicationListenerMethodAdapter implements GenericApplicationListener {
protected final Log logger = LogFactory.getLog(getClass());
private final String beanName;
private final Method method;
private final Class<?> targetClass;
private final Method bridgedMethod;
private final ResolvableType declaredEventType;
private final AnnotatedElementKey methodKey;
private ApplicationContext applicationContext;
private EventExpressionEvaluator evaluator;
public ApplicationListenerMethodAdapter(String beanName, Class<?> targetClass, Method method) {
this.beanName = beanName;
this.method = method;
this.targetClass = targetClass;
this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
this.declaredEventType = resolveDeclaredEventType();
this.methodKey = new AnnotatedElementKey(this.method, this.targetClass);
}
/**
* Initialize this instance.
*/
void init(ApplicationContext applicationContext, EventExpressionEvaluator evaluator) {
this.applicationContext = applicationContext;
this.evaluator = evaluator;
}
@Override
public void onApplicationEvent(ApplicationEvent event) {
Object[] args = resolveArguments(event);
if (shouldHandle(event, args)) {
Object result = doInvoke(args);
if (result != null) {
handleResult(result);
}
else {
logger.trace("No result object given - no result to handle");
}
}
}
/**
* Resolve the method arguments to use for the specified {@link ApplicationEvent}.
* <p>These arguments will be used to invoke the method handled by this instance. Can
* return {@code null} to indicate that no suitable arguments could be resolved and
* therefore the method should not be invoked at all for the specified event.
*/
protected Object[] resolveArguments(ApplicationEvent event) {
if (!ApplicationEvent.class.isAssignableFrom(this.declaredEventType.getRawClass())
&& event instanceof PayloadApplicationEvent) {
Object payload = ((PayloadApplicationEvent) event).getPayload();
if (this.declaredEventType.isAssignableFrom(ResolvableType.forClass(payload.getClass()))) {
return new Object[] {payload};
}
}
else {
return new Object[] {event};
}
return null;
}
protected void handleResult(Object result) {
Assert.notNull(this.applicationContext, "ApplicationContext must no be null.");
this.applicationContext.publishEvent(result);
}
private boolean shouldHandle(ApplicationEvent event, Object[] args) {
if (args == null) {
return false;
}
EventListener eventListener = AnnotationUtils.findAnnotation(this.method, EventListener.class);
String condition = (eventListener != null ? eventListener.condition() : null);
if (StringUtils.hasText(condition)) {
Assert.notNull(this.evaluator, "Evaluator must no be null.");
EvaluationContext evaluationContext = this.evaluator.createEvaluationContext(event,
this.targetClass, this.method, args);
return this.evaluator.condition(condition, this.methodKey, evaluationContext);
}
return true;
}
@Override
public boolean supportsEventType(ResolvableType eventType) {
if (this.declaredEventType.isAssignableFrom(eventType)) {
return true;
}
else if (PayloadApplicationEvent.class.isAssignableFrom(eventType.getRawClass())) {
ResolvableType payloadType = eventType.as(PayloadApplicationEvent.class).getGeneric();
return eventType.hasUnresolvableGenerics() || this.declaredEventType.isAssignableFrom(payloadType);
}
return false;
}
@Override
public boolean supportsSourceType(Class<?> sourceType) {
return true;
}
@Override
public int getOrder() {
Order order = AnnotationUtils.findAnnotation(this.method, Order.class);
return (order != null ? order.value() : 0);
}
/**
* Invoke the event listener method with the given argument values.
*/
protected Object doInvoke(Object... args) {
Object bean = getTargetBean();
ReflectionUtils.makeAccessible(this.bridgedMethod);
try {
return this.bridgedMethod.invoke(bean, args);
}
catch (IllegalArgumentException ex) {
assertTargetBean(this.bridgedMethod, bean, args);
throw new IllegalStateException(getInvocationErrorMessage(bean, ex.getMessage(), args), ex);
}
catch (IllegalAccessException ex) {
throw new IllegalStateException(getInvocationErrorMessage(bean, ex.getMessage(), args), ex);
}
catch (InvocationTargetException ex) {
// Throw underlying exception
Throwable targetException = ex.getTargetException();
if (targetException instanceof RuntimeException) {
throw (RuntimeException) targetException;
}
else {
String msg = getInvocationErrorMessage(bean, "Failed to invoke event listener method", args);
throw new UndeclaredThrowableException(targetException, msg);
}
}
}
/**
* Return the target bean instance to use.
*/
protected Object getTargetBean() {
Assert.notNull(this.applicationContext, "ApplicationContext must no be null.");
return this.applicationContext.getBean(this.beanName);
}
/**
* Add additional details such as the bean type and method signature to
* the given error message.
* @param message error message to append the HandlerMethod details to
*/
protected String getDetailedErrorMessage(Object bean, String message) {
StringBuilder sb = new StringBuilder(message).append("\n");
sb.append("HandlerMethod details: \n");
sb.append("Bean [").append(bean.getClass().getName()).append("]\n");
sb.append("Method [").append(this.bridgedMethod.toGenericString()).append("]\n");
return sb.toString();
}
/**
* Assert that the target bean class is an instance of the class where the given
* method is declared. In some cases the actual bean instance at event-
* processing time may be a JDK dynamic proxy (lazy initialization, prototype
* beans, and others). Event listener beans that require proxying should prefer
* class-based proxy mechanisms.
*/
private void assertTargetBean(Method method, Object targetBean, Object[] args) {
Class<?> methodDeclaringClass = method.getDeclaringClass();
Class<?> targetBeanClass = targetBean.getClass();
if (!methodDeclaringClass.isAssignableFrom(targetBeanClass)) {
String msg = "The event listener method class '" + methodDeclaringClass.getName() +
"' is not an instance of the actual bean instance '" +
targetBeanClass.getName() + "'. If the bean requires proxying " +
"(e.g. due to @Transactional), please use class-based proxying.";
throw new IllegalStateException(getInvocationErrorMessage(targetBean, msg, args));
}
}
private String getInvocationErrorMessage(Object bean, String message, Object[] resolvedArgs) {
StringBuilder sb = new StringBuilder(getDetailedErrorMessage(bean, message));
sb.append("Resolved arguments: \n");
for (int i = 0; i < resolvedArgs.length; i++) {
sb.append("[").append(i).append("] ");
if (resolvedArgs[i] == null) {
sb.append("[null] \n");
}
else {
sb.append("[type=").append(resolvedArgs[i].getClass().getName()).append("] ");
sb.append("[value=").append(resolvedArgs[i]).append("]\n");
}
}
return sb.toString();
}
private ResolvableType resolveDeclaredEventType() {
Parameter[] parameters = this.method.getParameters();
if (parameters.length != 1) {
throw new IllegalStateException("Only one parameter is allowed " +
"for event listener method: " + method);
}
return ResolvableType.forMethodParameter(this.method, 0);
}
@Override
public String toString() {
return this.method.toGenericString();
}
}
/*
* Copyright 2002-2015 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.event;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.aop.support.AopUtils;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.expression.AnnotatedElementKey;
import org.springframework.context.expression.CachedExpressionEvaluator;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
/**
* Utility class handling the SpEL expression parsing. Meant to be used
* as a reusable, thread-safe component.
*
* @author Stephane Nicoll
* @since 4.2
* @see CachedExpressionEvaluator
*/
class EventExpressionEvaluator extends CachedExpressionEvaluator {
// shared param discoverer since it caches data internally
private final ParameterNameDiscoverer paramNameDiscoverer = new DefaultParameterNameDiscoverer();
private final Map<ExpressionKey, Expression> conditionCache = new ConcurrentHashMap<ExpressionKey, Expression>(64);
private final Map<AnnotatedElementKey, Method> targetMethodCache = new ConcurrentHashMap<AnnotatedElementKey, Method>(64);
/**
* Create the suitable {@link EvaluationContext} for the specified event handling
* on the specified method.
*/
public EvaluationContext createEvaluationContext(ApplicationEvent event, Class<?> targetClass,
Method method, Object[] args) {
Method targetMethod = getTargetMethod(targetClass, method);
EventExpressionRootObject root = new EventExpressionRootObject(event, args);
return new MethodBasedEvaluationContext(root, targetMethod, args, this.paramNameDiscoverer);
}
/**
* Specify if the condition defined by the specified expression matches.
*/
public boolean condition(String conditionExpression,
AnnotatedElementKey elementKey, EvaluationContext evalContext) {
return getExpression(this.conditionCache, elementKey, conditionExpression)
.getValue(evalContext, boolean.class);
}
private Method getTargetMethod(Class<?> targetClass, Method method) {
AnnotatedElementKey methodKey = new AnnotatedElementKey(method, targetClass);
Method targetMethod = this.targetMethodCache.get(methodKey);
if (targetMethod == null) {
targetMethod = AopUtils.getMostSpecificMethod(method, targetClass);
if (targetMethod == null) {
targetMethod = method;
}
this.targetMethodCache.put(methodKey, targetMethod);
}
return targetMethod;
}
}
/*
* Copyright 2002-2015 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.event;
import org.springframework.context.ApplicationEvent;
/**
* Root object used during event listener expression evaluation.
*
* @author Stephane Nicoll
* @since 4.2
*/
class EventExpressionRootObject {
private final ApplicationEvent event;
private final Object[] args;
public EventExpressionRootObject(ApplicationEvent event, Object[] args) {
this.event = event;
this.args = args;
}
public ApplicationEvent getEvent() {
return event;
}
public Object[] getArgs() {
return args;
}
}
/*
* Copyright 2002-2015 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.event;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.ApplicationEvent;
/**
* Annotation that marks a method to listen for application events. The
* method should have one and only one parameter that reflects the event
* type to listen to. Events can be {@link ApplicationEvent} instances
* as well as arbitrary objects.
*
* <p>Processing of {@code @EventListener} annotations is performed via
* {@link EventListenerMethodProcessor} that is registered automatically
* when using Java config or via the {@code <context:annotation-driven/>}
* XML element.
*
* <p>Annotated methods may have a non-{@code void} return type. When they
* do, the result of the method invocation is sent as a new event. It is
* also possible to defined the order in which listeners for a certain
* event are invoked. To do so, add a regular {code @Order} annotation
* alongside this annotation.
*
* <p>While it is possible to define any arbitrary exception types, checked
* exceptions will be wrapped in a {@link java.lang.reflect.UndeclaredThrowableException}
* as the caller only handles runtime exceptions.
*
* @author Stephane Nicoll
* @since 4.2
* @see EventListenerMethodProcessor
*/
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EventListener {
/**
* Spring Expression Language (SpEL) attribute used for conditioning the event handling.
* <p>Default is "", meaning the event is always handled.
*/
String condition() default "";
}
\ No newline at end of file
/*
* Copyright 2002-2015 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.event;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.SpringProxy;
import org.springframework.aop.scope.ScopedProxyUtils;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
/**
* Register {@link EventListener} annotated method as individual {@link ApplicationListener}
* instances.
*
* @author Stephane Nicoll
* @since 4.2
*/
public class EventListenerMethodProcessor implements SmartInitializingSingleton, ApplicationContextAware {
protected final Log logger = LogFactory.getLog(getClass());
private ConfigurableApplicationContext applicationContext;
private final EventExpressionEvaluator evaluator = new EventExpressionEvaluator();
private final Set<Class<?>> nonAnnotatedClasses =
Collections.newSetFromMap(new ConcurrentHashMap<Class<?>, Boolean>(64));
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Assert.isTrue(applicationContext instanceof ConfigurableApplicationContext,
"ApplicationContext does not implement ConfigurableApplicationContext");
this.applicationContext = (ConfigurableApplicationContext) applicationContext;
}
@Override
public void afterSingletonsInstantiated() {
String[] allBeanNames = this.applicationContext.getBeanNamesForType(Object.class);
for (String beanName : allBeanNames) {
if (!ScopedProxyUtils.isScopedTarget(beanName)) {
Class<?> type = this.applicationContext.getType(beanName);
try {
processBean(beanName, type);
}
catch (RuntimeException e) {
throw new BeanInitializationException("Failed to process @EventListener " +
"annotation on bean with name '" + beanName + "'", e);
}
}
}
}
protected void processBean(String beanName, final Class<?> type) {
Class<?> targetType = getTargetClass(beanName, type);
if (!this.nonAnnotatedClasses.contains(targetType)) {
final Set<Method> annotatedMethods = new LinkedHashSet<Method>(1);
Method[] methods = ReflectionUtils.getUniqueDeclaredMethods(targetType);
for (Method method : methods) {
EventListener eventListener = AnnotationUtils.findAnnotation(method, EventListener.class);
if (eventListener != null) {
if (!type.equals(targetType)) {
method = getProxyMethod(type, method);
}
ApplicationListenerMethodAdapter applicationListener =
new ApplicationListenerMethodAdapter(beanName, type, method);
applicationListener.init(this.applicationContext, this.evaluator);
this.applicationContext.addApplicationListener(applicationListener);
annotatedMethods.add(method);
}
}
if (annotatedMethods.isEmpty()) {
this.nonAnnotatedClasses.add(type);
if (logger.isDebugEnabled()) {
logger.debug("No @EventListener annotations found on bean class: " + type);
}
}
else {
// Non-empty set of methods
if (logger.isDebugEnabled()) {
logger.debug(annotatedMethods.size() + " @EventListener methods processed on bean '" + beanName +
"': " + annotatedMethods);
}
}
}
}
private Class<?> getTargetClass(String beanName, Class<?> type) {
if (SpringProxy.class.isAssignableFrom(type)) {
Object bean = this.applicationContext.getBean(beanName);
return AopUtils.getTargetClass(bean);
}
else {
return type;
}
}
private Method getProxyMethod(Class<?> proxyType, Method method) {
try {
// Found a @EventListener method on the target class for this JDK proxy ->
// is it also present on the proxy itself?
return proxyType.getMethod(method.getName(), method.getParameterTypes());
}
catch (SecurityException ex) {
ReflectionUtils.handleReflectionException(ex);
}
catch (NoSuchMethodException ex) {
throw new IllegalStateException(String.format(
"@EventListener method '%s' found on bean target class '%s', " +
"but not found in any interface(s) for bean JDK proxy. Either " +
"pull the method up to an interface or switch to subclass (CGLIB) " +
"proxies by setting proxy-target-class/proxyTargetClass " +
"attribute to 'true'", method.getName(), method.getDeclaringClass().getSimpleName()));
}
return null;
}
}
/*
* Copyright 2002-2015 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.expression;
import java.lang.reflect.Method;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.ObjectUtils;
/**
* A method-based {@link org.springframework.expression.EvaluationContext} that
* provides explicit support for method-based invocations.
* <p>
* Expose the actual method arguments using the following aliases:
* <ol>
* <li>pX where X is the index of the argument (p0 for the first argument)</li>
* <li>aX where X is the index of the argument (a1 for the second argument)</li>
* <li>the name of the parameter as discovered by a configurable {@link ParameterNameDiscoverer}</li>
* </ol>
*
* @author Stephane Nicoll
* @since 4.2.0
*/
public class MethodBasedEvaluationContext extends StandardEvaluationContext {
private final Method method;
private final Object[] args;
private final ParameterNameDiscoverer paramDiscoverer;
private boolean paramLoaded = false;
public MethodBasedEvaluationContext(Object rootObject, Method method, Object[] args,
ParameterNameDiscoverer paramDiscoverer) {
super(rootObject);
this.method = method;
this.args = args;
this.paramDiscoverer = paramDiscoverer;
}
@Override
public Object lookupVariable(String name) {
Object variable = super.lookupVariable(name);
if (variable != null) {
return variable;
}
if (!this.paramLoaded) {
lazyLoadArguments();
this.paramLoaded = true;
variable = super.lookupVariable(name);
}
return variable;
}
/**
* Load the param information only when needed.
*/
protected void lazyLoadArguments() {
// shortcut if no args need to be loaded
if (ObjectUtils.isEmpty(this.args)) {
return;
}
// save arguments as indexed variables
for (int i = 0; i < this.args.length; i++) {
setVariable("a" + i, this.args[i]);
setVariable("p" + i, this.args[i]);
}
String[] parameterNames = this.paramDiscoverer.getParameterNames(this.method);
// save parameter names (if discovered)
if (parameterNames != null) {
for (int i = 0; i < parameterNames.length; i++) {
setVariable(parameterNames[i], this.args[i]);
}
}
}
}
......@@ -47,6 +47,7 @@ import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.PayloadApplicationEvent;
import org.springframework.context.HierarchicalMessageSource;
import org.springframework.context.LifecycleProcessor;
import org.springframework.context.MessageSource;
......@@ -329,12 +330,27 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
publishEvent(event, null);
}
protected void publishEvent(ApplicationEvent event, ResolvableType eventType) {
@Override
public void publishEvent(Object event) {
publishEvent(event, null);
}
protected void publishEvent(Object event, ResolvableType eventType) {
Assert.notNull(event, "Event must not be null");
if (logger.isTraceEnabled()) {
logger.trace("Publishing event in " + getDisplayName() + ": " + event);
}
getApplicationEventMulticaster().multicastEvent(event, eventType);
final ApplicationEvent applicationEvent;
if (event instanceof ApplicationEvent) {
applicationEvent = (ApplicationEvent) event;
}
else {
applicationEvent = new PayloadApplicationEvent<Object>(this, event);
if (eventType == null) {
eventType = ResolvableType.forClassWithGenerics(PayloadApplicationEvent.class, event.getClass());
}
}
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
if (this.parent != null) {
if (this.parent instanceof AbstractApplicationContext) {
((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
......
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2015 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.
......@@ -55,7 +55,7 @@ public class ClassPathBeanDefinitionScannerTests {
GenericApplicationContext context = new GenericApplicationContext();
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
int beanCount = scanner.scan(BASE_PACKAGE);
assertEquals(10, beanCount);
assertEquals(11, beanCount);
assertTrue(context.containsBean("serviceInvocationCounter"));
assertTrue(context.containsBean("fooServiceImpl"));
assertTrue(context.containsBean("stubFooDao"));
......@@ -66,6 +66,7 @@ public class ClassPathBeanDefinitionScannerTests {
assertTrue(context.containsBean(AnnotationConfigUtils.AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
assertTrue(context.containsBean(AnnotationConfigUtils.REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
assertTrue(context.containsBean(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
assertTrue(context.containsBean(AnnotationConfigUtils.EVENT_LISTENER_PROCESSOR_BEAN_NAME));
context.refresh();
FooServiceImpl service = context.getBean("fooServiceImpl", FooServiceImpl.class);
assertTrue(context.getDefaultListableBeanFactory().containsSingleton("myNamedComponent"));
......@@ -98,7 +99,7 @@ public class ClassPathBeanDefinitionScannerTests {
GenericApplicationContext context = new GenericApplicationContext();
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
int beanCount = scanner.scan(BASE_PACKAGE);
assertEquals(10, beanCount);
assertEquals(11, beanCount);
scanner.scan(BASE_PACKAGE);
assertTrue(context.containsBean("serviceInvocationCounter"));
assertTrue(context.containsBean("fooServiceImpl"));
......@@ -218,11 +219,12 @@ public class ClassPathBeanDefinitionScannerTests {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context, false);
scanner.addIncludeFilter(new AnnotationTypeFilter(CustomComponent.class));
int beanCount = scanner.scan(BASE_PACKAGE);
assertEquals(5, beanCount);
assertEquals(6, beanCount);
assertTrue(context.containsBean("messageBean"));
assertTrue(context.containsBean(AnnotationConfigUtils.AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
assertTrue(context.containsBean(AnnotationConfigUtils.REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
assertTrue(context.containsBean(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
assertTrue(context.containsBean(AnnotationConfigUtils.EVENT_LISTENER_PROCESSOR_BEAN_NAME));
}
@Test
......@@ -231,7 +233,7 @@ public class ClassPathBeanDefinitionScannerTests {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context, false);
scanner.addIncludeFilter(new AnnotationTypeFilter(CustomComponent.class));
int beanCount = scanner.scan(BASE_PACKAGE);
assertEquals(5, beanCount);
assertEquals(6, beanCount);
assertTrue(context.containsBean("messageBean"));
assertFalse(context.containsBean("serviceInvocationCounter"));
assertFalse(context.containsBean("fooServiceImpl"));
......@@ -241,6 +243,7 @@ public class ClassPathBeanDefinitionScannerTests {
assertTrue(context.containsBean(AnnotationConfigUtils.AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
assertTrue(context.containsBean(AnnotationConfigUtils.REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
assertTrue(context.containsBean(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
assertTrue(context.containsBean(AnnotationConfigUtils.EVENT_LISTENER_PROCESSOR_BEAN_NAME));
}
@Test
......@@ -249,7 +252,7 @@ public class ClassPathBeanDefinitionScannerTests {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context, true);
scanner.addIncludeFilter(new AnnotationTypeFilter(CustomComponent.class));
int beanCount = scanner.scan(BASE_PACKAGE);
assertEquals(11, beanCount);
assertEquals(12, beanCount);
assertTrue(context.containsBean("messageBean"));
assertTrue(context.containsBean("serviceInvocationCounter"));
assertTrue(context.containsBean("fooServiceImpl"));
......@@ -259,6 +262,7 @@ public class ClassPathBeanDefinitionScannerTests {
assertTrue(context.containsBean(AnnotationConfigUtils.AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
assertTrue(context.containsBean(AnnotationConfigUtils.REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
assertTrue(context.containsBean(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
assertTrue(context.containsBean(AnnotationConfigUtils.EVENT_LISTENER_PROCESSOR_BEAN_NAME));
}
@Test
......@@ -267,7 +271,7 @@ public class ClassPathBeanDefinitionScannerTests {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context, true);
scanner.addExcludeFilter(new AnnotationTypeFilter(Aspect.class));
int beanCount = scanner.scan(BASE_PACKAGE);
assertEquals(9, beanCount);
assertEquals(10, beanCount);
assertFalse(context.containsBean("serviceInvocationCounter"));
assertTrue(context.containsBean("fooServiceImpl"));
assertTrue(context.containsBean("stubFooDao"));
......@@ -276,6 +280,7 @@ public class ClassPathBeanDefinitionScannerTests {
assertTrue(context.containsBean(AnnotationConfigUtils.AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
assertTrue(context.containsBean(AnnotationConfigUtils.REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
assertTrue(context.containsBean(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
assertTrue(context.containsBean(AnnotationConfigUtils.EVENT_LISTENER_PROCESSOR_BEAN_NAME));
}
@Test
......@@ -284,7 +289,7 @@ public class ClassPathBeanDefinitionScannerTests {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context, true);
scanner.addExcludeFilter(new AssignableTypeFilter(FooService.class));
int beanCount = scanner.scan(BASE_PACKAGE);
assertEquals(9, beanCount);
assertEquals(10, beanCount);
assertFalse(context.containsBean("fooServiceImpl"));
assertTrue(context.containsBean("serviceInvocationCounter"));
assertTrue(context.containsBean("stubFooDao"));
......@@ -293,6 +298,7 @@ public class ClassPathBeanDefinitionScannerTests {
assertTrue(context.containsBean(AnnotationConfigUtils.AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
assertTrue(context.containsBean(AnnotationConfigUtils.REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
assertTrue(context.containsBean(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
assertTrue(context.containsBean(AnnotationConfigUtils.EVENT_LISTENER_PROCESSOR_BEAN_NAME));
}
@Test
......@@ -320,7 +326,7 @@ public class ClassPathBeanDefinitionScannerTests {
scanner.addExcludeFilter(new AssignableTypeFilter(FooService.class));
scanner.addExcludeFilter(new AnnotationTypeFilter(Aspect.class));
int beanCount = scanner.scan(BASE_PACKAGE);
assertEquals(8, beanCount);
assertEquals(9, beanCount);
assertFalse(context.containsBean("fooServiceImpl"));
assertFalse(context.containsBean("serviceInvocationCounter"));
assertTrue(context.containsBean("stubFooDao"));
......@@ -329,6 +335,7 @@ public class ClassPathBeanDefinitionScannerTests {
assertTrue(context.containsBean(AnnotationConfigUtils.AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
assertTrue(context.containsBean(AnnotationConfigUtils.REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
assertTrue(context.containsBean(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
assertTrue(context.containsBean(AnnotationConfigUtils.EVENT_LISTENER_PROCESSOR_BEAN_NAME));
}
@Test
......@@ -337,7 +344,7 @@ public class ClassPathBeanDefinitionScannerTests {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
scanner.setBeanNameGenerator(new TestBeanNameGenerator());
int beanCount = scanner.scan(BASE_PACKAGE);
assertEquals(10, beanCount);
assertEquals(11, beanCount);
assertFalse(context.containsBean("fooServiceImpl"));
assertTrue(context.containsBean("fooService"));
assertTrue(context.containsBean("serviceInvocationCounter"));
......@@ -347,6 +354,7 @@ public class ClassPathBeanDefinitionScannerTests {
assertTrue(context.containsBean(AnnotationConfigUtils.AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
assertTrue(context.containsBean(AnnotationConfigUtils.REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
assertTrue(context.containsBean(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
assertTrue(context.containsBean(AnnotationConfigUtils.EVENT_LISTENER_PROCESSOR_BEAN_NAME));
}
@Test
......@@ -356,7 +364,7 @@ public class ClassPathBeanDefinitionScannerTests {
GenericApplicationContext multiPackageContext = new GenericApplicationContext();
ClassPathBeanDefinitionScanner multiPackageScanner = new ClassPathBeanDefinitionScanner(multiPackageContext);
int singlePackageBeanCount = singlePackageScanner.scan(BASE_PACKAGE);
assertEquals(10, singlePackageBeanCount);
assertEquals(11, singlePackageBeanCount);
multiPackageScanner.scan(BASE_PACKAGE, "org.springframework.dao.annotation");
// assertTrue(multiPackageBeanCount > singlePackageBeanCount);
}
......@@ -367,7 +375,7 @@ public class ClassPathBeanDefinitionScannerTests {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
int initialBeanCount = context.getBeanDefinitionCount();
int scannedBeanCount = scanner.scan(BASE_PACKAGE);
assertEquals(10, scannedBeanCount);
assertEquals(11, scannedBeanCount);
assertEquals(scannedBeanCount, context.getBeanDefinitionCount() - initialBeanCount);
int addedBeanCount = scanner.scan("org.springframework.aop.aspectj.annotation");
assertEquals(initialBeanCount + scannedBeanCount + addedBeanCount, context.getBeanDefinitionCount());
......@@ -380,7 +388,7 @@ public class ClassPathBeanDefinitionScannerTests {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
scanner.setBeanNameGenerator(new TestBeanNameGenerator());
int beanCount = scanner.scan(BASE_PACKAGE);
assertEquals(10, beanCount);
assertEquals(11, beanCount);
context.refresh();
FooServiceImpl fooService = context.getBean("fooService", FooServiceImpl.class);
......
......@@ -30,19 +30,19 @@ public abstract class AbstractApplicationEventListenerTests {
protected ResolvableType getGenericApplicationEventType(String fieldName) {
try {
return ResolvableType.forField(GenericApplicationEvents.class.getField(fieldName));
return ResolvableType.forField(TestEvents.class.getField(fieldName));
}
catch (NoSuchFieldException e) {
throw new IllegalStateException("No such field on Events '" + fieldName + "'");
}
}
protected static class GenericApplicationEvent<T>
protected static class GenericTestEvent<T>
extends ApplicationEvent {
private final T payload;
public GenericApplicationEvent(Object source, T payload) {
public GenericTestEvent(Object source, T payload) {
super(source);
this.payload = payload;
}
......@@ -53,76 +53,72 @@ public abstract class AbstractApplicationEventListenerTests {
}
protected static class StringEvent extends GenericApplicationEvent<String> {
protected static class StringEvent extends GenericTestEvent<String> {
public StringEvent(Object source, String payload) {
super(source, payload);
}
}
protected static class LongEvent extends GenericApplicationEvent<Long> {
protected static class LongEvent extends GenericTestEvent<Long> {
public LongEvent(Object source, Long payload) {
super(source, payload);
}
}
protected <T> GenericApplicationEvent<T> createGenericEvent(T payload) {
return new GenericApplicationEvent<>(this, payload);
protected <T> GenericTestEvent<T> createGenericTestEvent(T payload) {
return new GenericTestEvent<>(this, payload);
}
static class GenericEventListener implements ApplicationListener<GenericApplicationEvent<?>> {
static class GenericEventListener implements ApplicationListener<GenericTestEvent<?>> {
@Override
public void onApplicationEvent(GenericApplicationEvent<?> event) {
public void onApplicationEvent(GenericTestEvent<?> event) {
}
}
static class ObjectEventListener implements ApplicationListener<GenericApplicationEvent<Object>> {
static class ObjectEventListener implements ApplicationListener<GenericTestEvent<Object>> {
@Override
public void onApplicationEvent(GenericApplicationEvent<Object> event) {
public void onApplicationEvent(GenericTestEvent<Object> event) {
}
}
static class UpperBoundEventListener
implements ApplicationListener<GenericApplicationEvent<? extends RuntimeException>> {
implements ApplicationListener<GenericTestEvent<? extends RuntimeException>> {
@Override
public void onApplicationEvent(GenericApplicationEvent<? extends RuntimeException> event) {
public void onApplicationEvent(GenericTestEvent<? extends RuntimeException> event) {
}
}
static class StringEventListener implements ApplicationListener<GenericApplicationEvent<String>> {
static class StringEventListener implements ApplicationListener<GenericTestEvent<String>> {
@Override
public void onApplicationEvent(GenericApplicationEvent<String> event) {
public void onApplicationEvent(GenericTestEvent<String> event) {
}
}
static class RawApplicationListener implements ApplicationListener {
@Override
public void onApplicationEvent(ApplicationEvent event) {
}
}
@SuppressWarnings("unused")
static class GenericApplicationEvents {
static class TestEvents {
public ApplicationEvent applicationEvent;
public GenericApplicationEvent<?> wildcardEvent;
public GenericTestEvent<?> wildcardEvent;
public GenericApplicationEvent<String> stringEvent;
public GenericTestEvent<String> stringEvent;
public GenericApplicationEvent<Long> longEvent;
public GenericTestEvent<Long> longEvent;
public GenericApplicationEvent<IllegalStateException> illegalStateExceptionEvent;
public GenericTestEvent<IllegalStateException> illegalStateExceptionEvent;
public GenericApplicationEvent<IOException> ioExceptionEvent;
public GenericTestEvent<IOException> ioExceptionEvent;
}
......
/*
* Copyright 2002-2015 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.event;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.PayloadApplicationEvent;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.context.event.test.AbstractIdentifiable;
import org.springframework.context.event.test.AnotherTestEvent;
import org.springframework.context.event.test.EventCollector;
import org.springframework.context.event.test.Identifiable;
import org.springframework.context.event.test.TestEvent;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Component;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
/**
* @author Stephane Nicoll
*/
public class AnnotationDrivenEventListenerTests {
@Rule
public final ExpectedException thrown = ExpectedException.none();
private ConfigurableApplicationContext context;
private EventCollector eventCollector;
private CountDownLatch countDownLatch; // 1 call by default
@After
public void closeContext() {
if (this.context != null) {
this.context.close();
}
}
@Test
public void simpleEventJavaConfig() {
load(TestEventListener.class);
TestEvent event = new TestEvent(this, "test");
TestEventListener listener = this.context.getBean(TestEventListener.class);
this.eventCollector.assertNoEventReceived(listener);
this.context.publishEvent(event);
this.eventCollector.assertEvent(listener, event);
this.eventCollector.assertTotalEventsCount(1);
}
@Test
public void simpleEventXmlConfig() {
this.context = new ClassPathXmlApplicationContext(
"org/springframework/context/event/simple-event-configuration.xml");
TestEvent event = new TestEvent(this, "test");
TestEventListener listener = this.context.getBean(TestEventListener.class);
this.eventCollector = getEventCollector(this.context);
this.eventCollector.assertNoEventReceived(listener);
this.context.publishEvent(event);
this.eventCollector.assertEvent(listener, event);
this.eventCollector.assertTotalEventsCount(1);
}
@Test
public void metaAnnotationIsDiscovered() {
load(MetaAnnotationListenerTestBean.class);
MetaAnnotationListenerTestBean bean = context.getBean(MetaAnnotationListenerTestBean.class);
this.eventCollector.assertNoEventReceived(bean);
TestEvent event = new TestEvent();
this.context.publishEvent(event);
this.eventCollector.assertEvent(bean, event);
this.eventCollector.assertTotalEventsCount(1);
}
@Test
public void contextEventsAreReceived() {
load(ContextEventListener.class);
ContextEventListener listener = this.context.getBean(ContextEventListener.class);
List<Object> events = this.eventCollector.getEvents(listener);
assertEquals("Wrong number of initial context events", 1, events.size());
assertEquals(ContextRefreshedEvent.class, events.get(0).getClass());
this.context.stop();
List<Object> eventsAfterStop = this.eventCollector.getEvents(listener);
assertEquals("Wrong number of context events on shutdown", 2, eventsAfterStop.size());
assertEquals(ContextStoppedEvent.class, eventsAfterStop.get(1).getClass());
this.eventCollector.assertTotalEventsCount(2);
}
@Test
public void methodSignatureNoEvent() {
AnnotationConfigApplicationContext failingContext =
new AnnotationConfigApplicationContext();
failingContext.register(BasicConfiguration.class,
InvalidMethodSignatureEventListener.class);
thrown.expect(BeanInitializationException.class);
thrown.expectMessage(InvalidMethodSignatureEventListener.class.getName());
thrown.expectMessage("cannotBeCalled");
failingContext.refresh();
}
@Test
public void simpleReply() {
load(TestEventListener.class, ReplyEventListener.class);
AnotherTestEvent event = new AnotherTestEvent(this, "dummy");
ReplyEventListener replyEventListener = this.context.getBean(ReplyEventListener.class);
TestEventListener listener = this.context.getBean(TestEventListener.class);
this.eventCollector.assertNoEventReceived(listener);
this.eventCollector.assertNoEventReceived(replyEventListener);
this.context.publishEvent(event);
this.eventCollector.assertEvent(replyEventListener, event);
this.eventCollector.assertEvent(listener, new TestEvent(replyEventListener, event.getId(), event.msg)); // reply
this.eventCollector.assertTotalEventsCount(2);
}
@Test
public void nullReplyIgnored() {
load(TestEventListener.class, ReplyEventListener.class);
AnotherTestEvent event = new AnotherTestEvent(this, null); // No response
ReplyEventListener replyEventListener = this.context.getBean(ReplyEventListener.class);
TestEventListener listener = this.context.getBean(TestEventListener.class);
this.eventCollector.assertNoEventReceived(listener);
this.eventCollector.assertNoEventReceived(replyEventListener);
this.context.publishEvent(event);
this.eventCollector.assertEvent(replyEventListener, event);
this.eventCollector.assertNoEventReceived(listener);
this.eventCollector.assertTotalEventsCount(1);
}
@Test
public void eventListenerWorksWithInterfaceProxy() throws Exception {
load(ProxyTestBean.class);
SimpleService proxy = this.context.getBean(SimpleService.class);
assertTrue("bean should be a proxy", proxy instanceof Advised);
this.eventCollector.assertNoEventReceived(proxy.getId());
TestEvent event = new TestEvent();
this.context.publishEvent(event);
this.eventCollector.assertEvent(proxy.getId(), event);
this.eventCollector.assertTotalEventsCount(1);
}
@Test
public void methodNotAvailableOnProxyIsDetected() throws Exception {
thrown.expect(BeanInitializationException.class);
thrown.expectMessage("handleIt2");
load(InvalidProxyTestBean.class);
}
@Test
public void eventListenerWorksWithCglibProxy() throws Exception {
load(CglibProxyTestBean.class);
CglibProxyTestBean proxy = this.context.getBean(CglibProxyTestBean.class);
assertTrue("bean should be a cglib proxy", AopUtils.isCglibProxy(proxy));
this.eventCollector.assertNoEventReceived(proxy.getId());
TestEvent event = new TestEvent();
this.context.publishEvent(event);
this.eventCollector.assertEvent(proxy.getId(), event);
this.eventCollector.assertTotalEventsCount(1);
}
@Test
public void asyncProcessingApplied() throws InterruptedException {
loadAsync(AsyncEventListener.class);
String threadName = Thread.currentThread().getName();
AnotherTestEvent event = new AnotherTestEvent(this, threadName);
AsyncEventListener listener = this.context.getBean(AsyncEventListener.class);
this.eventCollector.assertNoEventReceived(listener);
this.context.publishEvent(event);
countDownLatch.await(2, TimeUnit.SECONDS);
this.eventCollector.assertEvent(listener, event);
this.eventCollector.assertTotalEventsCount(1);
}
@Test
public void exceptionPropagated() {
load(ExceptionEventListener.class);
TestEvent event = new TestEvent(this, "fail");
ExceptionEventListener listener = this.context.getBean(ExceptionEventListener.class);
this.eventCollector.assertNoEventReceived(listener);
try {
this.context.publishEvent(event);
fail("An exception should have thrown");
}
catch (IllegalStateException e) {
assertEquals("Wrong exception", "Test exception", e.getMessage());
this.eventCollector.assertEvent(listener, event);
this.eventCollector.assertTotalEventsCount(1);
}
}
@Test
public void exceptionNotPropagatedWithAsync() throws InterruptedException {
loadAsync(ExceptionEventListener.class);
AnotherTestEvent event = new AnotherTestEvent(this, "fail");
ExceptionEventListener listener = this.context.getBean(ExceptionEventListener.class);
this.eventCollector.assertNoEventReceived(listener);
this.context.publishEvent(event);
countDownLatch.await(2, TimeUnit.SECONDS);
this.eventCollector.assertEvent(listener, event);
this.eventCollector.assertTotalEventsCount(1);
}
@Test
public void listenerWithSimplePayload() {
load(TestEventListener.class);
TestEventListener listener = this.context.getBean(TestEventListener.class);
this.eventCollector.assertNoEventReceived(listener);
this.context.publishEvent("test");
this.eventCollector.assertEvent(listener, "test");
this.eventCollector.assertTotalEventsCount(1);
}
@Test
public void listenerWithNonMatchingPayload() {
load(TestEventListener.class);
TestEventListener listener = this.context.getBean(TestEventListener.class);
this.eventCollector.assertNoEventReceived(listener);
this.context.publishEvent(123L);
this.eventCollector.assertNoEventReceived(listener);
this.eventCollector.assertTotalEventsCount(0);
}
@Test
public void replyWithPayload() {
load(TestEventListener.class, ReplyEventListener.class);
AnotherTestEvent event = new AnotherTestEvent(this, "String");
ReplyEventListener replyEventListener = this.context.getBean(ReplyEventListener.class);
TestEventListener listener = this.context.getBean(TestEventListener.class);
this.eventCollector.assertNoEventReceived(listener);
this.eventCollector.assertNoEventReceived(replyEventListener);
this.context.publishEvent(event);
this.eventCollector.assertEvent(replyEventListener, event);
this.eventCollector.assertEvent(listener, "String"); // reply
this.eventCollector.assertTotalEventsCount(2);
}
@Test
public void listenerWithGenericApplicationEvent() {
load(GenericEventListener.class);
GenericEventListener listener = this.context.getBean(GenericEventListener.class);
this.eventCollector.assertNoEventReceived(listener);
this.context.publishEvent("TEST");
this.eventCollector.assertEvent(listener, "TEST");
this.eventCollector.assertTotalEventsCount(1);
}
@Test
public void conditionMatch() {
long timestamp = System.currentTimeMillis();
load(ConditionalEventListener.class);
TestEvent event = new TestEvent(this, "OK");
TestEventListener listener = this.context.getBean(ConditionalEventListener.class);
this.eventCollector.assertNoEventReceived(listener);
this.context.publishEvent(event);
this.eventCollector.assertEvent(listener, event);
this.eventCollector.assertTotalEventsCount(1);
this.context.publishEvent("OK");
this.eventCollector.assertEvent(listener, event, "OK");
this.eventCollector.assertTotalEventsCount(2);
this.context.publishEvent(timestamp);
this.eventCollector.assertEvent(listener, event, "OK", timestamp);
this.eventCollector.assertTotalEventsCount(3);
}
@Test
public void conditionDoesNotMatch() {
long maxLong = Long.MAX_VALUE;
load(ConditionalEventListener.class);
TestEvent event = new TestEvent(this, "KO");
TestEventListener listener = this.context.getBean(ConditionalEventListener.class);
this.eventCollector.assertNoEventReceived(listener);
this.context.publishEvent(event);
this.eventCollector.assertNoEventReceived(listener);
this.eventCollector.assertTotalEventsCount(0);
this.context.publishEvent("KO");
this.eventCollector.assertNoEventReceived(listener);
this.eventCollector.assertTotalEventsCount(0);
this.context.publishEvent(maxLong);
this.eventCollector.assertNoEventReceived(listener);
this.eventCollector.assertTotalEventsCount(0);
}
@Test
public void orderedListeners() {
load(OrderedTestListener.class);
OrderedTestListener listener = this.context.getBean(OrderedTestListener.class);
assertTrue(listener.order.isEmpty());
this.context.publishEvent("whatever");
assertThat(listener.order, contains("first", "second", "third"));
}
private void load(Class<?>... classes) {
List<Class<?>> allClasses = new ArrayList<>();
allClasses.add(BasicConfiguration.class);
allClasses.addAll(Arrays.asList(classes));
doLoad(allClasses.toArray(new Class<?>[allClasses.size()]));
}
private void loadAsync(Class<?>... classes) {
List<Class<?>> allClasses = new ArrayList<>();
allClasses.add(AsyncConfiguration.class);
allClasses.addAll(Arrays.asList(classes));
doLoad(allClasses.toArray(new Class<?>[allClasses.size()]));
}
private void doLoad(Class<?>... classes) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(classes);
this.eventCollector = ctx.getBean(EventCollector.class);
this.countDownLatch = ctx.getBean(CountDownLatch.class);
this.context = ctx;
}
private EventCollector getEventCollector(ConfigurableApplicationContext context) {
return context.getBean(EventCollector.class);
}
@Configuration
static class BasicConfiguration {
@Bean
public EventCollector eventCollector() {
return new EventCollector();
}
@Bean
public CountDownLatch testCountDownLatch() {
return new CountDownLatch(1);
}
}
static abstract class AbstractTestEventListener extends AbstractIdentifiable {
@Autowired
private EventCollector eventCollector;
protected void collectEvent(Object content) {
this.eventCollector.addEvent(this, content);
}
}
@Component
static class TestEventListener extends AbstractTestEventListener {
@EventListener
public void handle(TestEvent event) {
collectEvent(event);
}
@EventListener
public void handleString(String content) {
collectEvent(content);
}
}
@EventListener
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface FooListener {
}
@Component
static class MetaAnnotationListenerTestBean extends AbstractTestEventListener {
@FooListener
public void handleIt(TestEvent event) {
collectEvent(event);
}
}
@Component
static class ContextEventListener extends AbstractTestEventListener {
@EventListener
public void handleContextEvent(ApplicationContextEvent event) {
collectEvent(event);
}
}
@Component
static class InvalidMethodSignatureEventListener {
@EventListener
public void cannotBeCalled(String s, Integer what) {
}
}
@Component
static class ReplyEventListener extends AbstractTestEventListener {
@EventListener
public Object handle(AnotherTestEvent event) {
collectEvent(event);
if (event.msg == null) {
return null;
}
else if (event.msg.equals("String")) {
return event.msg;
}
else {
return new TestEvent(this, event.getId(), event.msg);
}
}
}
@Component
static class ExceptionEventListener extends AbstractTestEventListener {
@Autowired
private CountDownLatch countDownLatch;
@EventListener
public void handle(TestEvent event) {
collectEvent(event);
if ("fail".equals(event.msg)) {
throw new IllegalStateException("Test exception");
}
}
@EventListener
@Async
public void handleAsync(AnotherTestEvent event) {
collectEvent(event);
if ("fail".equals(event.msg)) {
countDownLatch.countDown();
throw new IllegalStateException("Test exception");
}
}
}
@Configuration
@Import(BasicConfiguration.class)
@EnableAsync(proxyTargetClass = true)
static class AsyncConfiguration {
}
@Component
static class AsyncEventListener extends AbstractTestEventListener {
@Autowired
private CountDownLatch countDownLatch;
@EventListener
@Async
public void handleAsync(AnotherTestEvent event) {
assertTrue(!Thread.currentThread().getName().equals(event.msg));
collectEvent(event);
countDownLatch.countDown();
}
}
interface SimpleService extends Identifiable {
@EventListener
void handleIt(TestEvent event);
}
@Component
@Scope(proxyMode = ScopedProxyMode.INTERFACES)
static class ProxyTestBean extends AbstractIdentifiable implements SimpleService {
@Autowired
private EventCollector eventCollector;
@Override
public void handleIt(TestEvent event) {
eventCollector.addEvent(this, event);
}
}
@Component
@Scope(proxyMode = ScopedProxyMode.INTERFACES)
static class InvalidProxyTestBean extends ProxyTestBean {
@EventListener // does not exist on any interface so it should fail
public void handleIt2(TestEvent event) {
}
}
@Component
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
static class CglibProxyTestBean extends AbstractTestEventListener {
@EventListener
public void handleIt(TestEvent event) {
collectEvent(event);
}
}
@Component
static class GenericEventListener extends AbstractTestEventListener {
@EventListener
public void handleString(PayloadApplicationEvent<String> event) {
collectEvent(event.getPayload());
}
}
@Component
static class ConditionalEventListener extends TestEventListener {
@EventListener(condition = "'OK'.equals(#root.event.msg)")
@Override
public void handle(TestEvent event) {
super.handle(event);
}
@Override
@EventListener(condition = "'OK'.equals(#content)")
public void handleString(String content) {
super.handleString(content);
}
@EventListener(condition = "#root.event.timestamp > #p0")
public void handleTimestamp(Long timestamp) {
collectEvent(timestamp);
}
}
@Component
static class OrderedTestListener extends TestEventListener {
public final List<String> order = new ArrayList<>();
@EventListener
@Order(50)
public void handleThird(String payload) {
order.add("third");
}
@EventListener
@Order(-50)
public void handleFirst(String payload) {
order.add("first");
}
@EventListener
public void handleSecond(String payload) {
order.add("second");
}
}
}
......@@ -58,19 +58,19 @@ public class ApplicationContextEventTests extends AbstractApplicationEventListen
@Test
public void multicastGenericEvent() {
multicastEvent(true, StringEventListener.class, createGenericEvent("test"),
multicastEvent(true, StringEventListener.class, createGenericTestEvent("test"),
getGenericApplicationEventType("stringEvent"));
}
@Test
public void multicastGenericEventWrongType() {
multicastEvent(false, StringEventListener.class, createGenericEvent(123L),
multicastEvent(false, StringEventListener.class, createGenericTestEvent(123L),
getGenericApplicationEventType("longEvent"));
}
@Test // Unfortunate - this should work as well
public void multicastGenericEventWildcardSubType() {
multicastEvent(false, StringEventListener.class, createGenericEvent("test"),
multicastEvent(false, StringEventListener.class, createGenericTestEvent("test"),
getGenericApplicationEventType("wildcardEvent"));
}
......
/*
* Copyright 2002-2015 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.event;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.UndeclaredThrowableException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.PayloadApplicationEvent;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.Order;
import org.springframework.util.ReflectionUtils;
import static org.hamcrest.Matchers.*;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
/**
* @author Stephane Nicoll
*/
public class ApplicationListenerMethodAdapterTests extends AbstractApplicationEventListenerTests {
@Rule
public final ExpectedException thrown = ExpectedException.none();
private final SampleEvents sampleEvents = spy(new SampleEvents());
private final ApplicationContext context = mock(ApplicationContext.class);
@Test
public void rawListener() {
Method method = ReflectionUtils.findMethod(SampleEvents.class,
"handleRaw", ApplicationEvent.class);
supportsEventType(true, method, getGenericApplicationEventType("applicationEvent"));
}
@Test
public void rawListenerWithGenericEvent() {
Method method = ReflectionUtils.findMethod(SampleEvents.class,
"handleRaw", ApplicationEvent.class);
supportsEventType(true, method, getGenericApplicationEventType("stringEvent"));
}
@Test
public void genericListener() {
Method method = ReflectionUtils.findMethod(SampleEvents.class,
"handleGenericString", GenericTestEvent.class);
supportsEventType(true, method, getGenericApplicationEventType("stringEvent"));
}
@Test
public void genericListenerWrongParameterizedType() {
Method method = ReflectionUtils.findMethod(SampleEvents.class,
"handleGenericString", GenericTestEvent.class);
supportsEventType(false, method, getGenericApplicationEventType("longEvent"));
}
@Test
public void listenerWithPayloadAndGenericInformation() {
Method method = ReflectionUtils.findMethod(SampleEvents.class,
"handleString", String.class);
supportsEventType(true, method, createGenericEventType(String.class));
}
@Test
public void listenerWithInvalidPayloadAndGenericInformation() {
Method method = ReflectionUtils.findMethod(SampleEvents.class,
"handleString", String.class);
supportsEventType(false, method, createGenericEventType(Integer.class));
}
@Test
public void listenerWithPayloadTypeErasure() { // Always accept such event when the type is unknown
Method method = ReflectionUtils.findMethod(SampleEvents.class,
"handleString", String.class);
supportsEventType(true, method, ResolvableType.forClass(PayloadApplicationEvent.class));
}
@Test
public void listenerWithSubTypeSeveralGenerics() {
Method method = ReflectionUtils.findMethod(SampleEvents.class,
"handleString", String.class);
supportsEventType(true, method, ResolvableType.forClass(PayloadTestEvent.class));
}
@Test
public void listenerWithSubTypeSeveralGenericsResolved() {
Method method = ReflectionUtils.findMethod(SampleEvents.class,
"handleString", String.class);
supportsEventType(true, method, ResolvableType.forClass(PayloadStringTestEvent.class));
}
@Test
public void listenerWithTooManyParameters() {
Method method = ReflectionUtils.findMethod(SampleEvents.class,
"tooManyParameters", String.class, String.class);
thrown.expect(IllegalStateException.class);
createTestInstance(method);
}
@Test
public void listenerWithNoParameter() {
Method method = ReflectionUtils.findMethod(SampleEvents.class,
"noParameter");
thrown.expect(IllegalStateException.class);
createTestInstance(method);
}
@Test
public void defaultOrder() {
Method method = ReflectionUtils.findMethod(SampleEvents.class,
"handleGenericString", GenericTestEvent.class);
ApplicationListenerMethodAdapter adapter = createTestInstance(method);
assertEquals(0, adapter.getOrder());
}
@Test
public void specifiedOrder() {
Method method = ReflectionUtils.findMethod(SampleEvents.class,
"handleRaw", ApplicationEvent.class);
ApplicationListenerMethodAdapter adapter = createTestInstance(method);
assertEquals(42, adapter.getOrder());
}
@Test
public void invokeListener() {
Method method = ReflectionUtils.findMethod(SampleEvents.class,
"handleGenericString", GenericTestEvent.class);
GenericTestEvent<String> event = createGenericTestEvent("test");
invokeListener(method, event);
verify(this.sampleEvents, times(1)).handleGenericString(event);
}
@Test
public void invokeListenerRuntimeException() {
Method method = ReflectionUtils.findMethod(SampleEvents.class,
"generateRuntimeException", GenericTestEvent.class);
GenericTestEvent<String> event = createGenericTestEvent("fail");
thrown.expect(IllegalStateException.class);
thrown.expectMessage("Test exception");
thrown.expectCause(is(isNull(Throwable.class)));
invokeListener(method, event);
}
@Test
public void invokeListenerCheckedException() {
Method method = ReflectionUtils.findMethod(SampleEvents.class,
"generateCheckedException", GenericTestEvent.class);
GenericTestEvent<String> event = createGenericTestEvent("fail");
thrown.expect(UndeclaredThrowableException.class);
thrown.expectCause(is(instanceOf(IOException.class)));
invokeListener(method, event);
}
@Test
public void invokeListenerInvalidProxy() {
Object target = new InvalidProxyTestBean();
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(target);
proxyFactory.addInterface(SimpleService.class);
Object bean = proxyFactory.getProxy(getClass().getClassLoader());
Method method = ReflectionUtils.findMethod(InvalidProxyTestBean.class, "handleIt2", ApplicationEvent.class);
StaticApplicationListenerMethodAdapter listener =
new StaticApplicationListenerMethodAdapter(method, bean);
thrown.expect(IllegalStateException.class);
thrown.expectMessage("handleIt2");
listener.onApplicationEvent(createGenericTestEvent("test"));
}
@Test
public void invokeListenerWithPayload() {
Method method = ReflectionUtils.findMethod(SampleEvents.class,
"handleString", String.class);
PayloadApplicationEvent<String> event = new PayloadApplicationEvent<>(this, "test");
invokeListener(method, event);
verify(this.sampleEvents, times(1)).handleString("test");
}
@Test
public void invokeListenerWithPayloadWrongType() {
Method method = ReflectionUtils.findMethod(SampleEvents.class,
"handleString", String.class);
PayloadApplicationEvent<Long> event = new PayloadApplicationEvent<>(this, 123L);
invokeListener(method, event);
verify(this.sampleEvents, never()).handleString(anyString());
}
@Test
public void beanInstanceRetrievedAtEveryInvocation() {
Method method = ReflectionUtils.findMethod(SampleEvents.class,
"handleGenericString", GenericTestEvent.class);
when(this.context.getBean("testBean")).thenReturn(this.sampleEvents);
ApplicationListenerMethodAdapter listener = new ApplicationListenerMethodAdapter(
"testBean", GenericTestEvent.class, method);
listener.init(this.context, new EventExpressionEvaluator());
GenericTestEvent<String> event = createGenericTestEvent("test");
listener.onApplicationEvent(event);
verify(this.sampleEvents, times(1)).handleGenericString(event);
verify(this.context, times(1)).getBean("testBean");
listener.onApplicationEvent(event);
verify(this.sampleEvents, times(2)).handleGenericString(event);
verify(this.context, times(2)).getBean("testBean");
}
private void supportsEventType(boolean match, Method method, ResolvableType eventType) {
ApplicationListenerMethodAdapter adapter = createTestInstance(method);
assertEquals("Wrong match for event '" + eventType + "' on " + method,
match, adapter.supportsEventType(eventType));
}
private void invokeListener(Method method, ApplicationEvent event) {
ApplicationListenerMethodAdapter adapter = createTestInstance(method);
adapter.onApplicationEvent(event);
}
private ApplicationListenerMethodAdapter createTestInstance(Method method) {
return new StaticApplicationListenerMethodAdapter(method, this.sampleEvents);
}
private ResolvableType createGenericEventType(Class<?> payloadType) {
return ResolvableType.forClassWithGenerics(PayloadApplicationEvent.class, payloadType);
}
private static class StaticApplicationListenerMethodAdapter
extends ApplicationListenerMethodAdapter {
private final Object targetBean;
public StaticApplicationListenerMethodAdapter(Method method, Object targetBean) {
super("unused", targetBean.getClass(), method);
this.targetBean = targetBean;
}
@Override
public Object getTargetBean() {
return targetBean;
}
}
private static class SampleEvents {
@EventListener
@Order(42)
public void handleRaw(ApplicationEvent event) {
}
@EventListener
public void handleGenericString(GenericTestEvent<String> event) {
}
@EventListener
public void handleString(String payload) {
}
@EventListener
public void tooManyParameters(String event, String whatIsThis) {
}
@EventListener
public void noParameter() {
}
@EventListener
public void generateRuntimeException(GenericTestEvent<String> event) {
if ("fail".equals(event.getPayload())) {
throw new IllegalStateException("Test exception");
}
}
@EventListener
public void generateCheckedException(GenericTestEvent<String> event) throws IOException {
if ("fail".equals(event.getPayload())) {
throw new IOException("Test exception");
}
}
}
interface SimpleService {
void handleIt(ApplicationEvent event);
}
static class InvalidProxyTestBean implements SimpleService {
@Override
public void handleIt(ApplicationEvent event) {
}
@EventListener
public void handleIt2(ApplicationEvent event) {
}
}
@SuppressWarnings({"unused", "serial"})
static class PayloadTestEvent<V, T> extends PayloadApplicationEvent<T> {
private final V something;
public PayloadTestEvent(Object source, T payload, V something) {
super(source, payload);
this.something = something;
}
}
@SuppressWarnings({"unused", "serial"})
static class PayloadStringTestEvent extends PayloadTestEvent<Long, String> {
public PayloadStringTestEvent(Object source, String payload, Long something) {
super(source, payload, something);
}
}
}
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2015 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.
......@@ -26,6 +26,7 @@ import org.springframework.beans.factory.FactoryBean;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.TestListener;
import org.springframework.context.event.test.TestEvent;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.tests.sample.beans.ITestBean;
import org.springframework.tests.sample.beans.TestBean;
......@@ -116,15 +117,6 @@ public class EventPublicationInterceptorTests {
}
@SuppressWarnings("serial")
public static class TestEvent extends ApplicationEvent {
public TestEvent(Object source) {
super(source);
}
}
@SuppressWarnings("serial")
public static final class TestEventWithNoValidOneArgObjectCtor extends ApplicationEvent {
......
......@@ -54,7 +54,7 @@ public class GenericApplicationListenerAdapterTests extends AbstractApplicationE
@Test // Demonstrates we can't inject that event because the generic type is lost
public void genericListenerStrictTypeTypeErasure() {
GenericApplicationEvent<String> stringEvent = createGenericEvent("test");
GenericTestEvent<String> stringEvent = createGenericTestEvent("test");
ResolvableType eventType = ResolvableType.forType(stringEvent.getClass());
supportsEventType(false, StringEventListener.class, eventType);
}
......@@ -62,7 +62,7 @@ public class GenericApplicationListenerAdapterTests extends AbstractApplicationE
@Test // But it works if we specify the type properly
public void genericListenerStrictTypeAndResolvableType() {
ResolvableType eventType = ResolvableType
.forClassWithGenerics(GenericApplicationEvent.class, String.class);
.forClassWithGenerics(GenericTestEvent.class, String.class);
supportsEventType(true, StringEventListener.class, eventType);
}
......@@ -87,7 +87,7 @@ public class GenericApplicationListenerAdapterTests extends AbstractApplicationE
@Test
public void genericListenerStrictTypeNotMatchTypeErasure() {
GenericApplicationEvent<Long> longEvent = createGenericEvent(123L);
GenericTestEvent<Long> longEvent = createGenericTestEvent(123L);
ResolvableType eventType = ResolvableType.forType(longEvent.getClass());
supportsEventType(false, StringEventListener.class, eventType);
}
......@@ -118,7 +118,7 @@ public class GenericApplicationListenerAdapterTests extends AbstractApplicationE
@Test // Demonstrates we cant inject that event because the listener has a wildcard
public void genericListenerWildcardTypeTypeErasure() {
GenericApplicationEvent<String> stringEvent = createGenericEvent("test");
GenericTestEvent<String> stringEvent = createGenericTestEvent("test");
ResolvableType eventType = ResolvableType.forType(stringEvent.getClass());
supportsEventType(true, GenericEventListener.class, eventType);
}
......@@ -131,7 +131,7 @@ public class GenericApplicationListenerAdapterTests extends AbstractApplicationE
@Test // Demonstrates we cant inject that event because the listener has a raw type
public void genericListenerRawTypeTypeErasure() {
GenericApplicationEvent<String> stringEvent = createGenericEvent("test");
GenericTestEvent<String> stringEvent = createGenericTestEvent("test");
ResolvableType eventType = ResolvableType.forType(stringEvent.getClass());
supportsEventType(true, RawApplicationListener.class, eventType);
}
......
/*
* Copyright 2002-2015 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.event.test;
import java.util.UUID;
/**
* @author Stephane Nicoll
*/
public abstract class AbstractIdentifiable implements Identifiable {
private final String id;
public AbstractIdentifiable() {
this.id = UUID.randomUUID().toString();
}
@Override
public String getId() {
return id;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AbstractIdentifiable that = (AbstractIdentifiable) o;
return id.equals(that.id);
}
@Override
public int hashCode() {
return id.hashCode();
}
}
/*
* Copyright 2002-2015 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.event.test;
/**
* @author Stephane Nicoll
*/
@SuppressWarnings("serial")
public class AnotherTestEvent extends IdentifiableApplicationEvent {
public final String msg;
public AnotherTestEvent(Object source, String msg) {
super(source);
this.msg = msg;
}
}
/*
* Copyright 2002-2015 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.event.test;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import static org.junit.Assert.*;
/**
* Test utility to collect and assert events.
*
* @author Stephane Nicoll
*/
@Component
public class EventCollector {
private final MultiValueMap<String, Object> content = new LinkedMultiValueMap<>();
/**
* Register an event for the specified listener.
*/
public void addEvent(Identifiable listener, Object event) {
this.content.add(listener.getId(), event);
}
/**
* Return the events that the specified listener has received. The list of events
* is ordered according to their reception order.
*/
public List<Object> getEvents(Identifiable listener) {
return this.content.get(listener.getId());
}
/**
* Assert that the listener identified by the specified id has not received any event.
*/
public void assertNoEventReceived(String listenerId) {
List<Object> events = content.getOrDefault(listenerId, Collections.emptyList());
assertEquals("Expected no events but got " + events, 0, events.size());
}
/**
* Assert that the specified listener has not received any event.
*/
public void assertNoEventReceived(Identifiable listener) {
assertNoEventReceived(listener.getId());
}
/**
* Assert that the listener identified by the specified id has received the
* specified events, in that specific order.
*/
public void assertEvent(String listenerId, Object... events) {
List<Object> actual = content.getOrDefault(listenerId, Collections.emptyList());
assertEquals("wrong number of events", events.length, actual.size());
for (int i = 0; i < events.length; i++) {
assertEquals("Wrong event at index " + i, events[i], actual.get(i));
}
}
/**
* Assert that the specified listener has received the specified events, in
* that specific order.
*/
public void assertEvent(Identifiable listener, Object... events) {
assertEvent(listener.getId(), events);
}
/**
* Assert the number of events received by this instance. Checks that
* unexpected events have not been received. If an event is handled by
* several listeners, each instance will be registered.
*/
public void assertTotalEventsCount(int number) {
int actual = 0;
for (Map.Entry<String, List<Object>> entry : this.content.entrySet()) {
actual += entry.getValue().size();
}
assertEquals("Wrong number of total events (" + this.content.size() + ") " +
"registered listener(s)", number, actual);
}
}
/*
* Copyright 2002-2015 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.event.test;
/**
* A simple marker interface used to identify an event or an event listener
*
* @author Stephane Nicoll
*/
public interface Identifiable {
/**
* Return a unique global id used to identify this instance.
*/
String getId();
}
/*
* Copyright 2002-2015 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.event.test;
import java.util.UUID;
import org.springframework.context.ApplicationEvent;
/**
* A basic test event that can be uniquely identified easily.
*
* @author Stephane Nicoll
*/
@SuppressWarnings("serial")
public abstract class IdentifiableApplicationEvent extends ApplicationEvent implements Identifiable {
private final String id;
protected IdentifiableApplicationEvent(Object source, String id) {
super(source);
this.id = id;
}
protected IdentifiableApplicationEvent(Object source) {
this(source, UUID.randomUUID().toString());
}
protected IdentifiableApplicationEvent() {
this(new Object());
}
@Override
public String getId() {
return id;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
IdentifiableApplicationEvent that = (IdentifiableApplicationEvent) o;
return id.equals(that.id);
}
@Override
public int hashCode() {
return id.hashCode();
}
}
/*
* Copyright 2002-2015 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.event.test;
/**
* @author Stephane Nicoll
*/
@SuppressWarnings("serial")
public class TestEvent extends IdentifiableApplicationEvent {
public final String msg;
public TestEvent(Object source, String id, String msg) {
super(source, id);
this.msg = msg;
}
public TestEvent(Object source, String msg) {
super(source);
this.msg = msg;
}
public TestEvent(Object source) {
this(source, "test");
}
public TestEvent() {
this(new Object());
}
}
/*
* Copyright 2002-2015 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.expression;
import java.lang.reflect.Method;
import org.junit.Test;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.util.ReflectionUtils;
import static org.junit.Assert.*;
/**
* @author Stephane Nicoll
*/
public class MethodBasedEvaluationContextTest {
private final ParameterNameDiscoverer paramDiscover = new DefaultParameterNameDiscoverer();
@Test
public void simpleArguments() {
Method method = ReflectionUtils.findMethod(SampleMethods.class, "hello",
String.class, Boolean.class);
MethodBasedEvaluationContext context = createEvaluationContext(method, new Object[] {"test", true});
assertEquals("test", context.lookupVariable("a0"));
assertEquals("test", context.lookupVariable("p0"));
assertEquals("test", context.lookupVariable("foo"));
assertEquals(true, context.lookupVariable("a1"));
assertEquals(true, context.lookupVariable("p1"));
assertEquals(true, context.lookupVariable("flag"));
assertNull(context.lookupVariable("a2"));
}
@Test
public void nullArgument() {
Method method = ReflectionUtils.findMethod(SampleMethods.class, "hello",
String.class, Boolean.class);
MethodBasedEvaluationContext context = createEvaluationContext(method, new Object[] {null, null});
assertNull(context.lookupVariable("a0"));
assertNull(context.lookupVariable("p0"));
}
private MethodBasedEvaluationContext createEvaluationContext(Method method, Object[] args) {
return new MethodBasedEvaluationContext(this, method, args, this.paramDiscover);
}
@SuppressWarnings("unused")
private static class SampleMethods {
private void hello(String foo, Boolean flag) {
}
}
}
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean id="eventCollector" class="org.springframework.context.event.test.EventCollector"/>
<bean id="testEventListener"
class="org.springframework.context.event.AnnotationDrivenEventListenerTests$TestEventListener"/>
</beans>
\ No newline at end of file
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 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.
......@@ -146,6 +146,11 @@ public class BrokerMessageHandlerTests {
@Override
public void publishEvent(ApplicationEvent event) {
publishEvent((Object) event);
}
@Override
public void publishEvent(Object event) {
if (event instanceof BrokerAvailabilityEvent) {
this.availabilityEvents.add(((BrokerAvailabilityEvent) event).isBrokerAvailable());
}
......
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 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.
......@@ -252,6 +252,11 @@ public class StompBrokerRelayMessageHandlerIntegrationTests {
@Override
public void publishEvent(ApplicationEvent event) {
publishEvent((Object) event);
}
@Override
public void publishEvent(Object event) {
logger.debug("Processing ApplicationEvent " + event);
if (event instanceof BrokerAvailabilityEvent) {
this.eventQueue.add((BrokerAvailabilityEvent) event);
......
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 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.
......@@ -317,6 +317,10 @@ class StubWebApplicationContext implements WebApplicationContext {
public void publishEvent(ApplicationEvent event) {
}
@Override
public void publishEvent(Object event) {
}
@Override
public Resource[] getResources(String locationPattern) throws IOException {
return this.resourcePatternResolver.getResources(locationPattern);
......
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 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.
......@@ -44,6 +44,7 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.PayloadApplicationEvent;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.simp.SimpAttributes;
......@@ -448,6 +449,11 @@ public class StompSubProtocolHandlerTests {
public void publishEvent(ApplicationEvent event) {
events.add(event);
}
@Override
public void publishEvent(Object event) {
publishEvent(new PayloadApplicationEvent<Object>(this, event));
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册