2018-05-19

spring源碼分析(五)



目錄

五、源碼分析
--5.6、Spring AOP 設計原理及具體實踐
----5.6.1、SpringAOP 應用示例
------AOP 的相關概念
------通知(Advice)類型
------使用 SpringAOP 兩種方式
--------表達式中使用”execution“
--------訪問當前的連接點
--------通知參數
----5.6.2、SpringAOP 設計原理及源碼分析



五、源碼分析

5.6、Spring AOP 設計原理及具體實踐
5.6.1、SpringAOP 應用示例

AOP 是 OOP 的延續,是 AspectOrientedProgramming 的縮寫,意思是面向切面編程。可以通過預編譯方式和運行期動態代 理實現在不修改源代碼的情況下給程序動態統一添加功能的一種技術。 AOP 設計模式孜孜不倦追求的是調用者和被調用者之 間的解耦,AOP 可以說也是這種目標的一種實現。

我們現在做的一些非業務,如:日志、事務、安全等都會寫在業務代碼中(也即是說,這些非業務類橫切于業務類),但這些 代碼往往是重復,復制——粘貼式的代碼會給程序的維護帶來不便,AOP 就實現了把這些業務需求與系統需求分開來做。這 種解決的方式也稱代理機制。

AOP 的相關概念

  • 切面(Aspect):官方的抽象定義為“一個關注點的模塊化,這個關注點可能會橫切多個對象”。“切面”在 ApplicationContext 中<aop:aspect>來配置。

  • 連接點(Joinpoint):程序執行過程中的某一行為,例如,MemberService.get 的調用或者 MemberService.delete 拋出異常等行為。 ? 通知(Advice) :“切面”對于某個“連接點”所產生的動作。其中,一個“切面”可以包含多個“Advice”。

  • 切入點(Pointcut) :匹配連接點的斷言,在 AOP 中通知和一個切入點表達式關聯。切面中的所有通知所關注的連 接點,都由切入點表達式來決定。

  • 目標對象(TargetObject) :被一個或者多個切面所通知的對象。例如,AServcieImpl 和 BServiceImpl,當然在實 際運行時,SpringAOP 采用代理實現,實際 AOP 操作的是 TargetObject 的代理對象。

  • AOP 代理(AOPProxy):在 SpringAOP 中有兩種代理方式, JDK 動態代理和 CGLIB 代理。默認情況下, TargetObject 實現了接口時,則采用 JDK 動態代理,例如,AServiceImpl;反之,采用 CGLIB 代理,例如,BServiceImpl。強制 使用 CGLIB 代理需要將 <aop:config>的 proxy-target-class 屬性設為 true。

通知(Advice)類型:

  • 前置通知(Beforeadvice):在某連接點(JoinPoint)之前執行的通知,但這個通知不能阻止連接點前的執行。 ApplicationContext 中在<aop:aspect>里面使用<aop:before>元素進行聲明。例如,TestAspect 中的 doBefore 方法。

  • 后置通知(Afteradvice):當某連接點退出的時候執行的通知(不論是正常返回還是異常退出)。ApplicationContext 中在<aop:aspect>里面使用<aop:after>元素進行聲明。例如,ServiceAspect 中的 returnAfter 方法,所以 Teser 中調用 UserService.delete 拋出異常時,returnAfter 方法仍然執行。

  • 返回后通知(Afterreturnadvice):在某連接點正常完成后執行的通知,不包括拋出異常的情況。ApplicationContext 中在<aop:aspect>里面使用<after-returning>元素進行聲明。

  • 環繞通知(Aroundadvice):包圍一個連接點的通知,類似 Web 中 Servlet 規范中的 Filter 的 doFilter 方法。可以在 方法的調用前后完成自定義的行為,也可以選擇不執行。ApplicationContext 中在<aop:aspect>里面使用<aop:around> 元素進行聲明。例如,ServiceAspect 中的 around 方法。

  • 拋出異常后通知(Afterthrowingadvice):在方法拋出異常退出時執行的通知。ApplicationContext 中在<aop:aspect> 里面使用<aop:after-throwing>元素進行聲明。例如,ServiceAspect 中的 returnThrow 方法。

