Kaynağa Gözat

Refactor and improve fallback support for @SentinelResource annotation (#693)

- Refactor the semantics and logic for blockHandler and fallback
- Add `fallbackClass` support for fallback in global classes
- Add `defaultFallback` support
- Update test cases and demo

Signed-off-by: Eric Zhao <sczyh16@gmail.com>
master
Eric Zhao GitHub 5 yıl önce
ebeveyn
işleme
82578e1fdf
Veri tabanında bu imza için bilinen anahtar bulunamadı GPG Anahtar Kimliği: 4AEE18F83AFDEB23
12 değiştirilmiş dosya ile 317 ekleme ve 93 silme
  1. +21
    -0
      sentinel-core/src/main/java/com/alibaba/csp/sentinel/annotation/SentinelResource.java
  2. +12
    -2
      sentinel-demo/sentinel-demo-annotation-spring-aop/src/main/java/com/alibaba/csp/sentinel/demo/annotation/aop/controller/DemoController.java
  3. +6
    -0
      sentinel-demo/sentinel-demo-annotation-spring-aop/src/main/java/com/alibaba/csp/sentinel/demo/annotation/aop/service/ExceptionUtil.java
  4. +2
    -0
      sentinel-demo/sentinel-demo-annotation-spring-aop/src/main/java/com/alibaba/csp/sentinel/demo/annotation/aop/service/TestService.java
  5. +23
    -2
      sentinel-demo/sentinel-demo-annotation-spring-aop/src/main/java/com/alibaba/csp/sentinel/demo/annotation/aop/service/TestServiceImpl.java
  6. +14
    -9
      sentinel-extension/sentinel-annotation-aspectj/README.md
  7. +137
    -71
      sentinel-extension/sentinel-annotation-aspectj/src/main/java/com/alibaba/csp/sentinel/annotation/aspectj/AbstractSentinelAspectSupport.java
  8. +14
    -2
      sentinel-extension/sentinel-annotation-aspectj/src/main/java/com/alibaba/csp/sentinel/annotation/aspectj/ResourceMetadataRegistry.java
  9. +11
    -1
      sentinel-extension/sentinel-annotation-aspectj/src/main/java/com/alibaba/csp/sentinel/annotation/aspectj/SentinelResourceAspect.java
  10. +50
    -3
      sentinel-extension/sentinel-annotation-aspectj/src/test/java/com/alibaba/csp/sentinel/annotation/aspectj/integration/SentinelAnnotationIntegrationTest.java
  11. +21
    -3
      sentinel-extension/sentinel-annotation-aspectj/src/test/java/com/alibaba/csp/sentinel/annotation/aspectj/integration/service/FooService.java
  12. +6
    -0
      sentinel-extension/sentinel-annotation-aspectj/src/test/java/com/alibaba/csp/sentinel/annotation/aspectj/integration/service/FooUtil.java

+ 21
- 0
sentinel-core/src/main/java/com/alibaba/csp/sentinel/annotation/SentinelResource.java Dosyayı Görüntüle

@@ -60,6 +60,27 @@ public @interface SentinelResource {
*/
String fallback() default "";

/**
* The {@code defaultFallback} is used as the default universal fallback method.
* It should not accept any parameters, and the return type should be compatible
* with the original method.
*
* @return name of the default fallback method, empty by default
* @since 1.6.0
*/
String defaultFallback() default "";

/**
* The {@code fallback} is located in the same class with the original method by default.
* However, if some methods share the same signature and intend to set the same fallback,
* then users can set the class where the fallback function exists. Note that the shared fallback method
* must be static.
*
* @return the class where the fallback method is located (only single class)
* @since 1.6.0
*/
Class<?>[] fallbackClass() default {};

/**
* @return the list of exception classes to trace, {@link Throwable} by default
* @since 1.5.1


+ 12
- 2
sentinel-demo/sentinel-demo-annotation-spring-aop/src/main/java/com/alibaba/csp/sentinel/demo/annotation/aop/controller/DemoController.java Dosyayı Görüntüle

@@ -19,6 +19,8 @@ import com.alibaba.csp.sentinel.demo.annotation.aop.service.TestService;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
@@ -31,8 +33,16 @@ public class DemoController {
private TestService service;

@GetMapping("/foo")
public String foo() throws Exception {
public String apiFoo(@RequestParam(required = false) Long t) throws Exception {
if (t == null) {
t = System.currentTimeMillis();
}
service.test();
return service.hello(System.currentTimeMillis());
return service.hello(t);
}

@GetMapping("/baz/{name}")
public String apiBaz(@PathVariable("name") String name) {
return service.helloAnother(name);
}
}

+ 6
- 0
sentinel-demo/sentinel-demo-annotation-spring-aop/src/main/java/com/alibaba/csp/sentinel/demo/annotation/aop/service/ExceptionUtil.java Dosyayı Görüntüle

@@ -23,6 +23,12 @@ import com.alibaba.csp.sentinel.slots.block.BlockException;
public final class ExceptionUtil {

public static void handleException(BlockException ex) {
// Handler method that handles BlockException when blocked.
// The method parameter list should match original method, with the last additional
// parameter with type BlockException. The return type should be same as the original method.
// The block handler method should be located in the same class with original method by default.
// If you want to use method in other classes, you can set the blockHandlerClass
// with corresponding Class (Note the method in other classes must be static).
System.out.println("Oops: " + ex.getClass().getCanonicalName());
}
}

+ 2
- 0
sentinel-demo/sentinel-demo-annotation-spring-aop/src/main/java/com/alibaba/csp/sentinel/demo/annotation/aop/service/TestService.java Dosyayı Görüntüle

@@ -23,4 +23,6 @@ public interface TestService {
void test();

String hello(long s);

String helloAnother(String name);
}

+ 23
- 2
sentinel-demo/sentinel-demo-annotation-spring-aop/src/main/java/com/alibaba/csp/sentinel/demo/annotation/aop/service/TestServiceImpl.java Dosyayı Görüntüle

@@ -33,14 +33,35 @@ public class TestServiceImpl implements TestService {
}

@Override
@SentinelResource(value = "hello", blockHandler = "exceptionHandler")
@SentinelResource(value = "hello", fallback = "helloFallback")
public String hello(long s) {
if (s < 0) {
throw new IllegalArgumentException("invalid arg");
}
return String.format("Hello at %d", s);
}

public String exceptionHandler(long s, BlockException ex) {
@Override
@SentinelResource(value = "helloAnother", defaultFallback = "defaultFallback",
exceptionsToIgnore = {IllegalStateException.class})
public String helloAnother(String name) {
if (name == null || "bad".equals(name)) {
throw new IllegalArgumentException("oops");
}
if ("foo".equals(name)) {
throw new IllegalStateException("oops");
}
return "Hello, " + name;
}

public String helloFallback(long s, Throwable ex) {
// Do some log here.
ex.printStackTrace();
return "Oops, error occurred at " + s;
}

public String defaultFallback() {
System.out.println("Go to default fallback");
return "default_fallback";
}
}

+ 14
- 9
sentinel-extension/sentinel-annotation-aspectj/README.md Dosyayı Görüntüle

@@ -9,26 +9,31 @@ The `@SentinelResource` annotation indicates a resource definition, including:

- `value`: Resource name, required (cannot be empty)
- `entryType`: Resource entry type (inbound or outbound), `EntryType.OUT` by default
- `fallback`: Fallback method when degraded (optional). The fallback method should be located in the same class with original method. The signature of the fallback method should match the original method (parameter types and return type).
- `blockHandler`: Handler method that handles `BlockException` when blocked. The signature should match original method, with the last additional parameter type `BlockException`. The block handler method should be located in the same class with original method by default. If you want to use method in other classes, you can set the `blockHandlerClass` with corresponding `Class` (Note the method in other classes must be *static*).
- `exceptionsToTrace`: List of business exception classes to trace and record (since 1.5.1).
- `fallback` (refactored since 1.6.0): Fallback method when exceptions caught (including `BlockException`, but except the exceptions defined in `exceptionsToIgnore`). The fallback method should be located in the same class with original method by default. If you want to use method in other classes, you can set the `fallbackClass` with corresponding `Class` (Note the method in other classes must be *static*). The method signature requirement:
- The return type should match the origin method;
- The parameter list should match the origin method, and an additional `Throwable` parameter can be provided to get the actual exception.
- `defaultFallback` (since 1.6.0): The default fallback method when exceptions caught (including `BlockException`, but except the exceptions defined in `exceptionsToIgnore`). Its intended to be a universal common fallback method. The method should be located in the same class with original method by default. If you want to use method in other classes, you can set the `fallbackClass` with corresponding `Class` (Note the method in other classes must be *static*). The default fallback method signature requirement:
- The return type should match the origin method;
- parameter list should be empty, and an additional `Throwable` parameter can be provided to get the actual exception.
- `blockHandler`: Handler method that handles `BlockException` when blocked. The parameter list of the method should match original method, with the last additional parameter type `BlockException`. The return type should be same as the original method. The `blockHandler` method should be located in the same class with original method by default. If you want to use method in other classes, you can set the `blockHandlerClass` with corresponding `Class` (Note the method in other classes must be *static*).
- `exceptionsToIgnore` (since 1.6.0): List of business exception classes that should not be traced and caught in fallback.
- `exceptionsToTrace` (since 1.5.1): List of business exception classes to trace and record. In most cases, using `exceptionsToIgnore` is better. If both `exceptionsToTrace` and `exceptionsToIgnore` are present, only `exceptionsToIgnore` will be activated.

For example:

```java
@SentinelResource(value = "abc", fallback = "doFallback", blockHandler = "handleException")
@SentinelResource(value = "abc", fallback = "doFallback")
public String doSomething(long i) {
return "Hello " + i;
}

public String doFallback(long i) {
public String doFallback(long i, Throwable t) {
// Return fallback value.
return "Oops, degraded";
return "fallback";
}

public String handleException(long i, BlockException ex) {
// Handle the block exception here.
return null;
public String defaultFallback(Throwable t) {
return "default_fallback";
}
```



+ 137
- 71
sentinel-extension/sentinel-annotation-aspectj/src/main/java/com/alibaba/csp/sentinel/annotation/aspectj/AbstractSentinelAspectSupport.java Dosyayı Görüntüle

@@ -19,7 +19,6 @@ import com.alibaba.csp.sentinel.Tracer;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.log.RecordLog;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.util.MethodUtil;
import com.alibaba.csp.sentinel.util.StringUtil;

@@ -37,103 +36,127 @@ import java.util.Arrays;
*/
public abstract class AbstractSentinelAspectSupport {

protected String getResourceName(String resourceName, /*@NonNull*/ Method method) {
// If resource name is present in annotation, use this value.
if (StringUtil.isNotBlank(resourceName)) {
return resourceName;
}
// Parse name of target method.
return MethodUtil.resolveMethodName(method);
}

protected Object handleBlockException(ProceedingJoinPoint pjp, SentinelResource annotation, BlockException ex)
throws Exception {
return handleBlockException(pjp, annotation.fallback(), annotation.blockHandler(),
annotation.blockHandlerClass(), ex);
}

protected Object handleBlockException(ProceedingJoinPoint pjp, String fallback, String blockHandler,
Class<?>[] blockHandlerClass, BlockException ex) throws Exception {
// Execute fallback for degrading if configured.
Object[] originArgs = pjp.getArgs();
if (isDegradeFailure(ex)) {
Method method = extractFallbackMethod(pjp, fallback);
if (method != null) {
return method.invoke(pjp.getTarget(), originArgs);
}
}
// Execute block handler if configured.
Method blockHandlerMethod = extractBlockHandlerMethod(pjp, blockHandler, blockHandlerClass);
if (blockHandlerMethod != null) {
// Construct args.
Object[] args = Arrays.copyOf(originArgs, originArgs.length + 1);
args[args.length - 1] = ex;
if (isStatic(blockHandlerMethod)) {
return blockHandlerMethod.invoke(null, args);
}
return blockHandlerMethod.invoke(pjp.getTarget(), args);
}
// If no block handler is present, then directly throw the exception.
throw ex;
protected void traceException(Throwable ex) {
Tracer.trace(ex);
}

protected void traceException(Throwable ex, SentinelResource annotation) {
Class<? extends Throwable>[] exceptionsToIgnore = annotation.exceptionsToIgnore();
// The ignore list will be checked first.
if (exceptionsToIgnore.length > 0 && isIgnoredException(ex, exceptionsToIgnore)) {
if (exceptionsToIgnore.length > 0 && exceptionBelongsTo(ex, exceptionsToIgnore)) {
return;
}
if (isTracedException(ex, annotation.exceptionsToTrace())) {
Tracer.trace(ex);
if (exceptionBelongsTo(ex, annotation.exceptionsToTrace())) {
traceException(ex);
}
}

/**
* Check whether the exception is in tracked list of exception classes.
* Check whether the exception is in provided list of exception classes.
*
* @param ex
* provided throwable
* @param exceptionsToTrace
* list of exceptions to trace
* @return true if it should be traced, otherwise false
* @param ex provided throwable
* @param exceptions list of exceptions
* @return true if it is in the list, otherwise false
*/
private boolean isTracedException(Throwable ex, Class<? extends Throwable>[] exceptionsToTrace) {
if (exceptionsToTrace == null) {
protected boolean exceptionBelongsTo(Throwable ex, Class<? extends Throwable>[] exceptions) {
if (exceptions == null) {
return false;
}
for (Class<? extends Throwable> exceptionToTrace : exceptionsToTrace) {
if (exceptionToTrace.isAssignableFrom(ex.getClass())) {
for (Class<? extends Throwable> exceptionClass : exceptions) {
if (exceptionClass.isAssignableFrom(ex.getClass())) {
return true;
}
}
return false;
}

private boolean isIgnoredException(Throwable ex, Class<? extends Throwable>[] exceptionsToIgnore) {
if (exceptionsToIgnore == null) {
return false;
protected String getResourceName(String resourceName, /*@NonNull*/ Method method) {
// If resource name is present in annotation, use this value.
if (StringUtil.isNotBlank(resourceName)) {
return resourceName;
}
for (Class<? extends Throwable> exceptionToIgnore : exceptionsToIgnore) {
if (exceptionToIgnore.isAssignableFrom(ex.getClass())) {
return true;
// Parse name of target method.
return MethodUtil.resolveMethodName(method);
}

protected Object handleFallback(ProceedingJoinPoint pjp, SentinelResource annotation, Throwable ex)
throws Throwable {
return handleFallback(pjp, annotation.fallback(), annotation.defaultFallback(), annotation.fallbackClass(), ex);
}

protected Object handleFallback(ProceedingJoinPoint pjp, String fallback, String defaultFallback,
Class<?>[] fallbackClass, Throwable ex) throws Throwable {
Object[] originArgs = pjp.getArgs();

// Execute fallback function if configured.
Method fallbackMethod = extractFallbackMethod(pjp, fallback, fallbackClass);
if (fallbackMethod != null) {
// Construct args.
int paramCount = fallbackMethod.getParameterTypes().length;
Object[] args;
if (paramCount == originArgs.length) {
args = originArgs;
} else {
args = Arrays.copyOf(originArgs, originArgs.length + 1);
args[args.length - 1] = ex;
}
if (isStatic(fallbackMethod)) {
return fallbackMethod.invoke(null, args);
}
return fallbackMethod.invoke(pjp.getTarget(), args);
}
return false;
// If fallback is absent, we'll try the defaultFallback if provided.
return handleDefaultFallback(pjp, defaultFallback, fallbackClass, ex);
}

private boolean isDegradeFailure(/*@NonNull*/ BlockException ex) {
return ex instanceof DegradeException;
protected Object handleDefaultFallback(ProceedingJoinPoint pjp, String defaultFallback,
Class<?>[] fallbackClass, Throwable ex) throws Throwable {
// Execute the default fallback function if configured.
Method fallbackMethod = extractDefaultFallbackMethod(pjp, defaultFallback, fallbackClass);
if (fallbackMethod != null) {
// Construct args.
Object[] args = fallbackMethod.getParameterTypes().length == 0 ? new Object[0] : new Object[] {ex};
if (isStatic(fallbackMethod)) {
return fallbackMethod.invoke(null, args);
}
return fallbackMethod.invoke(pjp.getTarget(), args);
}

// If no any fallback is present, then directly throw the exception.
throw ex;
}

private Method extractFallbackMethod(ProceedingJoinPoint pjp, String fallbackName) {
protected Object handleBlockException(ProceedingJoinPoint pjp, SentinelResource annotation, BlockException ex)
throws Throwable {

// Execute block handler if configured.
Method blockHandlerMethod = extractBlockHandlerMethod(pjp, annotation.blockHandler(),
annotation.blockHandlerClass());
if (blockHandlerMethod != null) {
Object[] originArgs = pjp.getArgs();
// Construct args.
Object[] args = Arrays.copyOf(originArgs, originArgs.length + 1);
args[args.length - 1] = ex;
if (isStatic(blockHandlerMethod)) {
return blockHandlerMethod.invoke(null, args);
}
return blockHandlerMethod.invoke(pjp.getTarget(), args);
}

// If no block handler is present, then go to fallback.
return handleFallback(pjp, annotation, ex);
}

private Method extractFallbackMethod(ProceedingJoinPoint pjp, String fallbackName, Class<?>[] locationClass) {
if (StringUtil.isBlank(fallbackName)) {
return null;
}
Class<?> clazz = pjp.getTarget().getClass();
boolean mustStatic = locationClass != null && locationClass.length >= 1;
Class<?> clazz = mustStatic ? locationClass[0] : pjp.getTarget().getClass();
MethodWrapper m = ResourceMetadataRegistry.lookupFallback(clazz, fallbackName);
if (m == null) {
// First time, resolve the fallback.
Method method = resolveFallbackInternal(pjp, fallbackName);
Method method = resolveFallbackInternal(pjp, fallbackName, clazz, mustStatic);
// Cache the method instance.
ResourceMetadataRegistry.updateFallbackFor(clazz, fallbackName, method);
return method;
@@ -144,10 +167,53 @@ public abstract class AbstractSentinelAspectSupport {
return m.getMethod();
}

private Method resolveFallbackInternal(ProceedingJoinPoint pjp, /*@NonNull*/ String name) {
private Method extractDefaultFallbackMethod(ProceedingJoinPoint pjp, String defaultFallback,
Class<?>[] locationClass) {
if (StringUtil.isBlank(defaultFallback)) {
return null;
}
boolean mustStatic = locationClass != null && locationClass.length >= 1;
Class<?> clazz = mustStatic ? locationClass[0] : pjp.getTarget().getClass();

MethodWrapper m = ResourceMetadataRegistry.lookupDefaultFallback(clazz, defaultFallback);
if (m == null) {
// First time, resolve the default fallback.
Class<?> originReturnType = resolveMethod(pjp).getReturnType();
// Default fallback allows two kinds of parameter list.
// One is empty parameter list.
Class<?>[] defaultParamTypes = new Class<?>[0];
// The other is a single parameter {@link Throwable} to get relevant exception info.
Class<?>[] paramTypeWithException = new Class<?>[] {Throwable.class};
// We first find the default fallback with empty parameter list.
Method method = findMethod(mustStatic, clazz, defaultFallback, originReturnType, defaultParamTypes);
// If default fallback with empty params is absent, we then try to find the other one.
if (method == null) {
method = findMethod(mustStatic, clazz, defaultFallback, originReturnType, paramTypeWithException);
}
// Cache the method instance.
ResourceMetadataRegistry.updateDefaultFallbackFor(clazz, defaultFallback, method);
return method;
}
if (!m.isPresent()) {
return null;
}
return m.getMethod();
}

private Method resolveFallbackInternal(ProceedingJoinPoint pjp, /*@NonNull*/ String name, Class<?> clazz,
boolean mustStatic) {
Method originMethod = resolveMethod(pjp);
Class<?>[] parameterTypes = originMethod.getParameterTypes();
return findMethod(false, pjp.getTarget().getClass(), name, originMethod.getReturnType(), parameterTypes);
// Fallback function allows two kinds of parameter list.
Class<?>[] defaultParamTypes = originMethod.getParameterTypes();
Class<?>[] paramTypesWithException = Arrays.copyOf(defaultParamTypes, defaultParamTypes.length + 1);
paramTypesWithException[paramTypesWithException.length - 1] = Throwable.class;
// We first find the fallback matching the signature of origin method.
Method method = findMethod(mustStatic, clazz, name, originMethod.getReturnType(), defaultParamTypes);
// If fallback matching the origin method is absent, we then try to find the other one.
if (method == null) {
method = findMethod(mustStatic, clazz, name, originMethod.getReturnType(), paramTypesWithException);
}
return method;
}

private Method extractBlockHandlerMethod(ProceedingJoinPoint pjp, String name, Class<?>[] locationClass) {
@@ -195,8 +261,8 @@ public abstract class AbstractSentinelAspectSupport {
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
if (name.equals(method.getName()) && checkStatic(mustStatic, method)
&& returnType.isAssignableFrom(method.getReturnType())
&& Arrays.equals(parameterTypes, method.getParameterTypes())) {
&& returnType.isAssignableFrom(method.getReturnType())
&& Arrays.equals(parameterTypes, method.getParameterTypes())) {

RecordLog.info("Resolved method [{0}] in class [{1}]", name, clazz.getCanonicalName());
return method;
@@ -209,7 +275,7 @@ public abstract class AbstractSentinelAspectSupport {
} else {
String methodType = mustStatic ? " static" : "";
RecordLog.warn("Cannot find{0} method [{1}] in class [{2}] with parameters {3}",
methodType, name, clazz.getCanonicalName(), Arrays.toString(parameterTypes));
methodType, name, clazz.getCanonicalName(), Arrays.toString(parameterTypes));
return null;
}
}
@@ -223,7 +289,7 @@ public abstract class AbstractSentinelAspectSupport {
Class<?> targetClass = joinPoint.getTarget().getClass();

Method method = getDeclaredMethodFor(targetClass, signature.getName(),
signature.getMethod().getParameterTypes());
signature.getMethod().getParameterTypes());
if (method == null) {
throw new IllegalStateException("Cannot resolve target method: " + signature.getMethod().getName());
}


+ 14
- 2
sentinel-extension/sentinel-annotation-aspectj/src/main/java/com/alibaba/csp/sentinel/annotation/aspectj/ResourceMetadataRegistry.java Dosyayı Görüntüle

@@ -28,13 +28,18 @@ import com.alibaba.csp.sentinel.util.StringUtil;
*/
final class ResourceMetadataRegistry {

private static final Map<String, MethodWrapper> FALLBACK_MAP = new ConcurrentHashMap<String, MethodWrapper>();
private static final Map<String, MethodWrapper> BLOCK_HANDLER_MAP = new ConcurrentHashMap<String, MethodWrapper>();
private static final Map<String, MethodWrapper> FALLBACK_MAP = new ConcurrentHashMap<>();
private static final Map<String, MethodWrapper> DEFAULT_FALLBACK_MAP = new ConcurrentHashMap<>();
private static final Map<String, MethodWrapper> BLOCK_HANDLER_MAP = new ConcurrentHashMap<>();

static MethodWrapper lookupFallback(Class<?> clazz, String name) {
return FALLBACK_MAP.get(getKey(clazz, name));
}

static MethodWrapper lookupDefaultFallback(Class<?> clazz, String name) {
return DEFAULT_FALLBACK_MAP.get(getKey(clazz, name));
}

static MethodWrapper lookupBlockHandler(Class<?> clazz, String name) {
return BLOCK_HANDLER_MAP.get(getKey(clazz, name));
}
@@ -46,6 +51,13 @@ final class ResourceMetadataRegistry {
FALLBACK_MAP.put(getKey(clazz, name), MethodWrapper.wrap(method));
}

static void updateDefaultFallbackFor(Class<?> clazz, String name, Method method) {
if (clazz == null || StringUtil.isBlank(name)) {
throw new IllegalArgumentException("Bad argument");
}
DEFAULT_FALLBACK_MAP.put(getKey(clazz, name), MethodWrapper.wrap(method));
}

static void updateBlockHandlerFor(Class<?> clazz, String name, Method method) {
if (clazz == null || StringUtil.isBlank(name)) {
throw new IllegalArgumentException("Bad argument");


+ 11
- 1
sentinel-extension/sentinel-annotation-aspectj/src/main/java/com/alibaba/csp/sentinel/annotation/aspectj/SentinelResourceAspect.java Dosyayı Görüntüle

@@ -58,7 +58,17 @@ public class SentinelResourceAspect extends AbstractSentinelAspectSupport {
} catch (BlockException ex) {
return handleBlockException(pjp, annotation, ex);
} catch (Throwable ex) {
traceException(ex, annotation);
Class<? extends Throwable>[] exceptionsToIgnore = annotation.exceptionsToIgnore();
// The ignore list will be checked first.
if (exceptionsToIgnore.length > 0 && exceptionBelongsTo(ex, exceptionsToIgnore)) {
throw ex;
}
if (exceptionBelongsTo(ex, annotation.exceptionsToTrace())) {
traceException(ex, annotation);
return handleFallback(pjp, annotation, ex);
}

// No fallback function can handle the exception, so throw it out.
throw ex;
} finally {
if (entry != null) {


+ 50
- 3
sentinel-extension/sentinel-annotation-aspectj/src/test/java/com/alibaba/csp/sentinel/annotation/aspectj/integration/SentinelAnnotationIntegrationTest.java Dosyayı Görüntüle

@@ -99,6 +99,56 @@ public class SentinelAnnotationIntegrationTest extends AbstractJUnit4SpringConte
}
}

@Test
public void testFallbackWithNoParams() throws Exception {
assertThat(fooService.fooWithFallback(1)).isEqualTo("Hello for 1");
String resourceName = "apiFooWithFallback";
ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName);
assertThat(cn).isNotNull();
assertThat(cn.passQps()).isPositive();

// Fallback should be ignored for this.
try {
fooService.fooWithFallback(5758);
fail("should not reach here");
} catch (IllegalAccessException e) {
assertThat(cn.exceptionQps()).isZero();
}

// Fallback should take effect.
assertThat(fooService.fooWithFallback(5763)).isEqualTo("eee...");
assertThat(cn.exceptionQps()).isPositive();
assertThat(cn.blockQps()).isZero();

FlowRuleManager.loadRules(Collections.singletonList(
new FlowRule(resourceName).setCount(0)
));
// Fallback should not take effect for BlockException, as blockHandler is configured.
assertThat(fooService.fooWithFallback(2221)).isEqualTo("Oops, 2221");
assertThat(cn.blockQps()).isPositive();
}

@Test
public void testDefaultFallbackWithSingleParam() {
assertThat(fooService.anotherFoo(1)).isEqualTo("Hello for 1");
String resourceName = "apiAnotherFooWithDefaultFallback";
ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName);
assertThat(cn).isNotNull();
assertThat(cn.passQps()).isPositive();

// Default fallback should take effect.
assertThat(fooService.anotherFoo(5758)).isEqualTo(FooUtil.FALLBACK_DEFAULT_RESULT);
assertThat(cn.exceptionQps()).isPositive();
assertThat(cn.blockQps()).isZero();

FlowRuleManager.loadRules(Collections.singletonList(
new FlowRule(resourceName).setCount(0)
));
// Default fallback should also take effect for BlockException.
assertThat(fooService.anotherFoo(5758)).isEqualTo(FooUtil.FALLBACK_DEFAULT_RESULT);
assertThat(cn.blockQps()).isPositive();
}

@Test
public void testNormalBlockHandlerAndFallback() throws Exception {
assertThat(fooService.foo(1)).isEqualTo("Hello for 1");
@@ -107,9 +157,6 @@ public class SentinelAnnotationIntegrationTest extends AbstractJUnit4SpringConte
assertThat(cn).isNotNull();
assertThat(cn.passQps()).isPositive();

// Test for fallback.
assertThat(fooService.foo(9527)).isEqualTo("eee...");

// Test for biz exception.
try {
fooService.foo(5758);


+ 21
- 3
sentinel-extension/sentinel-annotation-aspectj/src/test/java/com/alibaba/csp/sentinel/annotation/aspectj/integration/service/FooService.java Dosyayı Görüntüle

@@ -29,12 +29,21 @@ import java.util.concurrent.ThreadLocalRandom;
@Service
public class FooService {

@SentinelResource(value = "apiFoo", blockHandler = "fooBlockHandler", fallback = "fooFallbackFunc",
@SentinelResource(value = "apiFoo", blockHandler = "fooBlockHandler",
exceptionsToTrace = {IllegalArgumentException.class})
public String foo(int i) throws Exception {
if (i == 9527) {
throw new DegradeException("ggg");
if (i == 5758) {
throw new IllegalAccessException();
}
if (i == 5763) {
throw new IllegalArgumentException();
}
return "Hello for " + i;
}

@SentinelResource(value = "apiFooWithFallback", blockHandler = "fooBlockHandler", fallback = "fooFallbackFunc",
exceptionsToTrace = {IllegalArgumentException.class})
public String fooWithFallback(int i) throws Exception {
if (i == 5758) {
throw new IllegalAccessException();
}
@@ -44,6 +53,15 @@ public class FooService {
return "Hello for " + i;
}

@SentinelResource(value = "apiAnotherFooWithDefaultFallback", defaultFallback = "globalDefaultFallback",
fallbackClass = {FooUtil.class})
public String anotherFoo(int i) {
if (i == 5758) {
throw new IllegalArgumentException("oops");
}
return "Hello for " + i;
}

@SentinelResource(blockHandler = "globalBlockHandler", blockHandlerClass = FooUtil.class)
public int random() {
return ThreadLocalRandom.current().nextInt(0, 30000);


+ 6
- 0
sentinel-extension/sentinel-annotation-aspectj/src/test/java/com/alibaba/csp/sentinel/annotation/aspectj/integration/service/FooUtil.java Dosyayı Görüntüle

@@ -23,9 +23,15 @@ import com.alibaba.csp.sentinel.slots.block.BlockException;
public class FooUtil {

public static final int BLOCK_FLAG = 88888;
public static final String FALLBACK_DEFAULT_RESULT = "fallback";

public static int globalBlockHandler(BlockException ex) {
System.out.println("Oops: " + ex.getClass().getSimpleName());
return BLOCK_FLAG;
}

public static String globalDefaultFallback(Throwable t) {
System.out.println("Fallback caught: " + t.getClass().getSimpleName());
return FALLBACK_DEFAULT_RESULT;
}
}

Yükleniyor…
İptal
Kaydet