/* * Copyright 2002-2018 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; /** * {@link GenericApplicationListener} adapter that delegates the processing of * an event to an {@link EventListener} annotated method. * *
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 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.
*/
@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.
*/
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 {@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.
* @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.
*/
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();
}
}