為了學習SentinelResourceAspect,這篇文章里我用Aspectj實現一個AOP實例,一起來看下。
Sentinel 提供了 @SentinelResource 注解用於定義資源,支持 AspectJ 的擴展用於自動定義資源、處理 BlockException 等。
SentinelResourceAspect是Sentinel中的核心切面,Sentinel對限流,攔截等的支持都依賴 SentinelResourceAspect,本文回顧AOP相關知識,實現一個AspectJ實例,然后帶你從源碼角度,探究SentinelResourceAspect的實現。
1、回顧 Spring AOP 知識
AOP為Aspect Oriented Programming的縮寫,意為:面向切面編程,通過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術。
利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發的效率。
常見使用場景
- 性能監控
在方法調用前后記錄調用時間,方法執行太長或超時報警。
- 緩存代理
緩存某方法的返回值,下次執行該方法時,直接從緩存里獲取。
- 軟件破解
使用AOP修改軟件的驗證類的判斷邏輯。
- 記錄日志
在方法執行前后記錄系統日志。
- 工作流系統
工作流系統需要將業務代碼和流程引擎代碼混合在一起執行,那么我們可以使用AOP將其分離,並動態掛接業務。
- 權限驗證
方法執行前驗證是否有權限執行當前方法,沒有則拋出沒有權限執行異常,由業務代碼捕捉。
AOP的一些概念
-
Aspect Aspect : 聲明類似於 Java 中的類聲明,在 Aspect 中會包含着一些 Pointcut 以及相應的 Advice。
-
Joint point : 攔截點,如某個業務方法, 表示在程序中明確定義的點,典型的包括方法調用,對類成員的訪問以及異常處理程序塊的執行等等,它自身還可以嵌套其它 joint point。
-
Pointcut : 表示一組 joint point,這些 joint point 或是通過邏輯關系組合起來,或是通過通配、正則表達式等方式集中起來,即Joinpoint的表達式,表示攔截哪些方法。一個Pointcut對應多個Joinpoint
-
Advice Advice : 定義了在 pointcut 里面定義的程序點具體要做的操作,即要切入的邏輯,它通過 before、after 和 around 來區別是在每個 joint point 之前、之后還是代替執行的代碼。
-
Target : 被aspectj橫切的對象。我們所說的joinPoint就是Target的某一行,如方法開始執行的地方、方法類調用某個其他方法的代碼
在Spring 中,AOP有多種實現方式,AspectJ是其中一種,另外還有JDK 和 Cglig的動態代理等。
2、基於 AspectJ 和@annotation攔截方法實現AOP
在學習 SentinelResourceAspect 源碼之前,我先動手實現一個 AspectJ 的AOP實例,完成這個實例以后,SentinelResourceAspect的原理只要看一眼源碼就可以明白。
(1)編寫Annotation類
@Target({ElementType.METHOD,ElementType.TYPE})
@Documented
@Inherited
public @interface WorkflowLogAnnotation {
String field();
}
(2)編寫接口和實現類
編寫接口類WorkflowService,編寫實現類WorkflowServiceImpl,注意此處在方法上增加WorkflowLogAnnotation
public class WorkflowServiceImpl implements WorkflowService {
@Override
@WorkflowLogAnnotation
public void start(String bpmnXml) {
System.out.println("啟動流程");
}
}
(3)加入切面動作
在流程啟動前和啟動后做一些操作,增加通知,注意Pointcut表達式改為攔截器方式。
@Aspect
public class WorkflowServiceAdviceAspect {
public WorkflowServiceAdviceAspect(){
}
@Pointcut("annotation(Spring.WorkflowLogAnnotation)")
public void startPoint()
@Before("startPoint()")
public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
System.out.println("執行方法前");
}
@AfterReturning("startPoint()")
public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable {
System.out.println("執行方法后");
}
}
(4)增加配置
在Xml中增加配置,如果WorkflowServiceAdviceAspect和WorkflowServiceImpl類上增加了@Component,下面的bean聲明可以用Spring的自動掃描來替代。
<aop:aspectj-autoproxy />
<!-- 定義通知內容,也就是切入點執行前后需要做的事情 -->
<bean id="workflowServiceAdviceAspect" class="Spring.WorkflowServiceAdviceAspect"></bean>
<!-- 定義被代理者 -->
<bean id="workflowService" class="business.WorkflowServiceImpl"></bean>
(5)完成驗證
完成上面的操作,一個 AspectJ 實例就完成了,是不是很簡單,下面看下 SentinelResourceAspect的源碼。
3、SentinelResourceAspect源碼
可以看到,SentinelResourceAspect切面和我們上面的實例實現方式是一樣的。
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();
Entry entry = null;
try {
entry = SphU.entry(resourceName, entryType, 1, pjp.getArgs());
Object result = pjp.proceed();
return result;
} 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, annotation);
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());
}
}
}
}
(1)進入方法調用SphU.entry
SentinelResourceAspect 使用aspect的around攔截,攔截標注有SentinelResource的注解,進入方法之前調用SphU.entry(resourceName, entryType),結束之后調用entry.exit();
entry = SphU.entry(resourceName, entryType, 1, pjp.getArgs());
Object result = pjp.proceed();
這個使用方式和我們單機使用 Sentinel的方式是一樣的。
(2)handleBlockException處理異常
異常的時候調用handleBlockException方法,會先判斷是否是降級需要處理的異常,是的話,則調用fallback方法,否則調用block handler方法。
Method blockHandlerMethod = extractBlockHandlerMethod(pjp, annotation.blockHandler(),
annotation.blockHandlerClass());
if (blockHandlerMethod != null) {
Object[] originArgs = pjp.getArgs();
// Construct args.
Object[] args = Arrays.copyOf(originArgs, originArgs.length + 1);
args[args.length - 1] = ex;
if (isStatic(blockHandlerMethod)) {
return blockHandlerMethod.invoke(null, args);
}
return blockHandlerMethod.invoke(pjp.getTarget(), args);
}
// If no block handler is present, then go to fallback.
return handleFallback(pjp, annotation, ex);
4、總結
Sentinel 使用了Aspectj來實現切面,可以更方便的應用Sentinel。