注:可以將多個通知應用到一個目標對象上,即可以將多個切面織入到同一目標對象。

使用 SpringAOP 可以基于兩種方式:

  • 一種是比較方便和強大的注解方式
  • 另一種則是中規中矩的 xml 配置方式。

注解,使用注解配置 SpringAOP 總體分為兩步:

第一步是在 xml 文件中聲明激活自動掃描組件功能,同時激活自動代 理功能(來測試 AOP 的注解功能):

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-2.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
    
<context:component-scan base-package="com.gupaoedu"/>
<context:annotation-config />
    
</beans>

第二步是為 Aspect 切面類添加注解:

//聲明這是一個組件
@Component
//聲明這是一個切面Bean
@Aspect
public class AnnotaionAspect {
    private final static Logger log = Logger.getLogger(AnnotaionAspect.class);
    //配置切入點,該方法無方法體,主要為方便同類中其他方法使用此處配置的切入點
    @Pointcut("execution(* com.gupaoedu.aop.service..*(..))")
    public void aspect(){ }
    /*
    * 配置前置通知,使用在方法aspect()上注冊的切入點
    * 同時接受JoinPoint切入點對象,可以沒有該參數
    */
    @Before("aspect()")
    public void before(JoinPoint joinPoint){
        log.info("before " + joinPoint);
    }
    //配置后置通知,使用在方法aspect()上注冊的切入點
    @After("aspect()")
    public void after(JoinPoint joinPoint){
        log.info("after " + joinPoint);
    }
    //配置環繞通知,使用在方法aspect()上注冊的切入點
    @Around("aspect()")
    public void around(JoinPoint joinPoint){
        long start = System.currentTimeMillis();
        try {
            ((ProceedingJoinPoint) joinPoint).proceed();
            long end = System.currentTimeMillis();
            log.info("around " + joinPoint + "\tUse time : " + (end - start) + " ms!");
        } catch (Throwable e) {
            long end = System.currentTimeMillis();
            log.info("around " + joinPoint + "\tUse time : " + (end - start) + " ms with exception : " + e.getMessage());
        }
    }
    //配置后置返回通知,使用在方法aspect()上注冊的切入點
    @AfterReturning("aspect()")
    public void afterReturn(JoinPoint joinPoint){
        log.info("afterReturn " + joinPoint);
    }
    //配置拋出異常后通知,使用在方法aspect()上注冊的切入點
    @AfterThrowing(pointcut="aspect()", throwing="ex")
    public void afterThrow(JoinPoint joinPoint, Exception ex){
        log.info("afterThrow " + joinPoint + "\t" + ex.getMessage());
    }
}

測試代碼

@ContextConfiguration(locations = {"classpath*:application-context.xml"})
@RunWith(SpringJUnit4ClassRunner.class)
public class AnnotationTester {
    @Autowired MemberService annotationService;
    @Autowired ApplicationContext app;
    @Test
    // @Ignore
    public void test(){
        System.out.println("=====這是一條華麗的分割線======");
        AnnotaionAspect aspect = app.getBean(AnnotaionAspect.class);
        System.out.println(aspect);
        annotationService.save(new Member());
        System.out.println("=====這是一條華麗的分割線======");
        try {
            annotationService.delete(1L);
        } catch (Exception e) {
            //e.printStackTrace();
        }
    }
}

控制臺輸出如下:

