001/* 002 * Copyright 2002-2019 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.context.event; 018 019import java.lang.reflect.InvocationTargetException; 020import java.lang.reflect.Method; 021import java.lang.reflect.Proxy; 022import java.lang.reflect.UndeclaredThrowableException; 023import java.util.ArrayList; 024import java.util.Collection; 025import java.util.Collections; 026import java.util.List; 027import java.util.concurrent.CompletionStage; 028 029import org.apache.commons.logging.Log; 030import org.apache.commons.logging.LogFactory; 031import org.reactivestreams.Subscriber; 032import org.reactivestreams.Subscription; 033 034import org.springframework.aop.support.AopUtils; 035import org.springframework.context.ApplicationContext; 036import org.springframework.context.ApplicationEvent; 037import org.springframework.context.PayloadApplicationEvent; 038import org.springframework.context.expression.AnnotatedElementKey; 039import org.springframework.core.BridgeMethodResolver; 040import org.springframework.core.ReactiveAdapter; 041import org.springframework.core.ReactiveAdapterRegistry; 042import org.springframework.core.ResolvableType; 043import org.springframework.core.annotation.AnnotatedElementUtils; 044import org.springframework.core.annotation.Order; 045import org.springframework.lang.Nullable; 046import org.springframework.util.Assert; 047import org.springframework.util.ClassUtils; 048import org.springframework.util.ObjectUtils; 049import org.springframework.util.ReflectionUtils; 050import org.springframework.util.StringUtils; 051import org.springframework.util.concurrent.ListenableFuture; 052 053/** 054 * {@link GenericApplicationListener} adapter that delegates the processing of 055 * an event to an {@link EventListener} annotated method. 056 * 057 * <p>Delegates to {@link #processEvent(ApplicationEvent)} to give subclasses 058 * a chance to deviate from the default. Unwraps the content of a 059 * {@link PayloadApplicationEvent} if necessary to allow a method declaration 060 * to define any arbitrary event type. If a condition is defined, it is 061 * evaluated prior to invoking the underlying method. 062 * 063 * @author Stephane Nicoll 064 * @author Juergen Hoeller 065 * @author Sam Brannen 066 * @since 4.2 067 */ 068public class ApplicationListenerMethodAdapter implements GenericApplicationListener { 069 070 private static final boolean reactiveStreamsPresent = ClassUtils.isPresent( 071 "org.reactivestreams.Publisher", ApplicationListenerMethodAdapter.class.getClassLoader()); 072 073 074 protected final Log logger = LogFactory.getLog(getClass()); 075 076 private final String beanName; 077 078 private final Method method; 079 080 private final Method targetMethod; 081 082 private final AnnotatedElementKey methodKey; 083 084 private final List<ResolvableType> declaredEventTypes; 085 086 @Nullable 087 private final String condition; 088 089 private final int order; 090 091 @Nullable 092 private ApplicationContext applicationContext; 093 094 @Nullable 095 private EventExpressionEvaluator evaluator; 096 097 098 public ApplicationListenerMethodAdapter(String beanName, Class<?> targetClass, Method method) { 099 this.beanName = beanName; 100 this.method = BridgeMethodResolver.findBridgedMethod(method); 101 this.targetMethod = (!Proxy.isProxyClass(targetClass) ? 102 AopUtils.getMostSpecificMethod(method, targetClass) : this.method); 103 this.methodKey = new AnnotatedElementKey(this.targetMethod, targetClass); 104 105 EventListener ann = AnnotatedElementUtils.findMergedAnnotation(this.targetMethod, EventListener.class); 106 this.declaredEventTypes = resolveDeclaredEventTypes(method, ann); 107 this.condition = (ann != null ? ann.condition() : null); 108 this.order = resolveOrder(this.targetMethod); 109 } 110 111 private static List<ResolvableType> resolveDeclaredEventTypes(Method method, @Nullable EventListener ann) { 112 int count = method.getParameterCount(); 113 if (count > 1) { 114 throw new IllegalStateException( 115 "Maximum one parameter is allowed for event listener method: " + method); 116 } 117 118 if (ann != null) { 119 Class<?>[] classes = ann.classes(); 120 if (classes.length > 0) { 121 List<ResolvableType> types = new ArrayList<>(classes.length); 122 for (Class<?> eventType : classes) { 123 types.add(ResolvableType.forClass(eventType)); 124 } 125 return types; 126 } 127 } 128 129 if (count == 0) { 130 throw new IllegalStateException( 131 "Event parameter is mandatory for event listener method: " + method); 132 } 133 return Collections.singletonList(ResolvableType.forMethodParameter(method, 0)); 134 } 135 136 private static int resolveOrder(Method method) { 137 Order ann = AnnotatedElementUtils.findMergedAnnotation(method, Order.class); 138 return (ann != null ? ann.value() : 0); 139 } 140 141 142 /** 143 * Initialize this instance. 144 */ 145 void init(ApplicationContext applicationContext, EventExpressionEvaluator evaluator) { 146 this.applicationContext = applicationContext; 147 this.evaluator = evaluator; 148 } 149 150 151 @Override 152 public void onApplicationEvent(ApplicationEvent event) { 153 processEvent(event); 154 } 155 156 @Override 157 public boolean supportsEventType(ResolvableType eventType) { 158 for (ResolvableType declaredEventType : this.declaredEventTypes) { 159 if (declaredEventType.isAssignableFrom(eventType)) { 160 return true; 161 } 162 if (PayloadApplicationEvent.class.isAssignableFrom(eventType.toClass())) { 163 ResolvableType payloadType = eventType.as(PayloadApplicationEvent.class).getGeneric(); 164 if (declaredEventType.isAssignableFrom(payloadType)) { 165 return true; 166 } 167 } 168 } 169 return eventType.hasUnresolvableGenerics(); 170 } 171 172 @Override 173 public boolean supportsSourceType(@Nullable Class<?> sourceType) { 174 return true; 175 } 176 177 @Override 178 public int getOrder() { 179 return this.order; 180 } 181 182 183 /** 184 * Process the specified {@link ApplicationEvent}, checking if the condition 185 * matches and handling a non-null result, if any. 186 */ 187 public void processEvent(ApplicationEvent event) { 188 Object[] args = resolveArguments(event); 189 if (shouldHandle(event, args)) { 190 Object result = doInvoke(args); 191 if (result != null) { 192 handleResult(result); 193 } 194 else { 195 logger.trace("No result object given - no result to handle"); 196 } 197 } 198 } 199 200 /** 201 * Resolve the method arguments to use for the specified {@link ApplicationEvent}. 202 * <p>These arguments will be used to invoke the method handled by this instance. 203 * Can return {@code null} to indicate that no suitable arguments could be resolved 204 * and therefore the method should not be invoked at all for the specified event. 205 */ 206 @Nullable 207 protected Object[] resolveArguments(ApplicationEvent event) { 208 ResolvableType declaredEventType = getResolvableType(event); 209 if (declaredEventType == null) { 210 return null; 211 } 212 if (this.method.getParameterCount() == 0) { 213 return new Object[0]; 214 } 215 Class<?> declaredEventClass = declaredEventType.toClass(); 216 if (!ApplicationEvent.class.isAssignableFrom(declaredEventClass) && 217 event instanceof PayloadApplicationEvent) { 218 Object payload = ((PayloadApplicationEvent<?>) event).getPayload(); 219 if (declaredEventClass.isInstance(payload)) { 220 return new Object[] {payload}; 221 } 222 } 223 return new Object[] {event}; 224 } 225 226 protected void handleResult(Object result) { 227 if (reactiveStreamsPresent && new ReactiveResultHandler().subscribeToPublisher(result)) { 228 if (logger.isTraceEnabled()) { 229 logger.trace("Adapted to reactive result: " + result); 230 } 231 } 232 else if (result instanceof CompletionStage) { 233 ((CompletionStage<?>) result).whenComplete((event, ex) -> { 234 if (ex != null) { 235 handleAsyncError(ex); 236 } 237 else if (event != null) { 238 publishEvent(event); 239 } 240 }); 241 } 242 else if (result instanceof ListenableFuture) { 243 ((ListenableFuture<?>) result).addCallback(this::publishEvents, this::handleAsyncError); 244 } 245 else { 246 publishEvents(result); 247 } 248 } 249 250 private void publishEvents(Object result) { 251 if (result.getClass().isArray()) { 252 Object[] events = ObjectUtils.toObjectArray(result); 253 for (Object event : events) { 254 publishEvent(event); 255 } 256 } 257 else if (result instanceof Collection<?>) { 258 Collection<?> events = (Collection<?>) result; 259 for (Object event : events) { 260 publishEvent(event); 261 } 262 } 263 else { 264 publishEvent(result); 265 } 266 } 267 268 private void publishEvent(@Nullable Object event) { 269 if (event != null) { 270 Assert.notNull(this.applicationContext, "ApplicationContext must not be null"); 271 this.applicationContext.publishEvent(event); 272 } 273 } 274 275 protected void handleAsyncError(Throwable t) { 276 logger.error("Unexpected error occurred in asynchronous listener", t); 277 } 278 279 private boolean shouldHandle(ApplicationEvent event, @Nullable Object[] args) { 280 if (args == null) { 281 return false; 282 } 283 String condition = getCondition(); 284 if (StringUtils.hasText(condition)) { 285 Assert.notNull(this.evaluator, "EventExpressionEvaluator must not be null"); 286 return this.evaluator.condition( 287 condition, event, this.targetMethod, this.methodKey, args, this.applicationContext); 288 } 289 return true; 290 } 291 292 /** 293 * Invoke the event listener method with the given argument values. 294 */ 295 @Nullable 296 protected Object doInvoke(Object... args) { 297 Object bean = getTargetBean(); 298 // Detect package-protected NullBean instance through equals(null) check 299 if (bean.equals(null)) { 300 return null; 301 } 302 303 ReflectionUtils.makeAccessible(this.method); 304 try { 305 return this.method.invoke(bean, args); 306 } 307 catch (IllegalArgumentException ex) { 308 assertTargetBean(this.method, bean, args); 309 throw new IllegalStateException(getInvocationErrorMessage(bean, ex.getMessage(), args), ex); 310 } 311 catch (IllegalAccessException ex) { 312 throw new IllegalStateException(getInvocationErrorMessage(bean, ex.getMessage(), args), ex); 313 } 314 catch (InvocationTargetException ex) { 315 // Throw underlying exception 316 Throwable targetException = ex.getTargetException(); 317 if (targetException instanceof RuntimeException) { 318 throw (RuntimeException) targetException; 319 } 320 else { 321 String msg = getInvocationErrorMessage(bean, "Failed to invoke event listener method", args); 322 throw new UndeclaredThrowableException(targetException, msg); 323 } 324 } 325 } 326 327 /** 328 * Return the target bean instance to use. 329 */ 330 protected Object getTargetBean() { 331 Assert.notNull(this.applicationContext, "ApplicationContext must no be null"); 332 return this.applicationContext.getBean(this.beanName); 333 } 334 335 /** 336 * Return the condition to use. 337 * <p>Matches the {@code condition} attribute of the {@link EventListener} 338 * annotation or any matching attribute on a composed annotation that 339 * is meta-annotated with {@code @EventListener}. 340 */ 341 @Nullable 342 protected String getCondition() { 343 return this.condition; 344 } 345 346 /** 347 * Add additional details such as the bean type and method signature to 348 * the given error message. 349 * @param message error message to append the HandlerMethod details to 350 */ 351 protected String getDetailedErrorMessage(Object bean, String message) { 352 StringBuilder sb = new StringBuilder(message).append("\n"); 353 sb.append("HandlerMethod details: \n"); 354 sb.append("Bean [").append(bean.getClass().getName()).append("]\n"); 355 sb.append("Method [").append(this.method.toGenericString()).append("]\n"); 356 return sb.toString(); 357 } 358 359 /** 360 * Assert that the target bean class is an instance of the class where the given 361 * method is declared. In some cases the actual bean instance at event- 362 * processing time may be a JDK dynamic proxy (lazy initialization, prototype 363 * beans, and others). Event listener beans that require proxying should prefer 364 * class-based proxy mechanisms. 365 */ 366 private void assertTargetBean(Method method, Object targetBean, Object[] args) { 367 Class<?> methodDeclaringClass = method.getDeclaringClass(); 368 Class<?> targetBeanClass = targetBean.getClass(); 369 if (!methodDeclaringClass.isAssignableFrom(targetBeanClass)) { 370 String msg = "The event listener method class '" + methodDeclaringClass.getName() + 371 "' is not an instance of the actual bean class '" + 372 targetBeanClass.getName() + "'. If the bean requires proxying " + 373 "(e.g. due to @Transactional), please use class-based proxying."; 374 throw new IllegalStateException(getInvocationErrorMessage(targetBean, msg, args)); 375 } 376 } 377 378 private String getInvocationErrorMessage(Object bean, String message, Object[] resolvedArgs) { 379 StringBuilder sb = new StringBuilder(getDetailedErrorMessage(bean, message)); 380 sb.append("Resolved arguments: \n"); 381 for (int i = 0; i < resolvedArgs.length; i++) { 382 sb.append("[").append(i).append("] "); 383 if (resolvedArgs[i] == null) { 384 sb.append("[null] \n"); 385 } 386 else { 387 sb.append("[type=").append(resolvedArgs[i].getClass().getName()).append("] "); 388 sb.append("[value=").append(resolvedArgs[i]).append("]\n"); 389 } 390 } 391 return sb.toString(); 392 } 393 394 @Nullable 395 private ResolvableType getResolvableType(ApplicationEvent event) { 396 ResolvableType payloadType = null; 397 if (event instanceof PayloadApplicationEvent) { 398 PayloadApplicationEvent<?> payloadEvent = (PayloadApplicationEvent<?>) event; 399 ResolvableType eventType = payloadEvent.getResolvableType(); 400 if (eventType != null) { 401 payloadType = eventType.as(PayloadApplicationEvent.class).getGeneric(); 402 } 403 } 404 for (ResolvableType declaredEventType : this.declaredEventTypes) { 405 Class<?> eventClass = declaredEventType.toClass(); 406 if (!ApplicationEvent.class.isAssignableFrom(eventClass) && 407 payloadType != null && declaredEventType.isAssignableFrom(payloadType)) { 408 return declaredEventType; 409 } 410 if (eventClass.isInstance(event)) { 411 return declaredEventType; 412 } 413 } 414 return null; 415 } 416 417 418 @Override 419 public String toString() { 420 return this.method.toGenericString(); 421 } 422 423 424 private class ReactiveResultHandler { 425 426 public boolean subscribeToPublisher(Object result) { 427 ReactiveAdapter adapter = ReactiveAdapterRegistry.getSharedInstance().getAdapter(result.getClass()); 428 if (adapter != null) { 429 adapter.toPublisher(result).subscribe(new EventPublicationSubscriber()); 430 return true; 431 } 432 return false; 433 } 434 } 435 436 437 private class EventPublicationSubscriber implements Subscriber<Object> { 438 439 @Override 440 public void onSubscribe(Subscription s) { 441 s.request(Integer.MAX_VALUE); 442 } 443 444 @Override 445 public void onNext(Object o) { 446 publishEvents(o); 447 } 448 449 @Override 450 public void onError(Throwable t) { 451 handleAsyncError(t); 452 } 453 454 @Override 455 public void onComplete() { 456 } 457 } 458 459}