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