=====這是一條華麗的分割線======
com.gupaoedu.aop.aspect.AnnotaionAspect@6ef714a0
[INFO ] [13:04:46] com.gupaoedu.aop.aspect.AnnotaionAspect - before execution(void
com.gupaoedu.aop.service.MemberService.save(Member))
[INFO ] [13:04:46] com.gupaoedu.aop.aspect.ArgsAspect - beforeArgUser execution(void
com.gupaoedu.aop.service.MemberService.save(Member))
[INFO ] [13:04:46] com.gupaoedu.aop.aspect.AnnotaionAspect - save member method . . . [INFO ] [13:04:46] com.gupaoedu.aop.aspect.AnnotaionAspect - around execution(void
com.gupaoedu.aop.service.MemberService.save(Member)) Use time : 38 ms!
[INFO ] [13:04:46] com.gupaoedu.aop.aspect.AnnotaionAspect - after execution(void
com.gupaoedu.aop.service.MemberService.save(Member))
[INFO ] [13:04:46] com.gupaoedu.aop.aspect.AnnotaionAspect - afterReturn execution(void
com.gupaoedu.aop.service.MemberService.save(Member)) =====這是一條華麗的分割線======
[INFO ] [13:04:46] com.gupaoedu.aop.aspect.AnnotaionAspect - before execution(boolean
com.gupaoedu.aop.service.MemberService.delete(long))
[INFO ] [13:04:46] com.gupaoedu.aop.aspect.ArgsAspect - beforeArgId execution(boolean
com.gupaoedu.aop.service.MemberService.delete(long)) ID:1
[INFO ] [13:04:46] com.gupaoedu.aop.aspect.AnnotaionAspect - delete method . . . [INFO ] [13:04:46] com.gupaoedu.aop.aspect.AnnotaionAspect - around execution(boolean
com.gupaoedu.aop.service.MemberService.delete(long)) Use time : 3 ms with exception : spring aop
ThrowAdvice演示
[INFO ] [13:04:46] com.gupaoedu.aop.aspect.AnnotaionAspect - after execution(boolean
com.gupaoedu.aop.service.MemberService.delete(long))
[INFO ] [13:04:46] com.gupaoedu.aop.aspect.AnnotaionAspect - afterReturn execution(boolean
com.gupaoedu.aop.service.MemberService.delete(long))

可以看到,正如我們預期的那樣,雖然我們并沒有對 MemberService 類包括其調用方式做任何改變,但是 Spring 仍然攔截到了其中方法的調用,或許這正是 AOP 的魔力所在。

再簡單說一下 xml 配置方式,其實也一樣簡單:

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
    
    <aop:aspectj-autoproxy proxy-target-class="true"/>

    <bean id="xmlAspect" class="com.gupaoedu.aop.aspect.XmlAspect"></bean>

    <!-- AOP配置 -->
    <aop:config>
        <!-- 聲明一個切面,并注入切面Bean,相當于@Aspect -->
        <aop:aspect ref="xmlAspect">
            <!-- 配置一個切入點,相當于@Pointcut -->
            <aop:pointcut expression="execution(* com.gupaoedu.aop.service..*(..))" id="simplePointcut"/>
            <!-- 配置通知,相當于@Before、@After、@AfterReturn、@Around、@AfterThrowing -->
            <aop:before pointcut-ref="simplePointcut" method="before"/>
            <aop:after pointcut-ref="simplePointcut" method="after"/>
            <aop:after-returning pointcut-ref="simplePointcut" method="afterReturn"/>
            <aop:after-throwing pointcut-ref="simplePointcut" method="afterThrow" throwing="ex"/>
        </aop:aspect>
    </aop:config>
</beans>

個人覺得不如注解靈活和強大,你可以不同意這個觀點,但是不知道如下的代碼會不會讓你的想法有所改善:

//配置切入點,該方法無方法體,主要為方便同類中其他方法使用此處配置的切入點
@Pointcut("execution(* com.gupaoedu.aop.service..*(..))")
public void aspect(){ }

//配置前置通知,攔截返回值為cn.ysh.studio.spring.mvc.bean.User的方法
@Before("execution(com.gupaoedu.model.Member com.gupaoedu.aop.service..*(..))")
public void beforeReturnUser(JoinPoint joinPoint){
    log.info("beforeReturnUser " + joinPoint);
}

//配置前置通知,攔截參數為cn.ysh.studio.spring.mvc.bean.User的方法
@Before("execution(* com.gupaoedu.aop.service..*(com.gupaoedu.model.Member))")
public void beforeArgUser(JoinPoint joinPoint){
    log.info("beforeArgUser " + joinPoint);
}

//配置前置通知,攔截含有long類型參數的方法,并將參數值注入到當前方法的形參id中
@Before("aspect()&&args(id)")
public void beforeArgId(JoinPoint joinPoint, long id){
    log.info("beforeArgId " + joinPoint + "\tID:" + id);
}

以下是 MemberService 的代碼:

