From 82dabbcd8a83e5e424cc2a2cb06ab90d364f618f Mon Sep 17 00:00:00 2001
From: seasidesky <62706379+seasidesky@users.noreply.github.com>
Date: Sat, 13 Jun 2020 14:06:14 +0800
Subject: [PATCH] Add annotation extension for Java EE CDI (#1541)
---
pom.xml | 5 +
sentinel-extension/pom.xml | 1 +
.../README.md | 60 ++++
.../pom.xml | 59 ++++
.../AbstractSentinelInterceptorSupport.java | 332 ++++++++++++++++++
.../cdi/interceptor/MethodWrapper.java | 51 +++
.../interceptor/ResourceMetadataRegistry.java | 85 +++++
.../interceptor/SentinelResourceBinding.java | 117 ++++++
.../SentinelResourceInterceptor.java | 73 ++++
.../src/main/resources/META-INF/beans.xml | 5 +
...bstractSentinelInterceptorSupportTest.java | 39 ++
.../cdi/interceptor/MethodWrapperTest.java | 44 +++
.../ResourceMetadataRegistryTest.java | 86 +++++
...lAnnotationInterceptorIntegrationTest.java | 195 ++++++++++
.../integration/service/FooService.java | 84 +++++
.../integration/service/FooUtil.java | 37 ++
.../src/test/resources/META-INF/beans.xml | 8 +
17 files changed, 1281 insertions(+)
create mode 100644 sentinel-extension/sentinel-annotation-cdi-interceptor/README.md
create mode 100644 sentinel-extension/sentinel-annotation-cdi-interceptor/pom.xml
create mode 100644 sentinel-extension/sentinel-annotation-cdi-interceptor/src/main/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/AbstractSentinelInterceptorSupport.java
create mode 100644 sentinel-extension/sentinel-annotation-cdi-interceptor/src/main/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/MethodWrapper.java
create mode 100644 sentinel-extension/sentinel-annotation-cdi-interceptor/src/main/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/ResourceMetadataRegistry.java
create mode 100644 sentinel-extension/sentinel-annotation-cdi-interceptor/src/main/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/SentinelResourceBinding.java
create mode 100644 sentinel-extension/sentinel-annotation-cdi-interceptor/src/main/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/SentinelResourceInterceptor.java
create mode 100644 sentinel-extension/sentinel-annotation-cdi-interceptor/src/main/resources/META-INF/beans.xml
create mode 100644 sentinel-extension/sentinel-annotation-cdi-interceptor/src/test/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/AbstractSentinelInterceptorSupportTest.java
create mode 100644 sentinel-extension/sentinel-annotation-cdi-interceptor/src/test/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/MethodWrapperTest.java
create mode 100644 sentinel-extension/sentinel-annotation-cdi-interceptor/src/test/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/ResourceMetadataRegistryTest.java
create mode 100644 sentinel-extension/sentinel-annotation-cdi-interceptor/src/test/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/integration/SentinelAnnotationInterceptorIntegrationTest.java
create mode 100644 sentinel-extension/sentinel-annotation-cdi-interceptor/src/test/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/integration/service/FooService.java
create mode 100644 sentinel-extension/sentinel-annotation-cdi-interceptor/src/test/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/integration/service/FooUtil.java
create mode 100644 sentinel-extension/sentinel-annotation-cdi-interceptor/src/test/resources/META-INF/beans.xml
diff --git a/pom.xml b/pom.xml
index 66ff2745ea..b4e629b093 100755
--- a/pom.xml
+++ b/pom.xml
@@ -99,6 +99,11 @@
sentinel-annotation-aspectj
${project.version}
+
+ com.alibaba.csp
+ sentinel-annotation-cdi-interceptor
+ ${project.version}
+
com.alibaba.csp
sentinel-parameter-flow-control
diff --git a/sentinel-extension/pom.xml b/sentinel-extension/pom.xml
index 0918a75680..a02832d2e6 100755
--- a/sentinel-extension/pom.xml
+++ b/sentinel-extension/pom.xml
@@ -22,6 +22,7 @@
sentinel-datasource-spring-cloud-config
sentinel-datasource-consul
sentinel-datasource-etcd
+ sentinel-annotation-cdi-interceptor
diff --git a/sentinel-extension/sentinel-annotation-cdi-interceptor/README.md b/sentinel-extension/sentinel-annotation-cdi-interceptor/README.md
new file mode 100644
index 0000000000..edcaf83b18
--- /dev/null
+++ b/sentinel-extension/sentinel-annotation-cdi-interceptor/README.md
@@ -0,0 +1,60 @@
+# Sentinel Annotation cdi interceptor
+
+This extension is an implementation using cdi interceptor for Sentinel annotations. [JSR 318: Enterprise JavaBeansTM 3.1/Interceptors 1.2](https://jcp.org/en/jsr/detail?id=318) define the javax interceptor and [CDI](http://www.cdi-spec.org/) related specifications extends the Java Interceptors specification and allows interceptor bindings to be applied to CDI stereotypes.
+
+[CDI](http://www.cdi-spec.org/) is an abbreviation for Contexts and Dependency Injection, the related JSRs are : [JSR 365: Contexts and Dependency Injection for JavaTM 2.0](https://jcp.org/en/jsr/detail?id=365), [JSR 346: Contexts and Dependency Injection for JavaTM EE 1.1](https://jcp.org/en/jsr/detail?id=346), [JSR 299: Contexts and Dependency Injection for the JavaTM EE platform](https://jcp.org/en/jsr/detail?id=299)
+
+## Annotation
+
+The `@SentinelResourceBinding` is modified from `@SentinelResource` by adding `@InterceptorBinding` for CDI specification, and in order to interceptor all kinds of `@SentinelResourceBinding` with different attributes in one interceptor, `@SentinelResourceBinding`'s all attribute is annotated by `@Nonbinding`
+
+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` (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
+@SentinelResourceBinding(value = "abc", fallback = "doFallback")
+public String doSomething(long i) {
+ return "Hello " + i;
+}
+
+public String doFallback(long i, Throwable t) {
+ // Return fallback value.
+ return "fallback";
+}
+
+public String defaultFallback(Throwable t) {
+ return "default_fallback";
+}
+```
+
+## Configuration
+
+according to [9.4. Interceptor enablement and ordering](https://docs.jboss.org/cdi/spec/2.0/cdi-spec.html#enabled_interceptors) to enable interceptor, it should be configured in resources/META-INF/beans.xml :
+
+```
+
+
+
+ com.alibaba.csp.sentinel.annotation.cdi.interceptor.SentinelResourceInterceptor
+
+
+```
+
+
+
+
diff --git a/sentinel-extension/sentinel-annotation-cdi-interceptor/pom.xml b/sentinel-extension/sentinel-annotation-cdi-interceptor/pom.xml
new file mode 100644
index 0000000000..9911f8befe
--- /dev/null
+++ b/sentinel-extension/sentinel-annotation-cdi-interceptor/pom.xml
@@ -0,0 +1,59 @@
+
+
+
+ sentinel-extension
+ com.alibaba.csp
+ 1.8.0-SNAPSHOT
+
+ 4.0.0
+
+ sentinel-annotation-cdi-interceptor
+ jar
+
+
+ 1.2.5
+ 2.0.2
+
+
+
+
+ jakarta.interceptor
+ jakarta.interceptor-api
+ ${jakarta.interceptor-api.version}
+
+
+
+ jakarta.enterprise
+ jakarta.enterprise.cdi-api
+ ${jakarta.enterprise.cdi-api.version}
+
+
+
+ com.alibaba.csp
+ sentinel-core
+
+
+
+ junit
+ junit
+ test
+
+
+
+ org.assertj
+ assertj-core
+ test
+
+
+
+ org.jboss.weld.se
+ weld-se-shaded
+ 3.1.4.Final
+ test
+
+
+
+
+
diff --git a/sentinel-extension/sentinel-annotation-cdi-interceptor/src/main/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/AbstractSentinelInterceptorSupport.java b/sentinel-extension/sentinel-annotation-cdi-interceptor/src/main/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/AbstractSentinelInterceptorSupport.java
new file mode 100644
index 0000000000..4292cfc1f6
--- /dev/null
+++ b/sentinel-extension/sentinel-annotation-cdi-interceptor/src/main/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/AbstractSentinelInterceptorSupport.java
@@ -0,0 +1,332 @@
+/*
+ * Copyright 1999-2020 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.annotation.cdi.interceptor;
+
+import com.alibaba.csp.sentinel.Tracer;
+import com.alibaba.csp.sentinel.log.RecordLog;
+import com.alibaba.csp.sentinel.slots.block.BlockException;
+import com.alibaba.csp.sentinel.util.MethodUtil;
+import com.alibaba.csp.sentinel.util.StringUtil;
+
+import javax.interceptor.InvocationContext;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+
+/**
+ * Some common functions for Sentinel annotation aspect.
+ *
+ * @author Eric Zhao
+ */
+public abstract class AbstractSentinelInterceptorSupport {
+
+ protected void traceException(Throwable ex) {
+ Tracer.trace(ex);
+ }
+
+ protected void traceException(Throwable ex, SentinelResourceBinding annotation) {
+ Class extends Throwable>[] exceptionsToIgnore = annotation.exceptionsToIgnore();
+ // The ignore list will be checked first.
+ if (exceptionsToIgnore.length > 0 && exceptionBelongsTo(ex, exceptionsToIgnore)) {
+ return;
+ }
+ if (exceptionBelongsTo(ex, annotation.exceptionsToTrace())) {
+ traceException(ex);
+ }
+ }
+
+ /**
+ * Check whether the exception is in provided list of exception classes.
+ *
+ * @param ex provided throwable
+ * @param exceptions list of exceptions
+ * @return true if it is in the list, otherwise false
+ */
+ protected boolean exceptionBelongsTo(Throwable ex, Class extends Throwable>[] exceptions) {
+ if (exceptions == null) {
+ return false;
+ }
+ for (Class extends Throwable> exceptionClass : exceptions) {
+ if (exceptionClass.isAssignableFrom(ex.getClass())) {
+ return true;
+ }
+ }
+ 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;
+ }
+ // Parse name of target method.
+ return MethodUtil.resolveMethodName(method);
+ }
+
+ protected Object handleFallback(InvocationContext ctx, SentinelResourceBinding annotation, Throwable ex)
+ throws Throwable {
+ return handleFallback(ctx, annotation.fallback(), annotation.defaultFallback(), annotation.fallbackClass(), ex);
+ }
+
+ protected Object handleFallback(InvocationContext ctx, String fallback, String defaultFallback,
+ Class>[] fallbackClass, Throwable ex) throws Throwable {
+ Object[] originArgs = ctx.getParameters();
+
+ // Execute fallback function if configured.
+ Method fallbackMethod = extractFallbackMethod(ctx, 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;
+ }
+
+ try {
+ if (isStatic(fallbackMethod)) {
+ return fallbackMethod.invoke(null, args);
+ }
+ return fallbackMethod.invoke(ctx.getTarget(), args);
+ } catch (InvocationTargetException e) {
+ // throw the actual exception
+ throw e.getTargetException();
+ }
+ }
+ // If fallback is absent, we'll try the defaultFallback if provided.
+ return handleDefaultFallback(ctx, defaultFallback, fallbackClass, ex);
+ }
+
+ protected Object handleDefaultFallback(InvocationContext ctx, String defaultFallback,
+ Class>[] fallbackClass, Throwable ex) throws Throwable {
+ // Execute the default fallback function if configured.
+ Method fallbackMethod = extractDefaultFallbackMethod(ctx, defaultFallback, fallbackClass);
+ if (fallbackMethod != null) {
+ // Construct args.
+ Object[] args = fallbackMethod.getParameterTypes().length == 0 ? new Object[0] : new Object[] {ex};
+ try {
+ if (isStatic(fallbackMethod)) {
+ return fallbackMethod.invoke(null, args);
+ }
+ return fallbackMethod.invoke(ctx.getTarget(), args);
+ } catch (InvocationTargetException e) {
+ // throw the actual exception
+ throw e.getTargetException();
+ }
+ }
+
+ // If no any fallback is present, then directly throw the exception.
+ throw ex;
+ }
+
+ protected Object handleBlockException(InvocationContext ctx, SentinelResourceBinding annotation, BlockException ex)
+ throws Throwable {
+
+ // Execute block handler if configured.
+ Method blockHandlerMethod = extractBlockHandlerMethod(ctx, annotation.blockHandler(),
+ annotation.blockHandlerClass());
+ if (blockHandlerMethod != null) {
+ Object[] originArgs = ctx.getParameters();
+ // Construct args.
+ Object[] args = Arrays.copyOf(originArgs, originArgs.length + 1);
+ args[args.length - 1] = ex;
+ try {
+ if (isStatic(blockHandlerMethod)) {
+ return blockHandlerMethod.invoke(null, args);
+ }
+ return blockHandlerMethod.invoke(ctx.getTarget(), args);
+ } catch (InvocationTargetException e) {
+ // throw the actual exception
+ throw e.getTargetException();
+ }
+ }
+
+ // If no block handler is present, then go to fallback.
+ return handleFallback(ctx, annotation, ex);
+ }
+
+ private Method extractFallbackMethod(InvocationContext ctx, String fallbackName, Class>[] locationClass) {
+ if (StringUtil.isBlank(fallbackName)) {
+ return null;
+ }
+ boolean mustStatic = locationClass != null && locationClass.length >= 1;
+ Class> clazz = mustStatic ? locationClass[0] : ctx.getTarget().getClass();
+ MethodWrapper m = ResourceMetadataRegistry.lookupFallback(clazz, fallbackName);
+ if (m == null) {
+ // First time, resolve the fallback.
+ Method method = resolveFallbackInternal(ctx, fallbackName, clazz, mustStatic);
+ // Cache the method instance.
+ ResourceMetadataRegistry.updateFallbackFor(clazz, fallbackName, method);
+ return method;
+ }
+ if (!m.isPresent()) {
+ return null;
+ }
+ return m.getMethod();
+ }
+
+ private Method extractDefaultFallbackMethod(InvocationContext ctx, String defaultFallback,
+ Class>[] locationClass) {
+ if (StringUtil.isBlank(defaultFallback)) {
+ return null;
+ }
+ boolean mustStatic = locationClass != null && locationClass.length >= 1;
+ Class> clazz = mustStatic ? locationClass[0] : ctx.getTarget().getClass();
+
+ MethodWrapper m = ResourceMetadataRegistry.lookupDefaultFallback(clazz, defaultFallback);
+ if (m == null) {
+ // First time, resolve the default fallback.
+ Class> originReturnType = resolveMethod(ctx).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(InvocationContext ctx, /*@NonNull*/ String name, Class> clazz,
+ boolean mustStatic) {
+ Method originMethod = resolveMethod(ctx);
+ // 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(InvocationContext ctx, String name, Class>[] locationClass) {
+ if (StringUtil.isBlank(name)) {
+ return null;
+ }
+
+ boolean mustStatic = locationClass != null && locationClass.length >= 1;
+ Class> clazz;
+ if (mustStatic) {
+ clazz = locationClass[0];
+ } else {
+ // By default current class.
+ clazz = ctx.getTarget().getClass();
+ }
+ MethodWrapper m = ResourceMetadataRegistry.lookupBlockHandler(clazz, name);
+ if (m == null) {
+ // First time, resolve the block handler.
+ Method method = resolveBlockHandlerInternal(ctx, name, clazz, mustStatic);
+ // Cache the method instance.
+ ResourceMetadataRegistry.updateBlockHandlerFor(clazz, name, method);
+ return method;
+ }
+ if (!m.isPresent()) {
+ return null;
+ }
+ return m.getMethod();
+ }
+
+ private Method resolveBlockHandlerInternal(InvocationContext ctx, /*@NonNull*/ String name, Class> clazz,
+ boolean mustStatic) {
+ Method originMethod = resolveMethod(ctx);
+ Class>[] originList = originMethod.getParameterTypes();
+ Class>[] parameterTypes = Arrays.copyOf(originList, originList.length + 1);
+ parameterTypes[parameterTypes.length - 1] = BlockException.class;
+ return findMethod(mustStatic, clazz, name, originMethod.getReturnType(), parameterTypes);
+ }
+
+ private boolean checkStatic(boolean mustStatic, Method method) {
+ return !mustStatic || isStatic(method);
+ }
+
+ private Method findMethod(boolean mustStatic, Class> clazz, String name, Class> returnType,
+ Class>... parameterTypes) {
+ 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())) {
+
+ RecordLog.info("Resolved method [{}] in class [{}]", name, clazz.getCanonicalName());
+ return method;
+ }
+ }
+ // Current class not found, find in the super classes recursively.
+ Class> superClass = clazz.getSuperclass();
+ if (superClass != null && !Object.class.equals(superClass)) {
+ return findMethod(mustStatic, superClass, name, returnType, parameterTypes);
+ } else {
+ String methodType = mustStatic ? " static" : "";
+ RecordLog.warn("Cannot find{} method [{}] in class [{}] with parameters {}",
+ methodType, name, clazz.getCanonicalName(), Arrays.toString(parameterTypes));
+ return null;
+ }
+ }
+
+ private boolean isStatic(Method method) {
+ return Modifier.isStatic(method.getModifiers());
+ }
+
+ protected Method resolveMethod(InvocationContext ctx) {
+ Class> targetClass = ctx.getTarget().getClass();
+
+ Method method = getDeclaredMethodFor(targetClass, ctx.getMethod().getName(),
+ ctx.getMethod().getParameterTypes());
+ if (method == null) {
+ throw new IllegalStateException("Cannot resolve target method: " + ctx.getMethod().getName());
+ }
+ return method;
+ }
+
+ /**
+ * Get declared method with provided name and parameterTypes in given class and its super classes.
+ * All parameters should be valid.
+ *
+ * @param clazz class where the method is located
+ * @param name method name
+ * @param parameterTypes method parameter type list
+ * @return resolved method, null if not found
+ */
+ private Method getDeclaredMethodFor(Class> clazz, String name, Class>... parameterTypes) {
+ try {
+ return clazz.getDeclaredMethod(name, parameterTypes);
+ } catch (NoSuchMethodException e) {
+ Class> superClass = clazz.getSuperclass();
+ if (superClass != null) {
+ return getDeclaredMethodFor(superClass, name, parameterTypes);
+ }
+ }
+ return null;
+ }
+}
diff --git a/sentinel-extension/sentinel-annotation-cdi-interceptor/src/main/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/MethodWrapper.java b/sentinel-extension/sentinel-annotation-cdi-interceptor/src/main/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/MethodWrapper.java
new file mode 100644
index 0000000000..c12e724a72
--- /dev/null
+++ b/sentinel-extension/sentinel-annotation-cdi-interceptor/src/main/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/MethodWrapper.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 1999-2020 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.annotation.cdi.interceptor;
+
+import java.lang.reflect.Method;
+
+/**
+ * @author Eric Zhao
+ */
+class MethodWrapper {
+
+ private final Method method;
+ private final boolean present;
+
+ private MethodWrapper(Method method, boolean present) {
+ this.method = method;
+ this.present = present;
+ }
+
+ static MethodWrapper wrap(Method method) {
+ if (method == null) {
+ return none();
+ }
+ return new MethodWrapper(method, true);
+ }
+
+ static MethodWrapper none() {
+ return new MethodWrapper(null, false);
+ }
+
+ Method getMethod() {
+ return method;
+ }
+
+ boolean isPresent() {
+ return present;
+ }
+}
diff --git a/sentinel-extension/sentinel-annotation-cdi-interceptor/src/main/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/ResourceMetadataRegistry.java b/sentinel-extension/sentinel-annotation-cdi-interceptor/src/main/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/ResourceMetadataRegistry.java
new file mode 100644
index 0000000000..f0b29b2a87
--- /dev/null
+++ b/sentinel-extension/sentinel-annotation-cdi-interceptor/src/main/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/ResourceMetadataRegistry.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 1999-2020 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.annotation.cdi.interceptor;
+
+import com.alibaba.csp.sentinel.util.StringUtil;
+
+import java.lang.reflect.Method;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Registry for resource configuration metadata (e.g. fallback method)
+ *
+ * @author Eric Zhao
+ */
+final class ResourceMetadataRegistry {
+
+ private static final Map FALLBACK_MAP = new ConcurrentHashMap<>();
+ private static final Map DEFAULT_FALLBACK_MAP = new ConcurrentHashMap<>();
+ private static final Map 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));
+ }
+
+ static void updateFallbackFor(Class> clazz, String name, Method method) {
+ if (clazz == null || StringUtil.isBlank(name)) {
+ throw new IllegalArgumentException("Bad argument");
+ }
+ 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");
+ }
+ BLOCK_HANDLER_MAP.put(getKey(clazz, name), MethodWrapper.wrap(method));
+ }
+
+ private static String getKey(Class> clazz, String name) {
+ return String.format("%s:%s", clazz.getCanonicalName(), name);
+ }
+
+ /**
+ * Only for internal test.
+ */
+ static void clearFallbackMap() {
+ FALLBACK_MAP.clear();
+ }
+
+ /**
+ * Only for internal test.
+ */
+ static void clearBlockHandlerMap() {
+ BLOCK_HANDLER_MAP.clear();
+ }
+}
diff --git a/sentinel-extension/sentinel-annotation-cdi-interceptor/src/main/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/SentinelResourceBinding.java b/sentinel-extension/sentinel-annotation-cdi-interceptor/src/main/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/SentinelResourceBinding.java
new file mode 100644
index 0000000000..6354a06ec4
--- /dev/null
+++ b/sentinel-extension/sentinel-annotation-cdi-interceptor/src/main/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/SentinelResourceBinding.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 1999-2020 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.annotation.cdi.interceptor;
+
+import com.alibaba.csp.sentinel.EntryType;
+
+import javax.enterprise.util.Nonbinding;
+import javax.interceptor.InterceptorBinding;
+import java.lang.annotation.*;
+
+/**
+ * The annotation indicates a definition of Sentinel resource.
+ *
+ * @author Eric Zhao
+ */
+@InterceptorBinding
+@Target({ ElementType.METHOD, ElementType.TYPE })
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface SentinelResourceBinding {
+
+ /**
+ * @return name of the Sentinel resource
+ */
+ @Nonbinding
+ String value() default "";
+
+ /**
+ * @return the entry type (inbound or outbound), outbound by default
+ */
+ @Nonbinding
+ EntryType entryType() default EntryType.OUT;
+
+ /**
+ * @return the classification (type) of the resource
+ * @since 1.7.0
+ */
+ @Nonbinding
+ int resourceType() default 0;
+
+ /**
+ * @return name of the block exception function, empty by default
+ */
+ @Nonbinding
+ String blockHandler() default "";
+
+ /**
+ * The {@code blockHandler} 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 block handler,
+ * then users can set the class where the block handler exists. Note that the block handler method
+ * must be static.
+ *
+ * @return the class where the block handler exists, should not provide more than one classes
+ */
+ @Nonbinding
+ Class>[] blockHandlerClass() default {};
+
+ /**
+ * @return name of the fallback function, empty by default
+ */
+ @Nonbinding
+ 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
+ */
+ @Nonbinding
+ 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
+ */
+ @Nonbinding
+ Class>[] fallbackClass() default {};
+
+ /**
+ * @return the list of exception classes to trace, {@link Throwable} by default
+ * @since 1.5.1
+ */
+ @Nonbinding
+ Class extends Throwable>[] exceptionsToTrace() default {Throwable.class};
+
+ /**
+ * Indicates the exceptions to be ignored. Note that {@code exceptionsToTrace} should
+ * not appear with {@code exceptionsToIgnore} at the same time, or {@code exceptionsToIgnore}
+ * will be of higher precedence.
+ *
+ * @return the list of exception classes to ignore, empty by default
+ * @since 1.6.0
+ */
+ @Nonbinding
+ Class extends Throwable>[] exceptionsToIgnore() default {};
+}
diff --git a/sentinel-extension/sentinel-annotation-cdi-interceptor/src/main/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/SentinelResourceInterceptor.java b/sentinel-extension/sentinel-annotation-cdi-interceptor/src/main/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/SentinelResourceInterceptor.java
new file mode 100644
index 0000000000..a43f9a1ca8
--- /dev/null
+++ b/sentinel-extension/sentinel-annotation-cdi-interceptor/src/main/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/SentinelResourceInterceptor.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 1999-2020 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.annotation.cdi.interceptor;
+
+import com.alibaba.csp.sentinel.Entry;
+import com.alibaba.csp.sentinel.EntryType;
+import com.alibaba.csp.sentinel.SphU;
+import com.alibaba.csp.sentinel.slots.block.BlockException;
+
+import javax.annotation.Priority;
+import javax.interceptor.AroundInvoke;
+import javax.interceptor.Interceptor;
+import javax.interceptor.InvocationContext;
+
+/**
+ * @author sea
+ */
+@Interceptor
+@SentinelResourceBinding
+@Priority(0)
+public class SentinelResourceInterceptor extends AbstractSentinelInterceptorSupport {
+
+ @AroundInvoke
+ Object aroundInvoke(InvocationContext ctx) throws Throwable {
+ SentinelResourceBinding annotation = ctx.getMethod().getAnnotation(SentinelResourceBinding.class);
+ if (annotation == null) {
+ // Should not go through here.
+ throw new IllegalStateException("Wrong state for SentinelResource annotation");
+ }
+
+ String resourceName = getResourceName(annotation.value(), ctx.getMethod());
+ EntryType entryType = annotation.entryType();
+ int resourceType = annotation.resourceType();
+ Entry entry = null;
+ try {
+ entry = SphU.entry(resourceName, resourceType, entryType, ctx.getParameters());
+ Object result = ctx.proceed();
+ return result;
+ } catch (BlockException ex) {
+ return handleBlockException(ctx, annotation, ex);
+ } catch (Throwable ex) {
+ 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);
+ return handleFallback(ctx, annotation, ex);
+ }
+
+ // No fallback function can handle the exception, so throw it out.
+ throw ex;
+ } finally {
+ if (entry != null) {
+ entry.exit(1, ctx.getParameters());
+ }
+ }
+ }
+}
diff --git a/sentinel-extension/sentinel-annotation-cdi-interceptor/src/main/resources/META-INF/beans.xml b/sentinel-extension/sentinel-annotation-cdi-interceptor/src/main/resources/META-INF/beans.xml
new file mode 100644
index 0000000000..3c4866dca4
--- /dev/null
+++ b/sentinel-extension/sentinel-annotation-cdi-interceptor/src/main/resources/META-INF/beans.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/sentinel-extension/sentinel-annotation-cdi-interceptor/src/test/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/AbstractSentinelInterceptorSupportTest.java b/sentinel-extension/sentinel-annotation-cdi-interceptor/src/test/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/AbstractSentinelInterceptorSupportTest.java
new file mode 100644
index 0000000000..f416477e04
--- /dev/null
+++ b/sentinel-extension/sentinel-annotation-cdi-interceptor/src/test/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/AbstractSentinelInterceptorSupportTest.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 1999-2020 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.annotation.cdi.interceptor;
+
+import com.alibaba.csp.sentinel.annotation.cdi.interceptor.integration.service.FooService;
+import org.junit.Test;
+
+import java.lang.reflect.Method;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author sea
+ */
+public class AbstractSentinelInterceptorSupportTest extends AbstractSentinelInterceptorSupport {
+
+ @Test
+ public void testGetResourceName() throws Exception {
+ Method method = FooService.class.getMethod("random");
+ String resourceName = "someRandom";
+ String expectedResolvedName = FooService.class.getName() + ":random()";
+ assertThat(getResourceName(resourceName, method)).isEqualTo(resourceName);
+ assertThat(getResourceName(null, method)).isEqualTo(expectedResolvedName);
+ assertThat(getResourceName("", method)).isEqualTo(expectedResolvedName);
+ }
+}
diff --git a/sentinel-extension/sentinel-annotation-cdi-interceptor/src/test/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/MethodWrapperTest.java b/sentinel-extension/sentinel-annotation-cdi-interceptor/src/test/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/MethodWrapperTest.java
new file mode 100644
index 0000000000..8989fa65db
--- /dev/null
+++ b/sentinel-extension/sentinel-annotation-cdi-interceptor/src/test/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/MethodWrapperTest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 1999-2020 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.annotation.cdi.interceptor;
+
+import org.junit.Test;
+
+import java.lang.reflect.Method;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+
+/**
+ * @author Eric Zhao
+ */
+public class MethodWrapperTest {
+
+ @Test
+ public void testWrapMethod() {
+ Method method = String.class.getMethods()[0];
+ MethodWrapper m = MethodWrapper.wrap(method);
+ assertThat(m.isPresent()).isTrue();
+ assertThat(m.getMethod()).isSameAs(method);
+ }
+
+ @Test
+ public void testNone() {
+ MethodWrapper none = MethodWrapper.none();
+ assertThat(none.isPresent()).isFalse();
+ assertThat(none.getMethod()).isNull();
+ }
+}
diff --git a/sentinel-extension/sentinel-annotation-cdi-interceptor/src/test/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/ResourceMetadataRegistryTest.java b/sentinel-extension/sentinel-annotation-cdi-interceptor/src/test/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/ResourceMetadataRegistryTest.java
new file mode 100644
index 0000000000..a822517029
--- /dev/null
+++ b/sentinel-extension/sentinel-annotation-cdi-interceptor/src/test/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/ResourceMetadataRegistryTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 1999-2020 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.annotation.cdi.interceptor;
+
+import com.alibaba.csp.sentinel.annotation.cdi.interceptor.integration.service.FooService;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.lang.reflect.Method;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+
+/**
+ * @author Eric Zhao
+ */
+public class ResourceMetadataRegistryTest {
+
+ @Before
+ public void setUp() throws Exception {
+ ResourceMetadataRegistry.clearBlockHandlerMap();
+ ResourceMetadataRegistry.clearFallbackMap();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ ResourceMetadataRegistry.clearBlockHandlerMap();
+ ResourceMetadataRegistry.clearFallbackMap();
+ }
+
+ @Test
+ public void testUpdateThenLookupFallback() {
+ Class> clazz = FooService.class;
+ String methodName = "someMethodFallback";
+ Method method = clazz.getMethods()[0];
+ assertThat(ResourceMetadataRegistry.lookupFallback(clazz, methodName)).isNull();
+
+ ResourceMetadataRegistry.updateFallbackFor(clazz, methodName, null);
+ assertThat(ResourceMetadataRegistry.lookupFallback(clazz, methodName).isPresent()).isFalse();
+
+ ResourceMetadataRegistry.updateFallbackFor(clazz, methodName, method);
+ MethodWrapper wrapper = ResourceMetadataRegistry.lookupFallback(clazz, methodName);
+ assertThat(wrapper.isPresent()).isTrue();
+ assertThat(wrapper.getMethod()).isSameAs(method);
+ }
+
+ @Test
+ public void testUpdateThenLookupBlockHandler() {
+ Class> clazz = FooService.class;
+ String methodName = "someMethodBlockHand;er";
+ Method method = clazz.getMethods()[1];
+ assertThat(ResourceMetadataRegistry.lookupBlockHandler(clazz, methodName)).isNull();
+
+ ResourceMetadataRegistry.updateBlockHandlerFor(clazz, methodName, null);
+ assertThat(ResourceMetadataRegistry.lookupBlockHandler(clazz, methodName).isPresent()).isFalse();
+
+ ResourceMetadataRegistry.updateBlockHandlerFor(clazz, methodName, method);
+ MethodWrapper wrapper = ResourceMetadataRegistry.lookupBlockHandler(clazz, methodName);
+ assertThat(wrapper.isPresent()).isTrue();
+ assertThat(wrapper.getMethod()).isSameAs(method);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testUpdateBlockHandlerBadArgument() {
+ ResourceMetadataRegistry.updateBlockHandlerFor(null, "sxs", String.class.getMethods()[0]);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testUpdateFallbackBadArgument() {
+ ResourceMetadataRegistry.updateBlockHandlerFor(String.class, "", String.class.getMethods()[0]);
+ }
+}
diff --git a/sentinel-extension/sentinel-annotation-cdi-interceptor/src/test/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/integration/SentinelAnnotationInterceptorIntegrationTest.java b/sentinel-extension/sentinel-annotation-cdi-interceptor/src/test/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/integration/SentinelAnnotationInterceptorIntegrationTest.java
new file mode 100644
index 0000000000..4b4bb5273c
--- /dev/null
+++ b/sentinel-extension/sentinel-annotation-cdi-interceptor/src/test/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/integration/SentinelAnnotationInterceptorIntegrationTest.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright 1999-2020 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.annotation.cdi.interceptor.integration;
+
+import com.alibaba.csp.sentinel.annotation.cdi.interceptor.integration.service.FooService;
+import com.alibaba.csp.sentinel.annotation.cdi.interceptor.integration.service.FooUtil;
+import com.alibaba.csp.sentinel.node.ClusterNode;
+import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
+import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
+import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
+import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot;
+import com.alibaba.csp.sentinel.util.MethodUtil;
+import org.junit.*;
+
+import javax.enterprise.inject.se.SeContainer;
+import javax.enterprise.inject.se.SeContainerInitializer;
+import java.util.ArrayList;
+import java.util.Collections;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.fail;
+
+/**
+ * @author sea
+ */
+public class SentinelAnnotationInterceptorIntegrationTest {
+
+ static SeContainer container;
+
+ FooService fooService;
+
+ @BeforeClass
+ public static void init() {
+ SeContainerInitializer containerInit = SeContainerInitializer.newInstance();
+ container = containerInit.initialize();
+ }
+
+ @AfterClass
+ public static void shutdown() {
+ container.close();
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ FlowRuleManager.loadRules(new ArrayList());
+ ClusterBuilderSlot.resetClusterNodes();
+ fooService = container.select(FooService.class).get();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ FlowRuleManager.loadRules(new ArrayList());
+ ClusterBuilderSlot.resetClusterNodes();
+ }
+
+ @Test
+ public void testForeignBlockHandlerClass() throws Exception {
+ assertThat(fooService.random()).isNotEqualTo(FooUtil.BLOCK_FLAG);
+ String resourceName = MethodUtil.resolveMethodName(FooService.class.getDeclaredMethod("random"));
+ ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName);
+ assertThat(cn).isNotNull();
+ assertThat(cn.passQps()).isPositive();
+
+ FlowRuleManager.loadRules(Collections.singletonList(
+ new FlowRule(resourceName).setCount(0)
+ ));
+ assertThat(fooService.random()).isEqualTo(FooUtil.BLOCK_FLAG);
+ assertThat(cn.blockQps()).isPositive();
+ }
+
+ @Test(expected = FlowException.class)
+ public void testBlockHandlerNotFound() {
+ assertThat(fooService.baz("Sentinel")).isEqualTo("cheers, Sentinel");
+ String resourceName = "apiBaz";
+ ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName);
+ assertThat(cn).isNotNull();
+ assertThat(cn.passQps()).isPositive();
+
+ FlowRuleManager.loadRules(Collections.singletonList(
+ new FlowRule(resourceName).setCount(0)
+ ));
+ fooService.baz("Sentinel");
+ }
+
+ @Test
+ public void testAnnotationExceptionsToIgnore() {
+ assertThat(fooService.baz("Sentinel")).isEqualTo("cheers, Sentinel");
+ String resourceName = "apiBaz";
+ ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName);
+ assertThat(cn).isNotNull();
+ assertThat(cn.passQps()).isPositive();
+
+ try {
+ fooService.baz("fail");
+ fail("should not reach here");
+ } catch (IllegalMonitorStateException ex) {
+ assertThat(cn.exceptionQps()).isZero();
+ }
+ }
+
+ @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");
+ String resourceName = "apiFoo";
+ ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName);
+ assertThat(cn).isNotNull();
+ assertThat(cn.passQps()).isPositive();
+
+ // Test for biz exception.
+ try {
+ fooService.foo(5758);
+ fail("should not reach here");
+ } catch (Exception ex) {
+ // Should not be traced.
+ assertThat(cn.exceptionQps()).isZero();
+ }
+
+ try {
+ fooService.foo(5763);
+ fail("should not reach here");
+ } catch (Exception ex) {
+ assertThat(cn.exceptionQps()).isPositive();
+ }
+
+ // Test for blockHandler
+ FlowRuleManager.loadRules(Collections.singletonList(
+ new FlowRule(resourceName).setCount(0)
+ ));
+ assertThat(fooService.foo(1121)).isEqualTo("Oops, 1121");
+ assertThat(cn.blockQps()).isPositive();
+ }
+}
diff --git a/sentinel-extension/sentinel-annotation-cdi-interceptor/src/test/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/integration/service/FooService.java b/sentinel-extension/sentinel-annotation-cdi-interceptor/src/test/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/integration/service/FooService.java
new file mode 100644
index 0000000000..5f070ad63e
--- /dev/null
+++ b/sentinel-extension/sentinel-annotation-cdi-interceptor/src/test/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/integration/service/FooService.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 1999-2020 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.annotation.cdi.interceptor.integration.service;
+import com.alibaba.csp.sentinel.annotation.cdi.interceptor.SentinelResourceBinding;
+import com.alibaba.csp.sentinel.slots.block.BlockException;
+
+import javax.enterprise.context.ApplicationScoped;
+import java.util.concurrent.ThreadLocalRandom;
+
+/**
+ * @author Eric Zhao
+ * @author sea
+ */
+@ApplicationScoped
+public class FooService {
+
+ @SentinelResourceBinding(value = "apiFoo", blockHandler = "fooBlockHandler",
+ exceptionsToTrace = {IllegalArgumentException.class})
+ public String foo(int i) throws Exception {
+ if (i == 5758) {
+ throw new IllegalAccessException();
+ }
+ if (i == 5763) {
+ throw new IllegalArgumentException();
+ }
+ return "Hello for " + i;
+ }
+
+ @SentinelResourceBinding(value = "apiFooWithFallback", blockHandler = "fooBlockHandler", fallback = "fooFallbackFunc",
+ exceptionsToTrace = {IllegalArgumentException.class})
+ public String fooWithFallback(int i) throws Exception {
+ if (i == 5758) {
+ throw new IllegalAccessException();
+ }
+ if (i == 5763) {
+ throw new IllegalArgumentException();
+ }
+ return "Hello for " + i;
+ }
+
+ @SentinelResourceBinding(value = "apiAnotherFooWithDefaultFallback", defaultFallback = "globalDefaultFallback",
+ fallbackClass = {FooUtil.class})
+ public String anotherFoo(int i) {
+ if (i == 5758) {
+ throw new IllegalArgumentException("oops");
+ }
+ return "Hello for " + i;
+ }
+
+ @SentinelResourceBinding(blockHandler = "globalBlockHandler", blockHandlerClass = FooUtil.class)
+ public int random() {
+ return ThreadLocalRandom.current().nextInt(0, 30000);
+ }
+
+ @SentinelResourceBinding(value = "apiBaz", blockHandler = "bazBlockHandler",
+ exceptionsToIgnore = {IllegalMonitorStateException.class})
+ public String baz(String name) {
+ if (name.equals("fail")) {
+ throw new IllegalMonitorStateException("boom!");
+ }
+ return "cheers, " + name;
+ }
+
+ public String fooBlockHandler(int i, BlockException ex) {
+ return "Oops, " + i;
+ }
+
+ public String fooFallbackFunc(int i) {
+ return "eee...";
+ }
+}
diff --git a/sentinel-extension/sentinel-annotation-cdi-interceptor/src/test/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/integration/service/FooUtil.java b/sentinel-extension/sentinel-annotation-cdi-interceptor/src/test/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/integration/service/FooUtil.java
new file mode 100644
index 0000000000..4c43516e40
--- /dev/null
+++ b/sentinel-extension/sentinel-annotation-cdi-interceptor/src/test/java/com/alibaba/csp/sentinel/annotation/cdi/interceptor/integration/service/FooUtil.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 1999-2020 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.annotation.cdi.interceptor.integration.service;
+
+import com.alibaba.csp.sentinel.slots.block.BlockException;
+
+/**
+ * @author Eric Zhao
+ */
+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;
+ }
+}
diff --git a/sentinel-extension/sentinel-annotation-cdi-interceptor/src/test/resources/META-INF/beans.xml b/sentinel-extension/sentinel-annotation-cdi-interceptor/src/test/resources/META-INF/beans.xml
new file mode 100644
index 0000000000..e11de0cc46
--- /dev/null
+++ b/sentinel-extension/sentinel-annotation-cdi-interceptor/src/test/resources/META-INF/beans.xml
@@ -0,0 +1,8 @@
+
+
+
+ com.alibaba.csp.sentinel.annotation.cdi.interceptor.SentinelResourceInterceptor
+
+