001/* 002 * Copyright 1999-2018 Alibaba Group Holding Ltd. 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 * http://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 */ 016package io.jboot.support.sentinel; 017 018import com.alibaba.csp.sentinel.Tracer; 019import com.alibaba.csp.sentinel.annotation.SentinelResource; 020import com.alibaba.csp.sentinel.log.RecordLog; 021import com.alibaba.csp.sentinel.slots.block.BlockException; 022import com.alibaba.csp.sentinel.util.MethodUtil; 023import com.alibaba.csp.sentinel.util.StringUtil; 024import com.jfinal.aop.Invocation; 025 026import java.lang.reflect.InvocationTargetException; 027import java.lang.reflect.Method; 028import java.lang.reflect.Modifier; 029import java.util.Arrays; 030 031/** 032 * Some common functions for Sentinel annotation aspect. 033 * 034 * @author Eric Zhao 035 */ 036public abstract class AbstractSentinelInterceptor { 037 038 protected void traceException(Throwable ex) { 039 Tracer.trace(ex); 040 } 041 042 protected void traceException(Throwable ex, SentinelResource annotation) { 043 Class<? extends Throwable>[] exceptionsToIgnore = annotation.exceptionsToIgnore(); 044 // The ignore list will be checked first. 045 if (exceptionsToIgnore.length > 0 && exceptionBelongsTo(ex, exceptionsToIgnore)) { 046 return; 047 } 048 if (exceptionBelongsTo(ex, annotation.exceptionsToTrace())) { 049 traceException(ex); 050 } 051 } 052 053 /** 054 * Check whether the exception is in provided list of exception classes. 055 * 056 * @param ex provided throwable 057 * @param exceptions list of exceptions 058 * @return true if it is in the list, otherwise false 059 */ 060 protected boolean exceptionBelongsTo(Throwable ex, Class<? extends Throwable>[] exceptions) { 061 if (exceptions == null) { 062 return false; 063 } 064 for (Class<? extends Throwable> exceptionClass : exceptions) { 065 if (exceptionClass.isAssignableFrom(ex.getClass())) { 066 return true; 067 } 068 } 069 return false; 070 } 071 072 protected String getResourceName(String resourceName, /*@NonNull*/ Method method) { 073 // If resource name is present in annotation, use this value. 074 if (StringUtil.isNotBlank(resourceName)) { 075 return resourceName; 076 } 077 // Parse name of target method. 078 return MethodUtil.resolveMethodName(method); 079 } 080 081 protected Object handleFallback(Invocation inv, SentinelResource annotation, Throwable ex) 082 throws Throwable { 083 return handleFallback(inv, annotation.fallback(), annotation.defaultFallback(), annotation.fallbackClass(), ex); 084 } 085 086 protected Object handleFallback(Invocation inv, String fallback, String defaultFallback, 087 Class<?>[] fallbackClass, Throwable ex) throws Throwable { 088 Object[] originArgs = inv.getArgs(); 089 090 // Execute fallback function if configured. 091 Method fallbackMethod = extractFallbackMethod(inv, fallback, fallbackClass); 092 if (fallbackMethod != null) { 093 // Construct args. 094 int paramCount = fallbackMethod.getParameterTypes().length; 095 Object[] args; 096 if (paramCount == originArgs.length) { 097 args = originArgs; 098 } else { 099 args = Arrays.copyOf(originArgs, originArgs.length + 1); 100 args[args.length - 1] = ex; 101 } 102 103 try { 104 if (isStatic(fallbackMethod)) { 105 return fallbackMethod.invoke(null, args); 106 } 107 return fallbackMethod.invoke(inv.getTarget(), args); 108 } catch (InvocationTargetException e) { 109 // throw the actual exception 110 throw e.getTargetException(); 111 } 112 } 113 // If fallback is absent, we'll try the defaultFallback if provided. 114 return handleDefaultFallback(inv, defaultFallback, fallbackClass, ex); 115 } 116 117 protected Object handleDefaultFallback(Invocation inv, String defaultFallback, Class<?>[] fallbackClass, Throwable ex) throws Throwable { 118 // Execute the default fallback function if configured. 119 Method fallbackMethod = extractDefaultFallbackMethod(inv, defaultFallback, fallbackClass); 120 if (fallbackMethod != null) { 121 // Construct args. 122 Object[] args = fallbackMethod.getParameterTypes().length == 0 ? new Object[0] : new Object[]{ex}; 123 try { 124 if (isStatic(fallbackMethod)) { 125 return fallbackMethod.invoke(null, args); 126 } 127 return fallbackMethod.invoke(inv.getTarget(), args); 128 } catch (InvocationTargetException e) { 129 // throw the actual exception 130 throw e.getTargetException(); 131 } 132 } 133 134 // If no any fallback is present, then directly throw the exception. 135 throw ex; 136 } 137 138 protected Object handleBlockException(Invocation inv, SentinelResource annotation, BlockException ex) 139 throws Throwable { 140 141 // Execute block handler if configured. 142 Method blockHandlerMethod = extractBlockHandlerMethod(inv, annotation.blockHandler(), 143 annotation.blockHandlerClass()); 144 if (blockHandlerMethod != null) { 145 Object[] originArgs = inv.getArgs(); 146 // Construct args. 147 Object[] args = Arrays.copyOf(originArgs, originArgs.length + 1); 148 args[args.length - 1] = ex; 149 try { 150 if (isStatic(blockHandlerMethod)) { 151 return blockHandlerMethod.invoke(null, args); 152 } 153 return blockHandlerMethod.invoke(inv.getTarget(), args); 154 } catch (InvocationTargetException e) { 155 // throw the actual exception 156 throw e.getTargetException(); 157 } 158 } 159 160 // If no block handler is present, then go to fallback. 161 return handleFallback(inv, annotation, ex); 162 } 163 164 private Method extractFallbackMethod(Invocation inv, String fallbackName, Class<?>[] locationClass) { 165 if (StringUtil.isBlank(fallbackName)) { 166 return null; 167 } 168 boolean mustStatic = locationClass != null && locationClass.length >= 1; 169 Class<?> clazz = mustStatic ? locationClass[0] : inv.getTarget().getClass(); 170 MethodWrapper m = ResourceMetadataRegistry.lookupFallback(clazz, fallbackName); 171 if (m == null) { 172 // First time, resolve the fallback. 173 Method method = resolveFallbackInternal(inv, fallbackName, clazz, mustStatic); 174 // Cache the method instance. 175 ResourceMetadataRegistry.updateFallbackFor(clazz, fallbackName, method); 176 return method; 177 } 178 if (!m.isPresent()) { 179 return null; 180 } 181 return m.getMethod(); 182 } 183 184 private Method extractDefaultFallbackMethod(Invocation inv, String defaultFallback, 185 Class<?>[] locationClass) { 186 if (StringUtil.isBlank(defaultFallback)) { 187 return null; 188 } 189 boolean mustStatic = locationClass != null && locationClass.length >= 1; 190 Class<?> clazz = mustStatic ? locationClass[0] : inv.getTarget().getClass(); 191 192 MethodWrapper m = ResourceMetadataRegistry.lookupDefaultFallback(clazz, defaultFallback); 193 if (m == null) { 194 // First time, resolve the default fallback. 195 Class<?> originReturnType = resolveMethod(inv).getReturnType(); 196 // Default fallback allows two kinds of parameter list. 197 // One is empty parameter list. 198 Class<?>[] defaultParamTypes = new Class<?>[0]; 199 // The other is a single parameter {@link Throwable} to get relevant exception info. 200 Class<?>[] paramTypeWithException = new Class<?>[]{Throwable.class}; 201 // We first find the default fallback with empty parameter list. 202 Method method = findMethod(mustStatic, clazz, defaultFallback, originReturnType, defaultParamTypes); 203 // If default fallback with empty params is absent, we then try to find the other one. 204 if (method == null) { 205 method = findMethod(mustStatic, clazz, defaultFallback, originReturnType, paramTypeWithException); 206 } 207 // Cache the method instance. 208 ResourceMetadataRegistry.updateDefaultFallbackFor(clazz, defaultFallback, method); 209 return method; 210 } 211 if (!m.isPresent()) { 212 return null; 213 } 214 return m.getMethod(); 215 } 216 217 private Method resolveFallbackInternal(Invocation inv, /*@NonNull*/ String name, Class<?> clazz, 218 boolean mustStatic) { 219 Method originMethod = resolveMethod(inv); 220 // Fallback function allows two kinds of parameter list. 221 Class<?>[] defaultParamTypes = originMethod.getParameterTypes(); 222 Class<?>[] paramTypesWithException = Arrays.copyOf(defaultParamTypes, defaultParamTypes.length + 1); 223 paramTypesWithException[paramTypesWithException.length - 1] = Throwable.class; 224 // We first find the fallback matching the signature of origin method. 225 Method method = findMethod(mustStatic, clazz, name, originMethod.getReturnType(), defaultParamTypes); 226 // If fallback matching the origin method is absent, we then try to find the other one. 227 if (method == null) { 228 method = findMethod(mustStatic, clazz, name, originMethod.getReturnType(), paramTypesWithException); 229 } 230 return method; 231 } 232 233 private Method extractBlockHandlerMethod(Invocation inv, String name, Class<?>[] locationClass) { 234 if (StringUtil.isBlank(name)) { 235 return null; 236 } 237 238 boolean mustStatic = locationClass != null && locationClass.length >= 1; 239 Class<?> clazz; 240 if (mustStatic) { 241 clazz = locationClass[0]; 242 } else { 243 // By default current class. 244 clazz = inv.getTarget().getClass(); 245 } 246 MethodWrapper m = ResourceMetadataRegistry.lookupBlockHandler(clazz, name); 247 if (m == null) { 248 // First time, resolve the block handler. 249 Method method = resolveBlockHandlerInternal(inv, name, clazz, mustStatic); 250 // Cache the method instance. 251 ResourceMetadataRegistry.updateBlockHandlerFor(clazz, name, method); 252 return method; 253 } 254 if (!m.isPresent()) { 255 return null; 256 } 257 return m.getMethod(); 258 } 259 260 private Method resolveBlockHandlerInternal(Invocation inv, /*@NonNull*/ String name, Class<?> clazz, 261 boolean mustStatic) { 262 Method originMethod = resolveMethod(inv); 263 Class<?>[] originList = originMethod.getParameterTypes(); 264 Class<?>[] parameterTypes = Arrays.copyOf(originList, originList.length + 1); 265 parameterTypes[parameterTypes.length - 1] = BlockException.class; 266 return findMethod(mustStatic, clazz, name, originMethod.getReturnType(), parameterTypes); 267 } 268 269 private boolean checkStatic(boolean mustStatic, Method method) { 270 return !mustStatic || isStatic(method); 271 } 272 273 private Method findMethod(boolean mustStatic, Class<?> clazz, String name, Class<?> returnType, 274 Class<?>... parameterTypes) { 275 Method[] methods = clazz.getDeclaredMethods(); 276 for (Method method : methods) { 277 if (name.equals(method.getName()) && checkStatic(mustStatic, method) 278 && returnType.isAssignableFrom(method.getReturnType()) 279 && Arrays.equals(parameterTypes, method.getParameterTypes())) { 280 281 RecordLog.info("Resolved method [{0}] in class [{1}]", name, clazz.getCanonicalName()); 282 return method; 283 } 284 } 285 // Current class not found, find in the super classes recursively. 286 Class<?> superClass = clazz.getSuperclass(); 287 if (superClass != null && !Object.class.equals(superClass)) { 288 return findMethod(mustStatic, superClass, name, returnType, parameterTypes); 289 } else { 290 String methodType = mustStatic ? " static" : ""; 291 RecordLog.warn("Cannot find{0} method [{1}] in class [{2}] with parameters {3}", 292 methodType, name, clazz.getCanonicalName(), Arrays.toString(parameterTypes)); 293 return null; 294 } 295 } 296 297 private boolean isStatic(Method method) { 298 return Modifier.isStatic(method.getModifiers()); 299 } 300 301 protected Method resolveMethod(Invocation inv) { 302 return inv.getMethod(); 303 } 304 305}