This commit is contained in:
shuhongfan
2023-09-04 16:40:17 +08:00
commit cf5ac25c14
8267 changed files with 1305066 additions and 0 deletions

View File

@@ -0,0 +1,69 @@
# Sentinel Annotation AspectJ
This extension is an AOP implementation using AspectJ for Sentinel annotations.
Currently only runtime weaving is supported.
## Annotation
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
@SentinelResource(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
### AspectJ
If you are using AspectJ directly, you can add the Sentinel annotation aspect to
your `aop.xml`:
```xml
<aspects>
<aspect name="com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect"/>
</aspects>
```
### Spring AOP
If you are using Spring AOP, you should add a configuration to register the aspect
as a Spring bean:
```java
@Configuration
public class SentinelAspectConfiguration {
@Bean
public SentinelResourceAspect sentinelResourceAspect() {
return new SentinelResourceAspect();
}
}
```
An example for using Sentinel Annotation AspectJ with Spring Boot can be found [here](https://github.com/alibaba/Sentinel/tree/master/sentinel-demo/sentinel-demo-annotation-spring-aop).

View File

@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>sentinel-extension</artifactId>
<groupId>com.alibaba.csp</groupId>
<version>1.8.3</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>sentinel-annotation-aspectj</artifactId>
<packaging>jar</packaging>
<properties>
<aspectj.version>1.9.2</aspectj.version>
<spring.test.version>5.1.5.RELEASE</spring.test.version>
</properties>
<dependencies>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectj.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.test.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.test.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.test.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,351 @@
/*
* 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.aspectj;
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.util.MethodUtil;
import com.alibaba.csp.sentinel.util.StringUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
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
* @author zhaoyuguang
*/
public abstract class AbstractSentinelAspectSupport {
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 && 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(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;
}
return invoke(pjp, fallbackMethod, args);
}
// If fallback is absent, we'll try the defaultFallback if provided.
return handleDefaultFallback(pjp, defaultFallback, fallbackClass, ex);
}
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};
return invoke(pjp, fallbackMethod, args);
}
// If no any fallback is present, then directly throw the exception.
throw ex;
}
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;
return invoke(pjp, blockHandlerMethod, args);
}
// If no block handler is present, then go to fallback.
return handleFallback(pjp, annotation, ex);
}
private Object invoke(ProceedingJoinPoint pjp, Method method, Object[] args) throws Throwable {
try {
if (!method.isAccessible()) {
makeAccessible(method);
}
if (isStatic(method)) {
return method.invoke(null, args);
}
return method.invoke(pjp.getTarget(), args);
} catch (InvocationTargetException e) {
// throw the actual exception
throw e.getTargetException();
}
}
/**
* Make the given method accessible, explicitly setting it accessible if
* necessary. The {@code setAccessible(true)} method is only called
* when actually necessary, to avoid unnecessary conflicts with a JVM
* SecurityManager (if active).
* @param method the method to make accessible
* @see java.lang.reflect.Method#setAccessible
*/
private static void makeAccessible(Method method) {
boolean isNotPublic = !Modifier.isPublic(method.getModifiers()) ||
!Modifier.isPublic(method.getDeclaringClass().getModifiers());
if (isNotPublic && !method.isAccessible()) {
method.setAccessible(true);
}
}
private Method extractFallbackMethod(ProceedingJoinPoint pjp, String fallbackName, Class<?>[] locationClass) {
if (StringUtil.isBlank(fallbackName)) {
return null;
}
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, clazz, mustStatic);
// Cache the method instance.
ResourceMetadataRegistry.updateFallbackFor(clazz, fallbackName, method);
return method;
}
if (!m.isPresent()) {
return null;
}
return m.getMethod();
}
private Method extractDefaultFallbackMethod(ProceedingJoinPoint pjp, String defaultFallback,
Class<?>[] locationClass) {
if (StringUtil.isBlank(defaultFallback)) {
SentinelResource annotationClass = pjp.getTarget().getClass().getAnnotation(SentinelResource.class);
if (annotationClass != null && StringUtil.isNotBlank(annotationClass.defaultFallback())) {
defaultFallback = annotationClass.defaultFallback();
if (locationClass == null || locationClass.length < 1) {
locationClass = annotationClass.fallbackClass();
}
} else {
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);
// 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) {
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 = pjp.getTarget().getClass();
}
MethodWrapper m = ResourceMetadataRegistry.lookupBlockHandler(clazz, name);
if (m == null) {
// First time, resolve the block handler.
Method method = resolveBlockHandlerInternal(pjp, 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(ProceedingJoinPoint pjp, /*@NonNull*/ String name, Class<?> clazz,
boolean mustStatic) {
Method originMethod = resolveMethod(pjp);
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(ProceedingJoinPoint joinPoint) {
MethodSignature signature = (MethodSignature)joinPoint.getSignature();
Class<?> targetClass = joinPoint.getTarget().getClass();
Method method = getDeclaredMethodFor(targetClass, signature.getName(),
signature.getMethod().getParameterTypes());
if (method == null) {
throw new IllegalStateException("Cannot resolve target method: " + signature.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;
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright 1999-2018 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.aspectj;
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;
}
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright 1999-2018 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.aspectj;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.alibaba.csp.sentinel.util.StringUtil;
/**
* Registry for resource configuration metadata (e.g. fallback method)
*
* @author Eric Zhao
*/
final class ResourceMetadataRegistry {
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));
}
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();
}
}

View File

@@ -0,0 +1,79 @@
/*
* Copyright 1999-2018 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.aspectj;
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import java.lang.reflect.Method;
/**
* Aspect for methods with {@link SentinelResource} annotation.
*
* @author Eric Zhao
*/
@Aspect
public class SentinelResourceAspect extends AbstractSentinelAspectSupport {
@Pointcut("@annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)")
public void sentinelResourceAnnotationPointcut() {
}
@Around("sentinelResourceAnnotationPointcut()")
public Object invokeResourceWithSentinel(ProceedingJoinPoint pjp) throws Throwable {
Method originMethod = resolveMethod(pjp);
SentinelResource annotation = originMethod.getAnnotation(SentinelResource.class);
if (annotation == null) {
// Should not go through here.
throw new IllegalStateException("Wrong state for SentinelResource annotation");
}
String resourceName = getResourceName(annotation.value(), originMethod);
EntryType entryType = annotation.entryType();
int resourceType = annotation.resourceType();
Entry entry = null;
try {
entry = SphU.entry(resourceName, resourceType, entryType, pjp.getArgs());
return pjp.proceed();
} catch (BlockException ex) {
return handleBlockException(pjp, 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(pjp, annotation, ex);
}
// No fallback function can handle the exception, so throw it out.
throw ex;
} finally {
if (entry != null) {
entry.exit(1, pjp.getArgs());
}
}
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright 1999-2018 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.aspectj;
import com.alibaba.csp.sentinel.annotation.aspectj.integration.service.FooService;
import org.junit.Test;
import java.lang.reflect.Method;
import static org.assertj.core.api.Assertions.*;
/**
* @author Eric Zhao
*/
public class AbstractSentinelAspectSupportTest extends AbstractSentinelAspectSupport {
@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);
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 1999-2018 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.aspectj;
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();
}
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright 1999-2018 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.aspectj;
import com.alibaba.csp.sentinel.annotation.aspectj.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]);
}
}

