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.aop.framework.adapter; 018 019import java.lang.reflect.InvocationTargetException; 020import java.lang.reflect.Method; 021import java.util.HashMap; 022import java.util.Map; 023 024import org.aopalliance.intercept.MethodInterceptor; 025import org.aopalliance.intercept.MethodInvocation; 026import org.apache.commons.logging.Log; 027import org.apache.commons.logging.LogFactory; 028 029import org.springframework.aop.AfterAdvice; 030import org.springframework.lang.Nullable; 031import org.springframework.util.Assert; 032 033/** 034 * Interceptor to wrap an after-throwing advice. 035 * 036 * <p>The signatures on handler methods on the {@code ThrowsAdvice} 037 * implementation method argument must be of the form:<br> 038 * 039 * {@code void afterThrowing([Method, args, target], ThrowableSubclass);} 040 * 041 * <p>Only the last argument is required. 042 * 043 * <p>Some examples of valid methods would be: 044 * 045 * <pre class="code">public void afterThrowing(Exception ex)</pre> 046 * <pre class="code">public void afterThrowing(RemoteException)</pre> 047 * <pre class="code">public void afterThrowing(Method method, Object[] args, Object target, Exception ex)</pre> 048 * <pre class="code">public void afterThrowing(Method method, Object[] args, Object target, ServletException ex)</pre> 049 * 050 * <p>This is a framework class that need not be used directly by Spring users. 051 * 052 * @author Rod Johnson 053 * @author Juergen Hoeller 054 * @see MethodBeforeAdviceInterceptor 055 * @see AfterReturningAdviceInterceptor 056 */ 057public class ThrowsAdviceInterceptor implements MethodInterceptor, AfterAdvice { 058 059 private static final String AFTER_THROWING = "afterThrowing"; 060 061 private static final Log logger = LogFactory.getLog(ThrowsAdviceInterceptor.class); 062 063 064 private final Object throwsAdvice; 065 066 /** Methods on throws advice, keyed by exception class. */ 067 private final Map<Class<?>, Method> exceptionHandlerMap = new HashMap<>(); 068 069 070 /** 071 * Create a new ThrowsAdviceInterceptor for the given ThrowsAdvice. 072 * @param throwsAdvice the advice object that defines the exception handler methods 073 * (usually a {@link org.springframework.aop.ThrowsAdvice} implementation) 074 */ 075 public ThrowsAdviceInterceptor(Object throwsAdvice) { 076 Assert.notNull(throwsAdvice, "Advice must not be null"); 077 this.throwsAdvice = throwsAdvice; 078 079 Method[] methods = throwsAdvice.getClass().getMethods(); 080 for (Method method : methods) { 081 if (method.getName().equals(AFTER_THROWING) && 082 (method.getParameterCount() == 1 || method.getParameterCount() == 4)) { 083 Class<?> throwableParam = method.getParameterTypes()[method.getParameterCount() - 1]; 084 if (Throwable.class.isAssignableFrom(throwableParam)) { 085 // An exception handler to register... 086 this.exceptionHandlerMap.put(throwableParam, method); 087 if (logger.isDebugEnabled()) { 088 logger.debug("Found exception handler method on throws advice: " + method); 089 } 090 } 091 } 092 } 093 094 if (this.exceptionHandlerMap.isEmpty()) { 095 throw new IllegalArgumentException( 096 "At least one handler method must be found in class [" + throwsAdvice.getClass() + "]"); 097 } 098 } 099 100 101 /** 102 * Return the number of handler methods in this advice. 103 */ 104 public int getHandlerMethodCount() { 105 return this.exceptionHandlerMap.size(); 106 } 107 108 109 @Override 110 public Object invoke(MethodInvocation mi) throws Throwable { 111 try { 112 return mi.proceed(); 113 } 114 catch (Throwable ex) { 115 Method handlerMethod = getExceptionHandler(ex); 116 if (handlerMethod != null) { 117 invokeHandlerMethod(mi, ex, handlerMethod); 118 } 119 throw ex; 120 } 121 } 122 123 /** 124 * Determine the exception handle method for the given exception. 125 * @param exception the exception thrown 126 * @return a handler for the given exception type, or {@code null} if none found 127 */ 128 @Nullable 129 private Method getExceptionHandler(Throwable exception) { 130 Class<?> exceptionClass = exception.getClass(); 131 if (logger.isTraceEnabled()) { 132 logger.trace("Trying to find handler for exception of type [" + exceptionClass.getName() + "]"); 133 } 134 Method handler = this.exceptionHandlerMap.get(exceptionClass); 135 while (handler == null && exceptionClass != Throwable.class) { 136 exceptionClass = exceptionClass.getSuperclass(); 137 handler = this.exceptionHandlerMap.get(exceptionClass); 138 } 139 if (handler != null && logger.isTraceEnabled()) { 140 logger.trace("Found handler for exception of type [" + exceptionClass.getName() + "]: " + handler); 141 } 142 return handler; 143 } 144 145 private void invokeHandlerMethod(MethodInvocation mi, Throwable ex, Method method) throws Throwable { 146 Object[] handlerArgs; 147 if (method.getParameterCount() == 1) { 148 handlerArgs = new Object[] {ex}; 149 } 150 else { 151 handlerArgs = new Object[] {mi.getMethod(), mi.getArguments(), mi.getThis(), ex}; 152 } 153 try { 154 method.invoke(this.throwsAdvice, handlerArgs); 155 } 156 catch (InvocationTargetException targetEx) { 157 throw targetEx.getTargetException(); 158 } 159 } 160 161}