@Service
public class MemberService {
    private final static Logger log = Logger.getLogger(AnnotaionAspect.class);
    public Member get(long id){
        log.info("getMemberById method . . .");
        return new Member();
    }
    public Member get(){
        log.info("getMember method . . .");
        return new Member();
    }
    public void save(Member member){
        log.info("save member method . . .");
    }
    public boolean delete(long id) throws Exception{
        log.info("delete method . . .");
        throw new Exception("spring aop ThrowAdvice演示");
    }
}

應該說學習 Spring AOP 有兩個難點,第一點在于理解 AOP 的理念和相關概念,第二點在于靈活掌握和使用切入點表達式。

概念的理解通常不在一朝一夕,慢慢浸泡的時間長了,自然就明白了,下面我們簡單地介紹一下切入點表達式的配置規則吧。

通常情況下,表達式中使用”execution“就可以滿足大部分的要求。表達式格式如下:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?
  • modifiers-pattern:方法的操作權限
  • ret-type-pattern:返回值
  • declaring-type-pattern:方法所在的包
  • name-pattern:方法名
  • parm-pattern:參數名
  • throws-pattern:異常

其中,除 ret-type-pattern 和 name-pattern 之外,其他都是可選的。上例中,execution(com.spring.service..*(..))表示 com.spring.service 包下,返回值為任意類型;方法名任意;參數不作限制的所有方法。

通知參數

可以通過 args 來綁定參數,這樣就可以在通知(Advice)中訪問具體參數了。例如,<aop:aspect>配置如下:

<aop:config>
    <aop:aspect ref="xmlAspect">
        <aop:pointcut id="simplePointcut" expression="execution(* com.gupaoedu.aop.service..*(..)) and args(msg,..)" />
        <aop:after pointcut-ref="simplePointcut" method="after"/>
    </aop:aspect>
</aop:config>

上面的代碼 args(msg,..)是指將切入點方法上的第一個 String 類型參數添加到參數名為 msg 的通知的入參上,這樣就可以直接 使用該參數啦。

訪問當前的連接點

在上面的 Aspect 切面 Bean 中已經看到了,每個通知方法第一個參數都是 JoinPoint。其實,在 Spring 中,任何通知(Advice) 方法都可以將第一個參數定義為 org.aspectj.lang.JoinPoint 類型用以接受當前連接點對象。JoinPoint 接口提供了一系列有用 的方法, 比如 getArgs() (返回方法參數)、getThis() (返回代理對象)、getTarget() (返回目標)、getSignature() (返回 正在被通知的方法相關信息)和 toString() (打印出正在被通知的方法的有用信息)。

5.6.2、SpringAOP 設計原理及源碼分析

開始之前先上圖,看看 Spring 中主要的 AOP 組件


AOP組件.PNG

Spring 提供了兩種方式來生成代理對象: JDKProxy 和 Cglib,具體使用哪種方式生成由 AopProxyFactory 根據 AdvisedSupport 對象的配置來決定。默認的策略是如果目標類是接口,則使用 JDK 動態代理技術,否則使用 Cglib 來生成代 理。下面我們來研究一下 Spring 如何使用 JDK 來生成代理對象,具體的生成代碼放在 JdkDynamicAopProxy 這個類中,直接 上相關代碼:

/**
* <ol>
* <li>獲取代理類要實現的接口,除了 Advised 對象中配置的,還會加上 SpringProxy, Advised(opaque=false)
* <li>檢查上面得到的接口中有沒有定義 equals 或者 hashcode 的接口
* <li>調用 Proxy.newProxyInstance 創建代理對象
* </ol>
*/
public Object getProxy(ClassLoader classLoader) {
    if (logger.isDebugEnabled()) {
        logger.debug("Creating JDK dynamic proxy: target source is " +
        this.advised.getTargetSource());
    }
    Class[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised);
    findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
    return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}

那這個其實很明了,注釋上我也已經寫清楚了,不再贅述。

下面的問題是,代理對象生成了,那切面是如何織入的

