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.HashMap; 022import java.util.List; 023import java.util.Map; 024 025import org.springframework.core.ExceptionDepthComparator; 026import org.springframework.lang.Nullable; 027import org.springframework.util.Assert; 028import org.springframework.util.ConcurrentReferenceHashMap; 029 030/** 031 * Cache exception handling method mappings and provide options to look up a method 032 * that should handle an exception. If multiple methods match, they are sorted using 033 * {@link ExceptionDepthComparator} and the top match is returned. 034 * 035 * @author Rossen Stoyanchev 036 * @author Juergen Hoeller 037 * @since 4.0 038 */ 039public abstract class AbstractExceptionHandlerMethodResolver { 040 041 private final Map<Class<? extends Throwable>, Method> mappedMethods = new HashMap<>(16); 042 043 private final Map<Class<? extends Throwable>, Method> exceptionLookupCache = new ConcurrentReferenceHashMap<>(16); 044 045 046 /** 047 * Protected constructor accepting exception-to-method mappings. 048 */ 049 protected AbstractExceptionHandlerMethodResolver(Map<Class<? extends Throwable>, Method> mappedMethods) { 050 Assert.notNull(mappedMethods, "Mapped Methods must not be null"); 051 this.mappedMethods.putAll(mappedMethods); 052 } 053 054 /** 055 * Extract the exceptions this method handles.This implementation looks for 056 * sub-classes of Throwable in the method signature. 057 * The method is static to ensure safe use from sub-class constructors. 058 */ 059 @SuppressWarnings("unchecked") 060 protected static List<Class<? extends Throwable>> getExceptionsFromMethodSignature(Method method) { 061 List<Class<? extends Throwable>> result = new ArrayList<>(); 062 for (Class<?> paramType : method.getParameterTypes()) { 063 if (Throwable.class.isAssignableFrom(paramType)) { 064 result.add((Class<? extends Throwable>) paramType); 065 } 066 } 067 if (result.isEmpty()) { 068 throw new IllegalStateException("No exception types mapped to " + method); 069 } 070 return result; 071 } 072 073 074 /** 075 * Whether the contained type has any exception mappings. 076 */ 077 public boolean hasExceptionMappings() { 078 return !this.mappedMethods.isEmpty(); 079 } 080 081 /** 082 * Find a {@link Method} to handle the given exception. 083 * Use {@link ExceptionDepthComparator} if more than one match is found. 084 * @param exception the exception 085 * @return a Method to handle the exception, or {@code null} if none found 086 */ 087 @Nullable 088 public Method resolveMethod(Throwable exception) { 089 Method method = resolveMethodByExceptionType(exception.getClass()); 090 if (method == null) { 091 Throwable cause = exception.getCause(); 092 if (cause != null) { 093 method = resolveMethodByExceptionType(cause.getClass()); 094 } 095 } 096 return method; 097 } 098 099 /** 100 * Find a {@link Method} to handle the given exception type. This can be 101 * useful if an {@link Exception} instance is not available (e.g. for tools). 102 * @param exceptionType the exception type 103 * @return a Method to handle the exception, or {@code null} if none found 104 * @since 4.3.1 105 */ 106 @Nullable 107 public Method resolveMethodByExceptionType(Class<? extends Throwable> exceptionType) { 108 Method method = this.exceptionLookupCache.get(exceptionType); 109 if (method == null) { 110 method = getMappedMethod(exceptionType); 111 this.exceptionLookupCache.put(exceptionType, method); 112 } 113 return method; 114 } 115 116 /** 117 * Return the {@link Method} mapped to the given exception type, or {@code null} if none. 118 */ 119 @Nullable 120 private Method getMappedMethod(Class<? extends Throwable> exceptionType) { 121 List<Class<? extends Throwable>> matches = new ArrayList<>(); 122 for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) { 123 if (mappedException.isAssignableFrom(exceptionType)) { 124 matches.add(mappedException); 125 } 126 } 127 if (!matches.isEmpty()) { 128 matches.sort(new ExceptionDepthComparator(exceptionType)); 129 return this.mappedMethods.get(matches.get(0)); 130 } 131 else { 132 return null; 133 } 134 } 135 136}