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.UndeclaredThrowableException; 022import java.util.ArrayList; 023import java.util.Collection; 024import java.util.Collections; 025import java.util.List; 026 027import org.apache.commons.logging.Log; 028import org.apache.commons.logging.LogFactory; 029 030import org.springframework.context.ApplicationContext; 031import org.springframework.context.ApplicationEvent; 032import org.springframework.context.PayloadApplicationEvent; 033import org.springframework.context.expression.AnnotatedElementKey; 034import org.springframework.core.BridgeMethodResolver; 035import org.springframework.core.ResolvableType; 036import org.springframework.core.annotation.AnnotatedElementUtils; 037import org.springframework.core.annotation.Order; 038import org.springframework.expression.EvaluationContext; 039import org.springframework.util.Assert; 040import org.springframework.util.ClassUtils; 041import org.springframework.util.ObjectUtils; 042import org.springframework.util.ReflectionUtils; 043import org.springframework.util.StringUtils; 044 045/** 046 * {@link GenericApplicationListener} adapter that delegates the processing of 047 * an event to an {@link EventListener} annotated method. 048 * 049 * <p>Delegates to {@link #processEvent(ApplicationEvent)} to give sub-classes 050 * a chance to deviate from the default. Unwraps the content of a 051 * {@link PayloadApplicationEvent} if necessary to allow method declaration 052 * to define any arbitrary event type. If a condition is defined, it is 053 * evaluated prior to invoking the underlying method. 054 * 055 * @author Stephane Nicoll 056 * @author Juergen Hoeller 057 * @author Sam Brannen 058 * @since 4.2 059 */ 060public class ApplicationListenerMethodAdapter implements GenericApplicationListener { 061 062 protected final Log logger = LogFactory.getLog(getClass()); 063 064 private final String beanName; 065 066 private final Method method; 067 068 private final Class<?> targetClass; 069 070 private final Method bridgedMethod; 071 072 private final List<ResolvableType> declaredEventTypes; 073 074 private final String condition; 075 076 private final int order; 077 078 private final AnnotatedElementKey methodKey; 079 080 private ApplicationContext applicationContext; 081 082 private EventExpressionEvaluator evaluator; 083 084 085 public ApplicationListenerMethodAdapter(String beanName, Class<?> targetClass, Method method) { 086 this.beanName = beanName; 087 this.method = method; 088 this.targetClass = targetClass; 089 this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method); 090 091 Method targetMethod = ClassUtils.getMostSpecificMethod(method, targetClass); 092 EventListener ann = AnnotatedElementUtils.findMergedAnnotation(targetMethod, EventListener.class); 093 094 this.declaredEventTypes = resolveDeclaredEventTypes(method, ann); 095 this.condition = (ann != null ? ann.condition() : null); 096 this.order = resolveOrder(targetMethod); 097 098 this.methodKey = new AnnotatedElementKey(method, targetClass); 099 } 100 101 102 private List<ResolvableType> resolveDeclaredEventTypes(Method method, EventListener ann) { 103 int count = method.getParameterTypes().length; 104 if (count > 1) { 105 throw new IllegalStateException( 106 "Maximum one parameter is allowed for event listener method: " + method); 107 } 108 if (ann != null && ann.classes().length > 0) { 109 List<ResolvableType> types = new ArrayList<ResolvableType>(ann.classes().length); 110 for (Class<?> eventType : ann.classes()) { 111 types.add(ResolvableType.forClass(eventType)); 112 } 113 return types; 114 } 115 else { 116 if (count == 0) { 117 throw new IllegalStateException( 118 "Event parameter is mandatory for event listener method: " + method); 119 } 120 return Collections.singletonList(ResolvableType.forMethodParameter(method, 0)); 121 } 122 } 123 124 private int resolveOrder(Method method) { 125 Order ann = AnnotatedElementUtils.findMergedAnnotation(method, Order.class); 126 return (ann != null ? ann.value() : 0); 127 } 128 129 /** 130 * Initialize this instance. 131 */ 132 void init(ApplicationContext applicationContext, EventExpressionEvaluator evaluator) { 133 this.applicationContext = applicationContext; 134 this.evaluator = evaluator; 135 } 136 137 138 @Override 139 public void onApplicationEvent(ApplicationEvent event) { 140 processEvent(event); 141 } 142 143 @Override 144 public boolean supportsEventType(ResolvableType eventType) { 145 for (ResolvableType declaredEventType : this.declaredEventTypes) { 146 if (declaredEventType.isAssignableFrom(eventType)) { 147 return true; 148 } 149 else if (PayloadApplicationEvent.class.isAssignableFrom(eventType.getRawClass())) { 150 ResolvableType payloadType = eventType.as(PayloadApplicationEvent.class).getGeneric(); 151 if (declaredEventType.isAssignableFrom(payloadType)) { 152 return true; 153 } 154 } 155 } 156 return eventType.hasUnresolvableGenerics(); 157 } 158 159 @Override 160 public boolean supportsSourceType(Class<?> sourceType) { 161 return true; 162 } 163 164 @Override 165 public int getOrder() { 166 return this.order; 167 } 168 169 170 /** 171 * Process the specified {@link ApplicationEvent}, checking if the condition 172 * match and handling non-null result, if any. 173 */ 174 public void processEvent(ApplicationEvent event) { 175 Object[] args = resolveArguments(event); 176 if (shouldHandle(event, args)) { 177 Object result = doInvoke(args); 178 if (result != null) { 179 handleResult(result); 180 } 181 else { 182 logger.trace("No result object given - no result to handle"); 183 } 184 } 185 } 186 187 /** 188 * Resolve the method arguments to use for the specified {@link ApplicationEvent}. 189 * <p>These arguments will be used to invoke the method handled by this instance. 190 * Can return {@code null} to indicate that no suitable arguments could be resolved 191 * and therefore the method should not be invoked at all for the specified event. 192 */ 193 protected Object[] resolveArguments(ApplicationEvent event) { 194 ResolvableType declaredEventType = getResolvableType(event); 195 if (declaredEventType == null) { 196 return null; 197 } 198 if (this.method.getParameterTypes().length == 0) { 199 return new Object[0]; 200 } 201 Class<?> eventClass = declaredEventType.getRawClass(); 202 if ((eventClass == null || !ApplicationEvent.class.isAssignableFrom(eventClass)) && 203 event instanceof PayloadApplicationEvent) { 204 Object payload = ((PayloadApplicationEvent) event).getPayload(); 205 if (eventClass == null || eventClass.isInstance(payload)) { 206 return new Object[] {payload}; 207 } 208 } 209 return new Object[] {event}; 210 } 211 212 protected void handleResult(Object result) { 213 if (result.getClass().isArray()) { 214 Object[] events = ObjectUtils.toObjectArray(result); 215 for (Object event : events) { 216 publishEvent(event); 217 } 218 } 219 else if (result instanceof Collection<?>) { 220 Collection<?> events = (Collection<?>) result; 221 for (Object event : events) { 222 publishEvent(event); 223 } 224 } 225 else { 226 publishEvent(result); 227 } 228 } 229 230 private void publishEvent(Object event) { 231 if (event != null) { 232 Assert.notNull(this.applicationContext, "ApplicationContext must not be null"); 233 this.applicationContext.publishEvent(event); 234 } 235 } 236 237 private boolean shouldHandle(ApplicationEvent event, Object[] args) { 238 if (args == null) { 239 return false; 240 } 241 String condition = getCondition(); 242 if (StringUtils.hasText(condition)) { 243 Assert.notNull(this.evaluator, "EventExpressionEvaluator must no be null"); 244 EvaluationContext evaluationContext = this.evaluator.createEvaluationContext( 245 event, this.targetClass, this.method, args, this.applicationContext); 246 return this.evaluator.condition(condition, this.methodKey, evaluationContext); 247 } 248 return true; 249 } 250 251 /** 252 * Invoke the event listener method with the given argument values. 253 */ 254 protected Object doInvoke(Object... args) { 255 Object bean = getTargetBean(); 256 ReflectionUtils.makeAccessible(this.bridgedMethod); 257 try { 258 return this.bridgedMethod.invoke(bean, args); 259 } 260 catch (IllegalArgumentException ex) { 261 assertTargetBean(this.bridgedMethod, bean, args); 262 throw new IllegalStateException(getInvocationErrorMessage(bean, ex.getMessage(), args), ex); 263 } 264 catch (IllegalAccessException ex) { 265 throw new IllegalStateException(getInvocationErrorMessage(bean, ex.getMessage(), args), ex); 266 } 267 catch (InvocationTargetException ex) { 268 // Throw underlying exception 269 Throwable targetException = ex.getTargetException(); 270 if (targetException instanceof RuntimeException) { 271 throw (RuntimeException) targetException; 272 } 273 else { 274 String msg = getInvocationErrorMessage(bean, "Failed to invoke event listener method", args); 275 throw new UndeclaredThrowableException(targetException, msg); 276 } 277 } 278 } 279 280 /** 281 * Return the target bean instance to use. 282 */ 283 protected Object getTargetBean() { 284 Assert.notNull(this.applicationContext, "ApplicationContext must no be null"); 285 return this.applicationContext.getBean(this.beanName); 286 } 287 288 /** 289 * Return the condition to use. 290 * <p>Matches the {@code condition} attribute of the {@link EventListener} 291 * annotation or any matching attribute on a composed annotation that 292 * is meta-annotated with {@code @EventListener}. 293 */ 294 protected String getCondition() { 295 return this.condition; 296 } 297 298 /** 299 * Add additional details such as the bean type and method signature to 300 * the given error message. 301 * @param message error message to append the HandlerMethod details to 302 */ 303 protected String getDetailedErrorMessage(Object bean, String message) { 304 StringBuilder sb = new StringBuilder(message).append("\n"); 305 sb.append("HandlerMethod details: \n"); 306 sb.append("Bean [").append(bean.getClass().getName()).append("]\n"); 307 sb.append("Method [").append(this.bridgedMethod.toGenericString()).append("]\n"); 308 return sb.toString(); 309 } 310 311 /** 312 * Assert that the target bean class is an instance of the class where the given 313 * method is declared. In some cases the actual bean instance at event- 314 * processing time may be a JDK dynamic proxy (lazy initialization, prototype 315 * beans, and others). Event listener beans that require proxying should prefer 316 * class-based proxy mechanisms. 317 */ 318 private void assertTargetBean(Method method, Object targetBean, Object[] args) { 319 Class<?> methodDeclaringClass = method.getDeclaringClass(); 320 Class<?> targetBeanClass = targetBean.getClass(); 321 if (!methodDeclaringClass.isAssignableFrom(targetBeanClass)) { 322 String msg = "The event listener method class '" + methodDeclaringClass.getName() + 323 "' is not an instance of the actual bean class '" + 324 targetBeanClass.getName() + "'. If the bean requires proxying " + 325 "(e.g. due to @Transactional), please use class-based proxying."; 326 throw new IllegalStateException(getInvocationErrorMessage(targetBean, msg, args)); 327 } 328 } 329 330 private String getInvocationErrorMessage(Object bean, String message, Object[] resolvedArgs) { 331 StringBuilder sb = new StringBuilder(getDetailedErrorMessage(bean, message)); 332 sb.append("Resolved arguments: \n"); 333 for (int i = 0; i < resolvedArgs.length; i++) { 334 sb.append("[").append(i).append("] "); 335 if (resolvedArgs[i] == null) { 336 sb.append("[null] \n"); 337 } 338 else { 339 sb.append("[type=").append(resolvedArgs[i].getClass().getName()).append("] "); 340 sb.append("[value=").append(resolvedArgs[i]).append("]\n"); 341 } 342 } 343 return sb.toString(); 344 } 345 346 347 private ResolvableType getResolvableType(ApplicationEvent event) { 348 ResolvableType payloadType = null; 349 if (event instanceof PayloadApplicationEvent) { 350 PayloadApplicationEvent<?> payloadEvent = (PayloadApplicationEvent<?>) event; 351 payloadType = payloadEvent.getResolvableType().as(PayloadApplicationEvent.class).getGeneric(); 352 } 353 for (ResolvableType declaredEventType : this.declaredEventTypes) { 354 if (!ApplicationEvent.class.isAssignableFrom(declaredEventType.getRawClass()) && payloadType != null) { 355 if (declaredEventType.isAssignableFrom(payloadType)) { 356 return declaredEventType; 357 } 358 } 359 if (declaredEventType.getRawClass().isInstance(event)) { 360 return declaredEventType; 361 } 362 } 363 return null; 364 } 365 366 367 @Override 368 public String toString() { 369 return this.method.toGenericString(); 370 } 371 372}