我們知道 InvocationHandler 是 JDK 動態代理的核心,生成的代理對象的方法調用都會委托到 InvocationHandler.invoke() 方法。而通過 JdkDynamicAopProxy 的簽名我們可以看到這個類其實也實現了 InvocationHandler,下面我們就通過分析這個 類中實現的 invoke()方法來具體看下 Spring AOP 是如何織入切面的。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    MethodInvocation invocation;
    Object oldProxy = null;
    boolean setProxyContext = false;
    
    TargetSource targetSource = this.advised.targetSource;
    Class targetClass = null;
    Object target = null;
    
    try {
        //eqauls()方法,具目標對象未實現此方法
        if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
            // The target does not implement the equals(Object) method itself.
            return equals(args[0]);
        }
        //hashCode()方法,具目標對象未實現此方法
        if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
            // The target does not implement the hashCode() method itself.
            return hashCode();
        }
        //Advised 接口或者其父接口中定義的方法,直接反射調用,不應用通知
        if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
            method.getDeclaringClass().isAssignableFrom(Advised.class)) {
            // Service invocations on ProxyConfig with the proxy config...
            return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
        }
        Object retVal;
        if (this.advised.exposeProxy) {
            // Make invocation available if necessary.
            oldProxy = AopContext.setCurrentProxy(proxy);
            setProxyContext = true;
        }
        // May be null. Get as late as possible to minimize the time we "own" the target,
        // in case it comes from a pool.
        //獲得目標對象的類
        target = targetSource.getTarget();
        if (target != null) {
            targetClass = target.getClass();
        }
        // Get the interception chain for this method.
        //獲取可以應用到此方法上的 Interceptor 列表
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method,
        targetClass);
        
        // Check whether we have any advice. If we don t, we can fallback on direct
        // reflective invocation of the target, and avoid creating a MethodInvocation.
        //如果沒有可以應用到此方法的通知(Interceptor),此直接反射調用 method.invoke(target, args)
        
        if (chain.isEmpty()) {
            // We can skip creating a MethodInvocation: just invoke the target directly
            // Note that the final invoker must be an InvokerInterceptor so we know it does
            // nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
            retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
        }else {
            // We need to create a method invocation...
            //創建 MethodInvocation
            invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass,
            chain);
            // Proceed to the joinpoint through the interceptor chain.
            retVal = invocation.proceed();
        }
        // Massage return value if necessary.
        Class<?> returnType = method.getReturnType();
        if (retVal != null && retVal == target && returnType.isInstance(proxy) &&
        !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
            // Special case: it returned "this" and the return type of the method
            // is type-compatible. Note that we can t help if the target sets
            // a reference to itself in another returned object.
            retVal = proxy;
        } else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
            throw new AopInvocationException("Null return value from advice does not match
            primitive return type for: " + method);
        }
        return retVal;
    }finally {
        if (target != null && !targetSource.isStatic()) {
            // Must have come from TargetSource.
            targetSource.releaseTarget(target);
        }
        if (setProxyContext) {
            // Restore old proxy.
            AopContext.setCurrentProxy(oldProxy);
        }
    }
}

主流程可以簡述為:獲取可以應用到此方法上的通知鏈(Interceptor Chain),如果有,則應用通知,并執行 joinpoint; 如 果沒有,則直接反射執行 joinpoint。而這里的關鍵是通知鏈是如何獲取的以及它又是如何執行的,下面逐一分析下。

首先,從上面的代碼可以看到,通知鏈是通過Advised.getInterceptorsAndDynamicInterceptionAdvice()這個方法來獲取 的,我們來看下這個方法的實現:

public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, Class targetClass){
    MethodCacheKey cacheKey = new MethodCacheKey(method);
    List<Object> cached = this.methodCache.get(cacheKey);
    if (cached == null) {
        cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
        this, method, targetClass);
        this.methodCache.put(cacheKey, cached);
    }
    return cached;
}

可以看到實際的獲取工作其實是由 AdvisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice()這個方法來 完成的,獲取到的結果會被緩存。

下面來分析下這個方法的實現:

