001/* 002 * Copyright 2002-2018 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.messaging.handler.invocation; 018 019import java.lang.reflect.Method; 020import java.util.ArrayList; 021import java.util.Collection; 022import java.util.Collections; 023import java.util.Comparator; 024import java.util.LinkedHashMap; 025import java.util.List; 026import java.util.Map; 027import java.util.Set; 028import java.util.concurrent.ConcurrentHashMap; 029 030import org.apache.commons.logging.Log; 031import org.apache.commons.logging.LogFactory; 032 033import org.springframework.beans.factory.InitializingBean; 034import org.springframework.context.ApplicationContext; 035import org.springframework.context.ApplicationContextAware; 036import org.springframework.core.MethodIntrospector; 037import org.springframework.core.MethodParameter; 038import org.springframework.messaging.Message; 039import org.springframework.messaging.MessageHandler; 040import org.springframework.messaging.MessageHandlingException; 041import org.springframework.messaging.MessagingException; 042import org.springframework.messaging.handler.DestinationPatternsMessageCondition; 043import org.springframework.messaging.handler.HandlerMethod; 044import org.springframework.messaging.handler.MessagingAdviceBean; 045import org.springframework.messaging.support.MessageBuilder; 046import org.springframework.messaging.support.MessageHeaderAccessor; 047import org.springframework.util.Assert; 048import org.springframework.util.ClassUtils; 049import org.springframework.util.CollectionUtils; 050import org.springframework.util.LinkedMultiValueMap; 051import org.springframework.util.MultiValueMap; 052import org.springframework.util.concurrent.ListenableFuture; 053import org.springframework.util.concurrent.ListenableFutureCallback; 054 055/** 056 * Abstract base class for HandlerMethod-based message handling. Provides most of 057 * the logic required to discover handler methods at startup, find a matching handler 058 * method at runtime for a given message and invoke it. 059 * 060 * <p>Also supports discovering and invoking exception handling methods to process 061 * exceptions raised during message handling. 062 * 063 * @author Rossen Stoyanchev 064 * @author Juergen Hoeller 065 * @since 4.0 066 * @param <T> the type of the Object that contains information mapping a 067 * {@link org.springframework.messaging.handler.HandlerMethod} to incoming messages 068 */ 069public abstract class AbstractMethodMessageHandler<T> 070 implements MessageHandler, ApplicationContextAware, InitializingBean { 071 072 /** 073 * Bean name prefix for target beans behind scoped proxies. Used to exclude those 074 * targets from handler method detection, in favor of the corresponding proxies. 075 * <p>We're not checking the autowire-candidate status here, which is how the 076 * proxy target filtering problem is being handled at the autowiring level, 077 * since autowire-candidate may have been turned to {@code false} for other 078 * reasons, while still expecting the bean to be eligible for handler methods. 079 * <p>Originally defined in {@link org.springframework.aop.scope.ScopedProxyUtils} 080 * but duplicated here to avoid a hard dependency on the spring-aop module. 081 */ 082 private static final String SCOPED_TARGET_NAME_PREFIX = "scopedTarget."; 083 084 085 protected final Log logger = LogFactory.getLog(getClass()); 086 087 private final List<String> destinationPrefixes = new ArrayList<String>(); 088 089 private final List<HandlerMethodArgumentResolver> customArgumentResolvers = 090 new ArrayList<HandlerMethodArgumentResolver>(4); 091 092 private final List<HandlerMethodReturnValueHandler> customReturnValueHandlers = 093 new ArrayList<HandlerMethodReturnValueHandler>(4); 094 095 private final HandlerMethodArgumentResolverComposite argumentResolvers = 096 new HandlerMethodArgumentResolverComposite(); 097 098 private final HandlerMethodReturnValueHandlerComposite returnValueHandlers = 099 new HandlerMethodReturnValueHandlerComposite(); 100 101 private ApplicationContext applicationContext; 102 103 private final Map<T, HandlerMethod> handlerMethods = new LinkedHashMap<T, HandlerMethod>(64); 104 105 private final MultiValueMap<String, T> destinationLookup = new LinkedMultiValueMap<String, T>(64); 106 107 private final Map<Class<?>, AbstractExceptionHandlerMethodResolver> exceptionHandlerCache = 108 new ConcurrentHashMap<Class<?>, AbstractExceptionHandlerMethodResolver>(64); 109 110 private final Map<MessagingAdviceBean, AbstractExceptionHandlerMethodResolver> exceptionHandlerAdviceCache = 111 new LinkedHashMap<MessagingAdviceBean, AbstractExceptionHandlerMethodResolver>(64); 112 113 114 /** 115 * When this property is configured only messages to destinations matching 116 * one of the configured prefixes are eligible for handling. When there is a 117 * match the prefix is removed and only the remaining part of the destination 118 * is used for method-mapping purposes. 119 * <p>By default, no prefixes are configured in which case all messages are 120 * eligible for handling. 121 */ 122 public void setDestinationPrefixes(Collection<String> prefixes) { 123 this.destinationPrefixes.clear(); 124 if (prefixes != null) { 125 for (String prefix : prefixes) { 126 prefix = prefix.trim(); 127 this.destinationPrefixes.add(prefix); 128 } 129 } 130 } 131 132 /** 133 * Return the configured destination prefixes, if any. 134 */ 135 public Collection<String> getDestinationPrefixes() { 136 return this.destinationPrefixes; 137 } 138 139 /** 140 * Sets the list of custom {@code HandlerMethodArgumentResolver}s that will be used 141 * after resolvers for supported argument type. 142 */ 143 public void setCustomArgumentResolvers(List<HandlerMethodArgumentResolver> customArgumentResolvers) { 144 this.customArgumentResolvers.clear(); 145 if (customArgumentResolvers != null) { 146 this.customArgumentResolvers.addAll(customArgumentResolvers); 147 } 148 } 149 150 /** 151 * Return the configured custom argument resolvers, if any. 152 */ 153 public List<HandlerMethodArgumentResolver> getCustomArgumentResolvers() { 154 return this.customArgumentResolvers; 155 } 156 157 /** 158 * Set the list of custom {@code HandlerMethodReturnValueHandler}s that will be used 159 * after return value handlers for known types. 160 */ 161 public void setCustomReturnValueHandlers(List<HandlerMethodReturnValueHandler> customReturnValueHandlers) { 162 this.customReturnValueHandlers.clear(); 163 if (customReturnValueHandlers != null) { 164 this.customReturnValueHandlers.addAll(customReturnValueHandlers); 165 } 166 } 167 168 /** 169 * Return the configured custom return value handlers, if any. 170 */ 171 public List<HandlerMethodReturnValueHandler> getCustomReturnValueHandlers() { 172 return this.customReturnValueHandlers; 173 } 174 175 /** 176 * Configure the complete list of supported argument types, effectively overriding 177 * the ones configured by default. This is an advanced option; for most use cases 178 * it should be sufficient to use {@link #setCustomArgumentResolvers}. 179 */ 180 public void setArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) { 181 if (argumentResolvers == null) { 182 this.argumentResolvers.clear(); 183 return; 184 } 185 this.argumentResolvers.addResolvers(argumentResolvers); 186 } 187 188 /** 189 * Return the complete list of argument resolvers. 190 */ 191 public List<HandlerMethodArgumentResolver> getArgumentResolvers() { 192 return this.argumentResolvers.getResolvers(); 193 } 194 195 /** 196 * Configure the complete list of supported return value types, effectively overriding 197 * the ones configured by default. This is an advanced option; for most use cases 198 * it should be sufficient to use {@link #setCustomReturnValueHandlers}. 199 */ 200 public void setReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) { 201 if (returnValueHandlers == null) { 202 this.returnValueHandlers.clear(); 203 return; 204 } 205 this.returnValueHandlers.addHandlers(returnValueHandlers); 206 } 207 208 /** 209 * Return the complete list of return value handlers. 210 */ 211 public List<HandlerMethodReturnValueHandler> getReturnValueHandlers() { 212 return this.returnValueHandlers.getReturnValueHandlers(); 213 } 214 215 @Override 216 public void setApplicationContext(ApplicationContext applicationContext) { 217 this.applicationContext = applicationContext; 218 } 219 220 public ApplicationContext getApplicationContext() { 221 return this.applicationContext; 222 } 223 224 225 @Override 226 public void afterPropertiesSet() { 227 if (this.argumentResolvers.getResolvers().isEmpty()) { 228 this.argumentResolvers.addResolvers(initArgumentResolvers()); 229 } 230 231 if (this.returnValueHandlers.getReturnValueHandlers().isEmpty()) { 232 this.returnValueHandlers.addHandlers(initReturnValueHandlers()); 233 } 234 235 for (String beanName : this.applicationContext.getBeanNamesForType(Object.class)) { 236 if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) { 237 Class<?> beanType = null; 238 try { 239 beanType = getApplicationContext().getType(beanName); 240 } 241 catch (Throwable ex) { 242 // An unresolvable bean type, probably from a lazy bean - let's ignore it. 243 if (logger.isDebugEnabled()) { 244 logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex); 245 } 246 } 247 if (beanType != null && isHandler(beanType)) { 248 detectHandlerMethods(beanName); 249 } 250 } 251 } 252 } 253 254 /** 255 * Return the list of argument resolvers to use. Invoked only if the resolvers 256 * have not already been set via {@link #setArgumentResolvers}. 257 * <p>Subclasses should also take into account custom argument types configured via 258 * {@link #setCustomArgumentResolvers}. 259 */ 260 protected abstract List<? extends HandlerMethodArgumentResolver> initArgumentResolvers(); 261 262 /** 263 * Return the list of return value handlers to use. Invoked only if the return 264 * value handlers have not already been set via {@link #setReturnValueHandlers}. 265 * <p>Subclasses should also take into account custom return value types configured 266 * via {@link #setCustomReturnValueHandlers}. 267 */ 268 protected abstract List<? extends HandlerMethodReturnValueHandler> initReturnValueHandlers(); 269 270 271 /** 272 * Whether the given bean type should be introspected for messaging handling methods. 273 */ 274 protected abstract boolean isHandler(Class<?> beanType); 275 276 /** 277 * Detect if the given handler has any methods that can handle messages and if 278 * so register it with the extracted mapping information. 279 * @param handler the handler to check, either an instance of a Spring bean name 280 */ 281 protected final void detectHandlerMethods(final Object handler) { 282 Class<?> handlerType = (handler instanceof String ? 283 this.applicationContext.getType((String) handler) : handler.getClass()); 284 final Class<?> userType = ClassUtils.getUserClass(handlerType); 285 286 Map<Method, T> methods = MethodIntrospector.selectMethods(userType, 287 new MethodIntrospector.MetadataLookup<T>() { 288 @Override 289 public T inspect(Method method) { 290 return getMappingForMethod(method, userType); 291 } 292 }); 293 294 if (logger.isDebugEnabled()) { 295 logger.debug(methods.size() + " message handler methods found on " + userType + ": " + methods); 296 } 297 for (Map.Entry<Method, T> entry : methods.entrySet()) { 298 registerHandlerMethod(handler, entry.getKey(), entry.getValue()); 299 } 300 } 301 302 /** 303 * Provide the mapping for a handler method. 304 * @param method the method to provide a mapping for 305 * @param handlerType the handler type, possibly a sub-type of the method's declaring class 306 * @return the mapping, or {@code null} if the method is not mapped 307 */ 308 protected abstract T getMappingForMethod(Method method, Class<?> handlerType); 309 310 /** 311 * Register a handler method and its unique mapping. 312 * @param handler the bean name of the handler or the handler instance 313 * @param method the method to register 314 * @param mapping the mapping conditions associated with the handler method 315 * @throws IllegalStateException if another method was already registered 316 * under the same mapping 317 */ 318 protected void registerHandlerMethod(Object handler, Method method, T mapping) { 319 Assert.notNull(mapping, "Mapping must not be null"); 320 HandlerMethod newHandlerMethod = createHandlerMethod(handler, method); 321 HandlerMethod oldHandlerMethod = this.handlerMethods.get(mapping); 322 323 if (oldHandlerMethod != null && !oldHandlerMethod.equals(newHandlerMethod)) { 324 throw new IllegalStateException("Ambiguous mapping found. Cannot map '" + newHandlerMethod.getBean() + 325 "' bean method \n" + newHandlerMethod + "\nto " + mapping + ": There is already '" + 326 oldHandlerMethod.getBean() + "' bean method\n" + oldHandlerMethod + " mapped."); 327 } 328 329 this.handlerMethods.put(mapping, newHandlerMethod); 330 if (logger.isInfoEnabled()) { 331 logger.info("Mapped \"" + mapping + "\" onto " + newHandlerMethod); 332 } 333 334 for (String pattern : getDirectLookupDestinations(mapping)) { 335 this.destinationLookup.add(pattern, mapping); 336 } 337 } 338 339 /** 340 * Create a HandlerMethod instance from an Object handler that is either a handler 341 * instance or a String-based bean name. 342 */ 343 protected HandlerMethod createHandlerMethod(Object handler, Method method) { 344 HandlerMethod handlerMethod; 345 if (handler instanceof String) { 346 String beanName = (String) handler; 347 handlerMethod = new HandlerMethod(beanName, 348 this.applicationContext.getAutowireCapableBeanFactory(), method); 349 } 350 else { 351 handlerMethod = new HandlerMethod(handler, method); 352 } 353 return handlerMethod; 354 } 355 356 /** 357 * Return destinations contained in the mapping that are not patterns and are 358 * therefore suitable for direct lookups. 359 */ 360 protected abstract Set<String> getDirectLookupDestinations(T mapping); 361 362 /** 363 * Subclasses can invoke this method to populate the MessagingAdviceBean cache 364 * (e.g. to support "global" {@code @MessageExceptionHandler}). 365 * @since 4.2 366 */ 367 protected void registerExceptionHandlerAdvice( 368 MessagingAdviceBean bean, AbstractExceptionHandlerMethodResolver resolver) { 369 370 this.exceptionHandlerAdviceCache.put(bean, resolver); 371 } 372 373 /** 374 * Return a map with all handler methods and their mappings. 375 */ 376 public Map<T, HandlerMethod> getHandlerMethods() { 377 return Collections.unmodifiableMap(this.handlerMethods); 378 } 379 380 381 @Override 382 public void handleMessage(Message<?> message) throws MessagingException { 383 String destination = getDestination(message); 384 if (destination == null) { 385 return; 386 } 387 String lookupDestination = getLookupDestination(destination); 388 if (lookupDestination == null) { 389 return; 390 } 391 MessageHeaderAccessor headerAccessor = MessageHeaderAccessor.getMutableAccessor(message); 392 headerAccessor.setHeader(DestinationPatternsMessageCondition.LOOKUP_DESTINATION_HEADER, lookupDestination); 393 headerAccessor.setLeaveMutable(true); 394 message = MessageBuilder.createMessage(message.getPayload(), headerAccessor.getMessageHeaders()); 395 396 if (logger.isDebugEnabled()) { 397 logger.debug("Searching methods to handle " + 398 headerAccessor.getShortLogMessage(message.getPayload()) + 399 ", lookupDestination='" + lookupDestination + "'"); 400 } 401 402 handleMessageInternal(message, lookupDestination); 403 headerAccessor.setImmutable(); 404 } 405 406 protected abstract String getDestination(Message<?> message); 407 408 /** 409 * Check whether the given destination (of an incoming message) matches to 410 * one of the configured destination prefixes and if so return the remaining 411 * portion of the destination after the matched prefix. 412 * <p>If there are no matching prefixes, return {@code null}. 413 * <p>If there are no destination prefixes, return the destination as is. 414 */ 415 protected String getLookupDestination(String destination) { 416 if (destination == null) { 417 return null; 418 } 419 if (CollectionUtils.isEmpty(this.destinationPrefixes)) { 420 return destination; 421 } 422 for (String prefix : this.destinationPrefixes) { 423 if (destination.startsWith(prefix)) { 424 return destination.substring(prefix.length()); 425 } 426 } 427 return null; 428 } 429 430 protected void handleMessageInternal(Message<?> message, String lookupDestination) { 431 List<Match> matches = new ArrayList<Match>(); 432 433 List<T> mappingsByUrl = this.destinationLookup.get(lookupDestination); 434 if (mappingsByUrl != null) { 435 addMatchesToCollection(mappingsByUrl, message, matches); 436 } 437 if (matches.isEmpty()) { 438 // No direct hits, go through all mappings 439 Set<T> allMappings = this.handlerMethods.keySet(); 440 addMatchesToCollection(allMappings, message, matches); 441 } 442 if (matches.isEmpty()) { 443 handleNoMatch(this.handlerMethods.keySet(), lookupDestination, message); 444 return; 445 } 446 447 Comparator<Match> comparator = new MatchComparator(getMappingComparator(message)); 448 Collections.sort(matches, comparator); 449 if (logger.isTraceEnabled()) { 450 logger.trace("Found " + matches.size() + " handler methods: " + matches); 451 } 452 453 Match bestMatch = matches.get(0); 454 if (matches.size() > 1) { 455 Match secondBestMatch = matches.get(1); 456 if (comparator.compare(bestMatch, secondBestMatch) == 0) { 457 Method m1 = bestMatch.handlerMethod.getMethod(); 458 Method m2 = secondBestMatch.handlerMethod.getMethod(); 459 throw new IllegalStateException("Ambiguous handler methods mapped for destination '" + 460 lookupDestination + "': {" + m1 + ", " + m2 + "}"); 461 } 462 } 463 464 handleMatch(bestMatch.mapping, bestMatch.handlerMethod, lookupDestination, message); 465 } 466 467 private void addMatchesToCollection(Collection<T> mappingsToCheck, Message<?> message, List<Match> matches) { 468 for (T mapping : mappingsToCheck) { 469 T match = getMatchingMapping(mapping, message); 470 if (match != null) { 471 matches.add(new Match(match, this.handlerMethods.get(mapping))); 472 } 473 } 474 } 475 476 /** 477 * Check if a mapping matches the current message and return a possibly 478 * new mapping with conditions relevant to the current request. 479 * @param mapping the mapping to get a match for 480 * @param message the message being handled 481 * @return the match or {@code null} if there is no match 482 */ 483 protected abstract T getMatchingMapping(T mapping, Message<?> message); 484 485 protected void handleNoMatch(Set<T> ts, String lookupDestination, Message<?> message) { 486 logger.debug("No matching message handler methods."); 487 } 488 489 /** 490 * Return a comparator for sorting matching mappings. 491 * The returned comparator should sort 'better' matches higher. 492 * @param message the current Message 493 * @return the comparator, never {@code null} 494 */ 495 protected abstract Comparator<T> getMappingComparator(Message<?> message); 496 497 protected void handleMatch(T mapping, HandlerMethod handlerMethod, String lookupDestination, Message<?> message) { 498 if (logger.isDebugEnabled()) { 499 logger.debug("Invoking " + handlerMethod.getShortLogMessage()); 500 } 501 handlerMethod = handlerMethod.createWithResolvedBean(); 502 InvocableHandlerMethod invocable = new InvocableHandlerMethod(handlerMethod); 503 invocable.setMessageMethodArgumentResolvers(this.argumentResolvers); 504 try { 505 Object returnValue = invocable.invoke(message); 506 MethodParameter returnType = handlerMethod.getReturnType(); 507 if (void.class == returnType.getParameterType()) { 508 return; 509 } 510 if (this.returnValueHandlers.isAsyncReturnValue(returnValue, returnType)) { 511 ListenableFuture<?> future = this.returnValueHandlers.toListenableFuture(returnValue, returnType); 512 if (future != null) { 513 future.addCallback(new ReturnValueListenableFutureCallback(invocable, message)); 514 } 515 } 516 else { 517 this.returnValueHandlers.handleReturnValue(returnValue, returnType, message); 518 } 519 } 520 catch (Exception ex) { 521 processHandlerMethodException(handlerMethod, ex, message); 522 } 523 catch (Throwable ex) { 524 Exception handlingException = 525 new MessageHandlingException(message, "Unexpected handler method invocation error", ex); 526 processHandlerMethodException(handlerMethod, handlingException, message); 527 } 528 } 529 530 protected void processHandlerMethodException(HandlerMethod handlerMethod, Exception exception, Message<?> message) { 531 InvocableHandlerMethod invocable = getExceptionHandlerMethod(handlerMethod, exception); 532 if (invocable == null) { 533 logger.error("Unhandled exception from message handler method", exception); 534 return; 535 } 536 invocable.setMessageMethodArgumentResolvers(this.argumentResolvers); 537 if (logger.isDebugEnabled()) { 538 logger.debug("Invoking " + invocable.getShortLogMessage()); 539 } 540 try { 541 Throwable cause = exception.getCause(); 542 Object returnValue = (cause != null ? 543 invocable.invoke(message, exception, cause, handlerMethod) : 544 invocable.invoke(message, exception, handlerMethod)); 545 MethodParameter returnType = invocable.getReturnType(); 546 if (void.class == returnType.getParameterType()) { 547 return; 548 } 549 this.returnValueHandlers.handleReturnValue(returnValue, returnType, message); 550 } 551 catch (Throwable ex2) { 552 logger.error("Error while processing handler method exception", ex2); 553 } 554 } 555 556 /** 557 * Find an {@code @MessageExceptionHandler} method for the given exception. 558 * The default implementation searches methods in the class hierarchy of the 559 * HandlerMethod first and if not found, it continues searching for additional 560 * {@code @MessageExceptionHandler} methods among the configured 561 * {@linkplain org.springframework.messaging.handler.MessagingAdviceBean 562 * MessagingAdviceBean}, if any. 563 * @param handlerMethod the method where the exception was raised 564 * @param exception the raised exception 565 * @return a method to handle the exception, or {@code null} 566 * @since 4.2 567 */ 568 protected InvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) { 569 if (logger.isDebugEnabled()) { 570 logger.debug("Searching methods to handle " + exception.getClass().getSimpleName()); 571 } 572 Class<?> beanType = handlerMethod.getBeanType(); 573 AbstractExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(beanType); 574 if (resolver == null) { 575 resolver = createExceptionHandlerMethodResolverFor(beanType); 576 this.exceptionHandlerCache.put(beanType, resolver); 577 } 578 Method method = resolver.resolveMethod(exception); 579 if (method != null) { 580 return new InvocableHandlerMethod(handlerMethod.getBean(), method); 581 } 582 for (MessagingAdviceBean advice : this.exceptionHandlerAdviceCache.keySet()) { 583 if (advice.isApplicableToBeanType(beanType)) { 584 resolver = this.exceptionHandlerAdviceCache.get(advice); 585 method = resolver.resolveMethod(exception); 586 if (method != null) { 587 return new InvocableHandlerMethod(advice.resolveBean(), method); 588 } 589 } 590 } 591 return null; 592 } 593 594 protected abstract AbstractExceptionHandlerMethodResolver createExceptionHandlerMethodResolverFor( 595 Class<?> beanType); 596 597 598 @Override 599 public String toString() { 600 return getClass().getSimpleName() + "[prefixes=" + getDestinationPrefixes() + "]"; 601 } 602 603 604 /** 605 * A thin wrapper around a matched HandlerMethod and its matched mapping for 606 * the purpose of comparing the best match with a comparator in the context 607 * of a message. 608 */ 609 private class Match { 610 611 private final T mapping; 612 613 private final HandlerMethod handlerMethod; 614 615 public Match(T mapping, HandlerMethod handlerMethod) { 616 this.mapping = mapping; 617 this.handlerMethod = handlerMethod; 618 } 619 620 @Override 621 public String toString() { 622 return this.mapping.toString(); 623 } 624 } 625 626 627 private class MatchComparator implements Comparator<Match> { 628 629 private final Comparator<T> comparator; 630 631 public MatchComparator(Comparator<T> comparator) { 632 this.comparator = comparator; 633 } 634 635 @Override 636 public int compare(Match match1, Match match2) { 637 return this.comparator.compare(match1.mapping, match2.mapping); 638 } 639 } 640 641 642 private class ReturnValueListenableFutureCallback implements ListenableFutureCallback<Object> { 643 644 private final InvocableHandlerMethod handlerMethod; 645 646 private final Message<?> message; 647 648 public ReturnValueListenableFutureCallback(InvocableHandlerMethod handlerMethod, Message<?> message) { 649 this.handlerMethod = handlerMethod; 650 this.message = message; 651 } 652 653 @Override 654 public void onSuccess(Object result) { 655 try { 656 MethodParameter returnType = this.handlerMethod.getAsyncReturnValueType(result); 657 returnValueHandlers.handleReturnValue(result, returnType, this.message); 658 } 659 catch (Throwable ex) { 660 handleFailure(ex); 661 } 662 } 663 664 @Override 665 public void onFailure(Throwable ex) { 666 handleFailure(ex); 667 } 668 669 private void handleFailure(Throwable ex) { 670 Exception cause = (ex instanceof Exception ? (Exception) ex : new IllegalStateException(ex)); 671 processHandlerMethodException(this.handlerMethod, cause, this.message); 672 } 673 } 674 675}