Selaa lähdekoodia

Add Sentinel annotation definition and aspectj extension

Signed-off-by: Eric Zhao <sczyh16@gmail.com>
master
Eric Zhao 6 vuotta sitten
vanhempi
commit
348b1c819b
7 muutettua tiedostoa jossa 440 lisäystä ja 0 poistoa
  1. +53
    -0
      sentinel-core/src/main/java/com/alibaba/csp/sentinel/annotation/SentinelResource.java
  2. +2
    -0
      sentinel-extension/pom.xml
  3. +63
    -0
      sentinel-extension/sentinel-annotation-aspectj/README.md
  4. +37
    -0
      sentinel-extension/sentinel-annotation-aspectj/pom.xml
  5. +51
    -0
      sentinel-extension/sentinel-annotation-aspectj/src/main/java/com/alibaba/csp/sentinel/annotation/aspectj/MethodWrapper.java
  6. +57
    -0
      sentinel-extension/sentinel-annotation-aspectj/src/main/java/com/alibaba/csp/sentinel/annotation/aspectj/ResourceMetadataRegistry.java
  7. +177
    -0
      sentinel-extension/sentinel-annotation-aspectj/src/main/java/com/alibaba/csp/sentinel/annotation/aspectj/SentinelResourceAspect.java

+ 53
- 0
sentinel-core/src/main/java/com/alibaba/csp/sentinel/annotation/SentinelResource.java Näytä tiedosto

@@ -0,0 +1,53 @@
/*
* 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;

import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import com.alibaba.csp.sentinel.EntryType;

/**
* @author Eric Zhao
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface SentinelResource {

/**
* @return name of the Sentinel resource
*/
String value();

/**
* @return the entry type (inbound or outbound), outbound by default
*/
EntryType entryType() default EntryType.OUT;

/**
* @return name of the block exception function, empty by default
*/
String blockHandler() default "";

/**
* @return name of the fallback function, empty by default
*/
String fallback() default "";
}

+ 2
- 0
sentinel-extension/pom.xml Näytä tiedosto

@@ -15,6 +15,8 @@
<module>sentinel-datasource-extension</module>
<module>sentinel-datasource-nacos</module>
<module>sentinel-datasource-zookeeper</module>

<module>sentinel-annotation-aspectj</module>
</modules>

</project>

+ 63
- 0
sentinel-extension/sentinel-annotation-aspectj/README.md Näytä tiedosto

@@ -0,0 +1,63 @@
# Sentinel Annotation AspectJ