View File

@@ -0,0 +1,244 @@
/*
* 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.aspectj.integration;
import com.alibaba.csp.sentinel.annotation.aspectj.integration.config.AopTestConfig;
import com.alibaba.csp.sentinel.annotation.aspectj.integration.service.BarService;
import com.alibaba.csp.sentinel.annotation.aspectj.integration.service.FooService;
import com.alibaba.csp.sentinel.annotation.aspectj.integration.service.FooUtil;
import com.alibaba.csp.sentinel.node.ClusterNode;
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.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.ArrayList;
import java.util.Collections;
import static org.assertj.core.api.Assertions.*;
/**
* Integration test for Sentinel annotation AspectJ extension.
*
* @author Eric Zhao
* @author zhaoyuguang
*/
@ContextConfiguration(classes = {SentinelAnnotationIntegrationTest.class, AopTestConfig.class})
public class SentinelAnnotationIntegrationTest extends AbstractJUnit4SpringContextTests {
@Autowired
private FooService fooService;
@Autowired
private BarService barService;
@Test
public void testProxySuccessful() {
assertThat(AopUtils.isAopProxy(fooService)).isTrue();
assertThat(AopUtils.isCglibProxy(fooService)).isTrue();
}
@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 = UndeclaredThrowableException.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();
}
@Test
public void testClassLevelDefaultFallbackWithSingleParam() {
assertThat(barService.anotherBar(1)).isEqualTo("Hello for 1");
String resourceName = "apiAnotherBarWithDefaultFallback";
ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName);
assertThat(cn).isNotNull();
assertThat(cn.passQps()).isPositive();
assertThat(barService.doSomething(1)).isEqualTo("do something");
String resourceName1 = "com.alibaba.csp.sentinel.annotation.aspectj.integration.service.BarService:doSomething(int)";
ClusterNode cn1 = ClusterBuilderSlot.getClusterNode(resourceName1);
assertThat(cn1).isNotNull();
assertThat(cn1.passQps()).isPositive();
assertThat(barService.anotherBar(5758)).isEqualTo("eee...");
assertThat(cn.exceptionQps()).isPositive();
assertThat(cn.blockQps()).isZero();
assertThat(barService.doSomething(5758)).isEqualTo("GlobalFallback:doFallback");
assertThat(cn1.exceptionQps()).isPositive();
assertThat(cn1.blockQps()).isZero();
}
@Test
public void testFallBackPrivateMethod() throws Exception {
String resourceName = "apiFooWithFallback";
ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName);
try {
fooService.fooWithPrivateFallback(5758);
fail("should not reach here");
} catch (Exception ex) {
// Should not be traced.
assertThat(cn.exceptionQps()).isZero();
}
assertThat(fooService.fooWithPrivateFallback(5763)).isEqualTo("EEE...");
// Test for blockHandler
FlowRuleManager.loadRules(Collections.singletonList(
new FlowRule(resourceName).setCount(0)
));
assertThat(fooService.fooWithPrivateFallback(2221)).isEqualTo("Oops, 2221");
}
@Before
public void setUp() throws Exception {
FlowRuleManager.loadRules(new ArrayList<FlowRule>());
ClusterBuilderSlot.resetClusterNodes();
}
@After
public void tearDown() throws Exception {
FlowRuleManager.loadRules(new ArrayList<FlowRule>());
ClusterBuilderSlot.resetClusterNodes();
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 1999-2018 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.aspectj.integration.config;
import com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
* @author Eric Zhao
*/
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ComponentScan("com.alibaba.csp.sentinel.annotation.aspectj.integration")
public class AopTestConfig {
@Bean
public SentinelResourceAspect sentinelResourceAspect() {
return new SentinelResourceAspect();
}
}

View File

@@ -0,0 +1,48 @@
/*
* 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.aspectj.integration.service;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import org.springframework.stereotype.Service;
/**
* @author zhaoyuguang
*/
@Service
@SentinelResource(defaultFallback = "doFallback", fallbackClass = GlobalFallback.class)
public class BarService {
@SentinelResource(value = "apiAnotherBarWithDefaultFallback", defaultFallback = "fallbackFunc")
public String anotherBar(int i) {
if (i == 5758) {
throw new IllegalArgumentException("oops");
}
return "Hello for " + i;
}
@SentinelResource()
public String doSomething(int i) {
if (i == 5758) {
throw new IllegalArgumentException("oops");
}
return "do something";
}
public String fallbackFunc(Throwable t) {
System.out.println(t.getMessage());
return "eee...";
}
}

View File

@@ -0,0 +1,106 @@
/*
* Copyright 1999-2018 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.aspectj.integration.service;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import org.springframework.stereotype.Service;
import java.util.concurrent.ThreadLocalRandom;
/**
* @author Eric Zhao
*/
@Service
public class FooService {
@SentinelResource(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;
}
@SentinelResource(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;
}
@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);
}
@SentinelResource(value = "apiBaz", blockHandler = "bazBlockHandler",
exceptionsToIgnore = {IllegalMonitorStateException.class})
public String baz(String name) {
if (name.equals("fail")) {
throw new IllegalMonitorStateException("boom!");
}
return "cheers, " + name;
}
@SentinelResource(value = "apiFooWithFallback", blockHandler = "fooBlockHandlerPrivate", fallback = "fooFallbackFuncPrivate",
exceptionsToTrace = {IllegalArgumentException.class})
public String fooWithPrivateFallback(int i) throws Exception {
if (i == 5758) {
throw new IllegalAccessException();
}
if (i == 5763) {
throw new IllegalArgumentException();
}
return "Hello for " + i;
}
public String fooBlockHandler(int i, BlockException ex) {
return "Oops, " + i;
}
public String fooFallbackFunc(int i) {
return "eee...";
}
private String fooFallbackFuncPrivate(int i) {
return "EEE...";
}
private String fooBlockHandlerPrivate(int i, BlockException ex) {
return "Oops, " + i;
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 1999-2018 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.aspectj.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;
}
}

View File

@@ -0,0 +1,26 @@
/*
* 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.aspectj.integration.service;
/**
* @author zhaoyuguang
*/
public class GlobalFallback {
public static String doFallback(Throwable t) {
return "GlobalFallback:doFallback";
}
}