天天看點

Aspectj執行個體探究,解析SentinelResourceAspect實作

為了學習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。