This extension is an AOP implementation using AspectJ for Sentinel annotations.
Currently only runtime waving 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`: Fallback method when degraded (optional). The fallback method should be located in the same class with original method. The signature of the fallback method should match the original method (parameter types and return type)
- `blockHandler`: Handler method that handles `BlockException` when blocked. The block handler method should be located in the same class with original method, and the signature should match original method, with the last additional parameter type `BlockException`.

For example:

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

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

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

## 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).

+ 37
- 0
sentinel-extension/sentinel-annotation-aspectj/pom.xml Näytä tiedosto

@@ -0,0 +1,37 @@
<?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>0.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>sentinel-annotation-aspectj</artifactId>
<packaging>jar</packaging>

<properties>
<aspectj.version>1.9.1</aspectj.version>
</properties>

<dependencies>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
</dependency>

<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${aspectj.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectj.version}</version>
</dependency>
</dependencies>

</project>

+ 51
- 0
sentinel-extension/sentinel-annotation-aspectj/src/main/java/com/alibaba/csp/sentinel/annotation/aspectj/MethodWrapper.java Näytä tiedosto

@@ -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;
}
}

+ 57
- 0
sentinel-extension/sentinel-annotation-aspectj/src/main/java/com/alibaba/csp/sentinel/annotation/aspectj/ResourceMetadataRegistry.java Näytä tiedosto

@@ -0,0 +1,57 @@
/*
* 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;

/**
* @author Eric Zhao
*/
public final class ResourceMetadataRegistry {

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

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

public static MethodWrapper lookupBlockHandler(Class<?> clazz, String name) {
return BLOCK_HANDLER_MAP.get(getKey(clazz, name));
}

public 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));
}

public 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));
}

public static String getKey(Class<?> clazz, String name) {
return String.format("%s:%s", clazz.getCanonicalName(), name);
}
}

+ 177
- 0
sentinel-extension/sentinel-annotation-aspectj/src/main/java/com/alibaba/csp/sentinel/annotation/aspectj/SentinelResourceAspect.java Näytä tiedosto

@@ -0,0 +1,177 @@
/*
* 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.Arrays;

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.context.ContextUtil;
import com.alibaba.csp.sentinel.log.RecordLog;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.util.StringUtil;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;

/**
* Aspect for methods with {@link SentinelResource} annotation.
*
* @author Eric Zhao
*/
@Aspect
public class SentinelResourceAspect {

@Pointcut("@annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)")
public void sentinelResourceAnnotationPointcut() {
}

@Around("sentinelResourceAnnotationPointcut()")
public Object invokeResourceWithSentinel(ProceedingJoinPoint pjp) throws Throwable {
Method originMethod = getMethod(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 = annotation.value();
EntryType entryType = annotation.entryType();
Entry entry = null;
try {
ContextUtil.enter(resourceName);
entry = SphU.entry(resourceName, entryType);
Object result = pjp.proceed();
return result;
} catch (BlockException ex) {
return handleBlockException(pjp, annotation, ex);
} finally {
if (entry != null) {
entry.exit();
}
ContextUtil.exit();
}
}

private Object handleBlockException(ProceedingJoinPoint pjp, SentinelResource annotation, BlockException ex)
throws Exception {
// Execute fallback for degrading if configured.
Object[] originArgs = pjp.getArgs();
if (isDegradeFailure(ex)) {
Method method = extractFallbackMethod(pjp, annotation.fallback());
if (method != null) {
return method.invoke(pjp.getTarget(), originArgs);
}
}
// Execute block handler if configured.
Method blockHandler = extractBlockHandlerMethod(pjp, annotation.blockHandler());
if (blockHandler != null) {
Object[] args = Arrays.copyOf(originArgs, originArgs.length + 1);
args[args.length - 1] = ex;
return blockHandler.invoke(pjp.getTarget(), args);
}
// If no block handler is present, then directly throw the exception.
throw ex;
}

private boolean isDegradeFailure(/*@NonNull*/ BlockException ex) {
return ex instanceof DegradeException;
}

private Method extractFallbackMethod(ProceedingJoinPoint pjp, String fallbackName) {
if (StringUtil.isBlank(fallbackName)) {
return null;
}
Class<?> clazz = pjp.getTarget().getClass();
MethodWrapper m = ResourceMetadataRegistry.lookupFallback(clazz, fallbackName);
if (m == null) {
// First time, resolve the fallback.
Method method = resolveFallbackInternal(pjp, fallbackName);
// Cache the method instance.
ResourceMetadataRegistry.updateFallbackFor(clazz, fallbackName, method);
return method;
}
if (!m.isPresent()) {
return null;
}
return m.getMethod();
}

private Method resolveFallbackInternal(ProceedingJoinPoint pjp, /*@NonNull*/ String name) {
Method originMethod = getMethod(pjp);
Class<?>[] parameterTypes = originMethod.getParameterTypes();
return findMethod(pjp.getTarget().getClass(), name, originMethod.getReturnType(), parameterTypes);
}

private Method findMethod(Class<?> clazz, String name, Class<?> returnType, Class<?>... parameterTypes) {
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
if (name.equals(method.getName()) && returnType.isAssignableFrom(method.getReturnType())
&& Arrays.equals(parameterTypes, method.getParameterTypes())) {
return method;
}
}
// Current class nou found, find in the super classes recursively.
Class<?> superClass = clazz.getSuperclass();
if (superClass != null && !Object.class.equals(superClass)) {
return findMethod(superClass, name, returnType, parameterTypes);
} else {
RecordLog.info(
String.format("[SentinelResourceAspect] Cannot find method [%s] in class [%s] with parameters %s",
name, clazz.getCanonicalName(), Arrays.toString(parameterTypes)));
return null;
}
}

private Method extractBlockHandlerMethod(ProceedingJoinPoint pjp, String name) {
if (StringUtil.isBlank(name)) {
return null;
}
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);
// 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) {
Method originMethod = getMethod(pjp);
Class<?>[] originList = originMethod.getParameterTypes();
Class<?>[] parameterTypes = Arrays.copyOf(originList, originList.length + 1);
parameterTypes[parameterTypes.length - 1] = BlockException.class;
return findMethod(pjp.getTarget().getClass(), name, originMethod.getReturnType(), parameterTypes);
}

private Method getMethod(ProceedingJoinPoint joinPoint) {
MethodSignature signature = (MethodSignature)joinPoint.getSignature();
return signature.getMethod();
}
}

Loading…
Peruuta
Tallenna