/*
 * Copyright 2002-2019 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.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.aop.support.AopUtils;
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.AnnotatedElementUtils;
import org.springframework.core.annotation.Order;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

GenericApplicationListener adapter that delegates the processing of an event to an EventListener annotated method.

Delegates to processEvent(ApplicationEvent) to give sub-classes a chance to deviate from the default. Unwraps the content of a PayloadApplicationEvent if necessary to allow method declaration to define any arbitrary event type. If a condition is defined, it is evaluated prior to invoking the underlying method.

Author:Stephane Nicoll, Juergen Hoeller, Sam Brannen
Since:4.2
/** * {@link GenericApplicationListener} adapter that delegates the processing of * an event to an {@link EventListener} annotated method. * * <p>Delegates to {@link #processEvent(ApplicationEvent)} to give sub-classes * a chance to deviate from the default. Unwraps the content of a * {@link PayloadApplicationEvent} if necessary to allow method declaration * to define any arbitrary event type. If a condition is defined, it is * evaluated prior to invoking the underlying method. * * @author Stephane Nicoll * @author Juergen Hoeller * @author Sam Brannen * @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 Method targetMethod; private final AnnotatedElementKey methodKey; private final List<ResolvableType> declaredEventTypes; @Nullable private final String condition; private final int order; @Nullable private ApplicationContext applicationContext; @Nullable private EventExpressionEvaluator evaluator; public ApplicationListenerMethodAdapter(String beanName, Class<?> targetClass, Method method) { this.beanName = beanName; this.method = BridgeMethodResolver.findBridgedMethod(method); this.targetMethod = (!Proxy.isProxyClass(targetClass) ? AopUtils.getMostSpecificMethod(method, targetClass) : this.method); this.methodKey = new AnnotatedElementKey(this.targetMethod, targetClass); EventListener ann = AnnotatedElementUtils.findMergedAnnotation(this.targetMethod, EventListener.class); this.declaredEventTypes = resolveDeclaredEventTypes(method, ann); this.condition = (ann != null ? ann.condition() : null); this.order = resolveOrder(this.targetMethod); } private static List<ResolvableType> resolveDeclaredEventTypes(Method method, @Nullable EventListener ann) { int count = method.getParameterCount(); if (count > 1) { throw new IllegalStateException( "Maximum one parameter is allowed for event listener method: " + method); } if (ann != null) { Class<?>[] classes = ann.classes(); if (classes.length > 0) { List<ResolvableType> types = new ArrayList<>(classes.length); for (Class<?> eventType : classes) { types.add(ResolvableType.forClass(eventType)); } return types; } } if (count == 0) { throw new IllegalStateException( "Event parameter is mandatory for event listener method: " + method); } return Collections.singletonList(ResolvableType.forMethodParameter(method, 0)); } private static int resolveOrder(Method method) { Order ann = AnnotatedElementUtils.findMergedAnnotation(method, Order.class); return (ann != null ? ann.value() : 0); }
Initialize this instance.
/** * Initialize this instance. */
void init(ApplicationContext applicationContext, EventExpressionEvaluator evaluator) { this.applicationContext = applicationContext; this.evaluator = evaluator; } @Override public void onApplicationEvent(ApplicationEvent event) { processEvent(event); } @Override public boolean supportsEventType(ResolvableType eventType) { for (ResolvableType declaredEventType : this.declaredEventTypes) { if (declaredEventType.isAssignableFrom(eventType)) { return true; } if (PayloadApplicationEvent.class.isAssignableFrom(eventType.toClass())) { ResolvableType payloadType = eventType.as(PayloadApplicationEvent.class).getGeneric(); if (declaredEventType.isAssignableFrom(payloadType)) { return true; } } } return eventType.hasUnresolvableGenerics(); } @Override public boolean supportsSourceType(@Nullable Class<?> sourceType) { return true; } @Override public int getOrder() { return this.order; }
Process the specified ApplicationEvent, checking if the condition match and handling non-null result, if any.
/** * Process the specified {@link ApplicationEvent}, checking if the condition * match and handling non-null result, if any. */
public void processEvent(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 ApplicationEvent.

These arguments will be used to invoke the method handled by this instance. Can return null to indicate that no suitable arguments could be resolved and therefore the method should not be invoked at all for the specified event.

/** * 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. */
@Nullable protected Object[] resolveArguments(ApplicationEvent event) { ResolvableType declaredEventType = getResolvableType(event); if (declaredEventType == null) { return null; } if (this.method.getParameterCount() == 0) { return new Object[0]; } if (!ApplicationEvent.class.isAssignableFrom(declaredEventType.toClass()) && event instanceof PayloadApplicationEvent) { return new Object[] {((PayloadApplicationEvent) event).getPayload()}; } else { return new Object[] {event}; } } protected void handleResult(Object result) { if (result.getClass().isArray()) { Object[] events = ObjectUtils.toObjectArray(result); for (Object event : events) { publishEvent(event); } } else if (result instanceof Collection<?>) { Collection<?> events = (Collection<?>) result; for (Object event : events) { publishEvent(event); } } else { publishEvent(result); } } private void publishEvent(@Nullable Object event) { if (event != null) { Assert.notNull(this.applicationContext, "ApplicationContext must not be null"); this.applicationContext.publishEvent(event); } } private boolean shouldHandle(ApplicationEvent event, @Nullable Object[] args) { if (args == null) { return false; } String condition = getCondition(); if (StringUtils.hasText(condition)) { Assert.notNull(this.evaluator, "EventExpressionEvaluator must not be null"); return this.evaluator.condition( condition, event, this.targetMethod, this.methodKey, args, this.applicationContext); } return true; }
Invoke the event listener method with the given argument values.
/** * Invoke the event listener method with the given argument values. */
@Nullable protected Object doInvoke(Object... args) { Object bean = getTargetBean(); ReflectionUtils.makeAccessible(this.method); try { return this.method.invoke(bean, args); } catch (IllegalArgumentException ex) { assertTargetBean(this.method, 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.
/** * 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); }
Return the condition to use.

Matches the condition attribute of the EventListener annotation or any matching attribute on a composed annotation that is meta-annotated with @EventListener.

/** * Return the condition to use. * <p>Matches the {@code condition} attribute of the {@link EventListener} * annotation or any matching attribute on a composed annotation that * is meta-annotated with {@code @EventListener}. */
@Nullable protected String getCondition() { return this.condition; }
Add additional details such as the bean type and method signature to the given error message.
Params:
  • message – error message to append the HandlerMethod details to
/** * 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.method.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.
/** * 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 class '" + 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(); } @Nullable private ResolvableType getResolvableType(ApplicationEvent event) { ResolvableType payloadType = null; if (event instanceof PayloadApplicationEvent) { PayloadApplicationEvent<?> payloadEvent = (PayloadApplicationEvent<?>) event; ResolvableType eventType = payloadEvent.getResolvableType(); if (eventType != null) { payloadType = eventType.as(PayloadApplicationEvent.class).getGeneric(); } } for (ResolvableType declaredEventType : this.declaredEventTypes) { Class<?> eventClass = declaredEventType.toClass(); if (!ApplicationEvent.class.isAssignableFrom(eventClass) && payloadType != null && declaredEventType.isAssignableFrom(payloadType)) { return declaredEventType; } if (eventClass.isInstance(event)) { return declaredEventType; } } return null; } @Override public String toString() { return this.method.toGenericString(); } }