/**
* 從提供的配置實例 config 中獲取 advisor 列表,遍歷處理這些 advisor.如果是 IntroductionAdvisor,
* 則判斷此 Advisor 能否應用到目標類 targetClass 上.如果是 PointcutAdvisor,則判斷
* 此 Advisor 能否應用到目標方法 method 上.將滿足條件的 Advisor 通過 AdvisorAdaptor 轉化成 Interceptor
列表返回.
*/
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(
Advised config, Method method, Class targetClass) {
    // This is somewhat tricky... we have to process introductions first,
    // but we need to preserve order in the ultimate list.
    List<Object> interceptorList = new ArrayList<Object>(config.getAdvisors().length);
    //查看是否包含 IntroductionAdvisor
    boolean hasIntroductions = hasMatchingIntroductions(config, targetClass);
    //這里實際上注冊一系列 AdvisorAdapter,用于將 Advisor 轉化成 MethodInterceptor
    AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
    for (Advisor advisor : config.getAdvisors()) {
        if (advisor instanceof PointcutAdvisor) {
            // Add it conditionally.
            PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
            if (config.isPreFiltered() ||
            pointcutAdvisor.getPointcut().getClassFilter().matches(targetClass)) {
                //這個地方這兩個方法的位置可以互換下
                //將 Advisor 轉化成 Interceptor
                MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
                //檢查當前 advisor 的 pointcut 是否可以匹配當前方法
                MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
                if (MethodMatchers.matches(mm, method, targetClass, hasIntroductions)) {
                    if (mm.isRuntime()) {
                        // Creating a new object instance in the getInterceptors() method
                        // isn't a problem as we normally cache created chains.
                        for (MethodInterceptor interceptor : interceptors) {
                            interceptorList.add(new
                            InterceptorAndDynamicMethodMatcher(interceptor, mm));
                        }
                    }else {
                        interceptorList.addAll(Arrays.asList(interceptors));
                    }
                }
            }
        }else if (advisor instanceof IntroductionAdvisor) {
            IntroductionAdvisor ia = (IntroductionAdvisor) advisor;
            if (config.isPreFiltered() || ia.getClassFilter().matches(targetClass)) {
            Interceptor[] interceptors = registry.getInterceptors(advisor);
                interceptorList.addAll(Arrays.asList(interceptors));
            }
        }else {
            Interceptor[] interceptors = registry.getInterceptors(advisor);
            interceptorList.addAll(Arrays.asList(interceptors));
        }
    }
    return interceptorList;
}

這個方法執行完成后,Advised 中配置能夠應用到連接點或者目標類的 Advisor 全部被轉化成了 MethodInterceptor. 接下來我們再看下得到的攔截器鏈是怎么起作用的。

......
// Check whether we have any advice. If we don't, we can fallback on direct
// reflective invocation of the target, and avoid creating a MethodInvocation.
//如果沒有可以應用到此方法的通知(Interceptor),此直接反射調用 method.invoke(target, args)
if (chain.isEmpty()) {
    // We can skip creating a MethodInvocation: just invoke the target directly
    // Note that the final invoker must be an InvokerInterceptor so we know it does
    // nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
    retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
}
else {
    // We need to create a method invocation...
    //創建 MethodInvocation
    invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
    // Proceed to the joinpoint through the interceptor chain.
    retVal = invocation.proceed();
}
......

從這段代碼可以看出,如果得到的攔截器鏈為空,則直接反射調用目標方法,否則創建 MethodInvocation,調用其 proceed方法,觸發攔截器鏈的執行,來看下具體代碼

public Object proceed() throws Throwable {
    // We start with an index of -1 and increment early.
    //如果 Interceptor 執行完了,則執行 joinPoint
    if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
        return invokeJoinpoint();
    }
    Object interceptorOrInterceptionAdvice =
    this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
    //如果要動態匹配 joinPoint
    if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
        // Evaluate dynamic method matcher here: static part will already have
        // been evaluated and found to match.
        InterceptorAndDynamicMethodMatcher dm =
        (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
        //動態匹配:運行時參數是否滿足匹配條件
        if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
            //執行當前 Intercetpor
            return dm.interceptor.invoke(this);
        }else {
            // Dynamic matching failed.
            // Skip this interceptor and invoke the next in the chain.
            //動態匹配失敗時,略過當前 Intercetpor,調用下一個 Interceptor
            return proceed();
        }
    }else {
        // It's an interceptor, so we just invoke it: The pointcut will have
        // been evaluated statically before this object was constructed.
        //執行當前 Intercetpor
        return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,345評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,494評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,283評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,953評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,714評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,410評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,940評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,776評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,210評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,654評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373