Spring 高級編程(第五版)-第五章-關于Spring的AOP-讀書筆記

第五章 Spring AOP


概覽

本章首先是脫離Spring之外講了AOP的基礎,其次分析了AOP的兩種類型:靜態AOP動態AOP,同時還有講述了AspectJ以及Spring AOP中的代理:JDK動態代理CGLIB代理以及切入點的高級使用以及AOP的框架服務。


問題

首先還是回憶一下迄今為止所了解的AOP并整理成一句話或者一段話在心里描述出來:Spring AOP 的實際作用類似于攔截器但于攔截器又不太相同,SpringAOP可以指定類以及某個方法并且提供了前置,后置以及環繞等切面,通常作用于日志,事務等需要橫向處理的地方,同時在AOP中涉及到兩種代理及JDK的動態代理和CGLIB代理。

對于本章的期望如下:

1.JDK動態代理和CGLIB代理的區別,以及在Spring中兩者是如何選擇的。

2.AOP的兩種類型到底有什么不同,體現在什么地方?如何選擇

3.AOP的框架服務具體是指的什么?如何操作?

字典

在這一章節中有許多AOP的基礎概念需要了解,首先達成領域術語上的一致(以下摘抄原文):

  • 連接點:連接點是應用程序執行期間明確定義的一個點,連接點的典型示例包括方法調用、方法調用本身、類初始化和對象實例化。連接點是AOP的核心概念,并且定義了在應用程序中可以使用AOP插入其他邏輯的點。

  • 通知:在特定連接點執行的代碼就是通知,它是由類中的方法定義的。有許多類型的通知,比如前置通知(在連接點之前執行)和后置通知(在連接點之后執行),還有環繞通知

  • 切入點:切入點是用于定義何時執行通知的連接點集合。通過創建切入點,可以更細致地控制如何將通知應用于應用程序的組件中,如前所述,典型的連接點是方法調用,或是特定類型(這里泛指特定的類)中所有方法調用的集合,通常情況下,可以在復雜的關系中插入切入點,從而進一步限制執行通知的時間。

  • 切面*:切面是封裝在類中的通知和切入點的組合,這種組合定義了應該包含在應用程序中的邏輯及其應該執行的位置。

  • 織入:織入是在適當的位置將切面插入到應用程序代碼中的過程,對于編譯時AOP解決方案,織入過程通常在生成時完成。同樣,對于運行時AOP的解決方案,織入過程在運行時動態執行,此外,AspectJ還支持另一種稱為加載時織入(LTW)的織入機制,在該機制中,攔截底層的JVM類加載器,并在類加載器加載字節碼時向其提供織入功能。

  • 目標對象:執行流由AOP進程修改的對象被稱為目標對象,通常也會看到目標對象被稱為通知(advice)對象。

  • 引入:這是通過引入其他方法或字段來修改對象結構的過程,可以通過引入AOP來使任何對象實現特定的接口,而無須對象的類顯式地實現該接口。


AOP Hello World

首先我們來看一段簡單的AOP代碼,以此來回顧一下AOP的具體實現:


package com.liuningyun.demo.chapter5;

public class Agent {

    public void say(){

        System.out.println("hello world !");

    }

}


package com.liuningyun.demo.chapter5;

import org.aopalliance.intercept.MethodInterceptor;

import org.aopalliance.intercept.MethodInvocation;

public class AgentDecorator implements MethodInterceptor {

    @Override

    public Object invoke(MethodInvocation methodInvocation) throws Throwable {

        System.out.println("invoke "+methodInvocation.getMethod());

        Object proceed = methodInvocation.proceed();

        System.out.println(" End!");

        return proceed;

    }

}


package com.liuningyun.demo;

import com.liuningyun.demo.chapter5.Agent;

import com.liuningyun.demo.chapter5.AgentDecorator;

import org.springframework.aop.framework.ProxyFactory;

public class Demo {

    public static void main(String[] args) {

        Agent agent = new Agent();// 目標對象

        AgentDecorator agentDecorator = new AgentDecorator(); // 通知

        ProxyFactory proxyFactory = new ProxyFactory(); //代理類

        //設置通知,在此處將AgentDecorator通知傳遞給ProxyFactory

        proxyFactory.addAdvice(agentDecorator);

        //設置目標對象,即需要被織入的對象

        proxyFactory.setTarget(agent);

        Agent proxy = (Agent) proxyFactory.getProxy(); //獲取目標對象的代理類

        proxy.say(); //調用代理對象的方法

    }

}

如上,一個簡單的Agent對象和AgentDecorator對象,其中AgentDecorator實現了一個MethodInterceptor接口,關于MethodInterceptor接口,我們不如來看看Spring的源碼:


package org.aopalliance.intercept;

/**

 * Intercepts calls on an interface on its way to the target. These

 * are nested "on top" of the target.

 *

 * <p>The user should implement the {@link #invoke(MethodInvocation)}

 * method to modify the original behavior. E.g. the following class

 * implements a tracing interceptor (traces all the calls on the

 * intercepted method(s)):

 *

 * <pre class=code>

 * class TracingInterceptor implements MethodInterceptor {

 * Object invoke(MethodInvocation i) throws Throwable {

 * System.out.println("method "+i.getMethod()+" is called on "+

 * i.getThis()+" with args "+i.getArguments());

 * Object ret=i.proceed();

 * System.out.println("method "+i.getMethod()+" returns "+ret);

 * return ret;

 * }

 * }

 * </pre>

 *

 * @author Rod Johnson

 */

@FunctionalInterface

public interface MethodInterceptor extends Interceptor {

    Object invoke(MethodInvocation invocation) throws Throwable;

}

從源碼的注釋中我們可以得知該接口的實現可以在目標對象方法調用的前后進行通知,這是一個環繞通知的接口,同時我們不應該忽略一個東西,就是該接口的package并不是來至于Spring,經過一番查閱得知AOP ALLIANCE為AOP實現定義了一組標準的接口,這個包來至于一個叫AOP聯盟的組織,具體我們不深究,有興趣可以去以下地址探索:http://aopalliance.sourceforge.net/ ,在這里我們只需要意識到這么一個東西即可,這說明MethodInterceptor是一個標準的Aop Alliance接口,對于上面main方法的輸出如下:


invoke public void com.liuningyun.demo.chapter5.Agent.say()

hello world !

 End!

可以看到在say()方法輸出helloWorld!的前后我們成功的織入了自己想要的東西,完全符合期望,但是此刻我們似乎應該意識到另外一個問題,即認知中JDK動態代理應該是只能代理接口,而代理類應該需要CGLIB代理才符合預期,在上面的代碼中似乎沒有任何設置用來選擇使用哪一種代理方式,帶著這個疑問我進行了進一步的源碼跟蹤,結果如下:

首先從ProxyFactory開始,從下圖我們可以看到ProxyFactory其實繼承了一個叫做ProxyCreatorSupport的類:

ProxyFactory.png

此時我們再到ProxyCreatorSupport源碼中去看看:


public class ProxyCreatorSupport extends AdvisedSupport {

    private AopProxyFactory aopProxyFactory;

    private List<AdvisedSupportListener> listeners = new LinkedList();

    private boolean active = false;

    public ProxyCreatorSupport() {

        this.aopProxyFactory = new DefaultAopProxyFactory();

    }

    public AopProxyFactory getAopProxyFactory() {

        return this.aopProxyFactory;

    }

    protected final synchronized AopProxy createAopProxy() {

        if (!this.active) {

            this.activate();

        }

        return this.getAopProxyFactory().createAopProxy(this);

    }

 ....省略部分方法

}

我們可以看到有趣的一個地方在于ProxyCreatorSupport的無參構造器里面初始化了一個DefaultAopProxyFactory的默認AOP代理工廠,這時我們再來看看ProxyFactory類:


public class ProxyFactory extends ProxyCreatorSupport {

    public ProxyFactory() {

    }

    public Object getProxy() {

        return this.createAopProxy().getProxy();

    }

  ....省略部分代碼

}

ProxyFactory中,我們可以看到實際我們調用getProxy()方法的時候是調用了父類中的createAopProxy()方法去獲取代理的,從父類中我們又發現其實它默認是從父類中DefaultAopProxyFactory代理工廠去獲取的代理,那么我們最后看看這個代理工廠的源碼:


public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {

    @Override

    public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {

        if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {

            Class<?> targetClass = config.getTargetClass();

            if (targetClass == null) {

                throw new AopConfigException("TargetSource cannot determine target class: " +

                        "Either an interface or a target is required for proxy creation.");

            }

            if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {

                return new JdkDynamicAopProxy(config);

            }

            return new ObjenesisCglibAopProxy(config);

        }

        else {

            return new JdkDynamicAopProxy(config);

        }

    }

    /**

     * Determine whether the supplied {@link AdvisedSupport} has only the

     * {@link org.springframework.aop.SpringProxy} interface specified

     * (or no proxy interfaces specified at all).

     */

    private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {

        Class<?>[] ifcs = config.getProxiedInterfaces();

        return (ifcs.length == 0 || (ifcs.length == 1 && SpringProxy.class.isAssignableFrom(ifcs[0])));

    }

}

在這個類中我們似乎可以看到一點熟悉的東西了,在工廠中出現了兩個看起來并不陌生的東西ObjenesisCglibAopProxyJdkDynamicAopProxy,我們可以看到其實在最后獲取代理的時候Spring根據代理的目標對象的一些屬性來自動選擇了是使用JDK動態代理還是CGLIB代理,從上面代碼中我們可以看到是否有機會走到new ObjenesisCglibAopProxy() ** 實際上是由isOptimizeisProxyTargetClasshasNoUserSuppliedProxyInterfaces方法來決定的(手動設置isOptimizeisProxyTargetClass可以強制改變其選擇分支),其中isOptimizeisProxyTargetClass默認是返回false,那么我們只需要關注hasNoUserSuppliedProxyInterfaces方法,從該方法的源碼中我們其實可以知道這個方法的作用是用來判斷目標對象是否有實現接口,如果沒有實現接口則ifcs.length == 0 為true,否則將調用一個native方法進行類判斷,isAssignableFrom的作用在于判斷所實現的接口是否是SpringProxy.class或者其子類,所以實際上ProxyFactory將代理創建過程委托給了DefaultAopProxyFactory的一個實例,該實例又轉而委托給了ObjenesisCglibAopProxyJdkDynamicAopProxy**。


  從上面的一個簡單示例其實我們可以看出來Spring其實幫我們進行了AOP代理的選擇并簡單回答了我們閱讀本章所期望了解的第一個問題:**JDK動態代理**和**CGLIB代理**的區別,JDK動態代理只支持代理接口,而CGLIB代理則沒有這些限制,同時我們可以通過來設置**isOptimize**或**isProxyTargetClass**為true來強制使用CGLIB代理,但是實際上Spring更期望的是我們使用JDK動態代理,因為盡管我們設置了**isOptimize**或**isProxyTargetClass**為true,Spring依然會檢查代理目標類是否是接口并路由到**JDK動態代理**上,另外需要注意的是,SpringAOP中唯一的限制就是無法通知**final**類,因為**final**類無法覆蓋方法。

Spring AOP所支持的通知(Advice)

在Spring中所有通知都實現了Advice接口,下面將會列出Spring所支持的六種通知(以下摘自原文):

  • 前置通知(MethodBeforeAdvice):通過使用前置通知,可以在連接點執行之前完成自定義處理,在Spring中的連接點通常就是方法調用,所以允許方法執行前執行預處理,前置通知可以完全訪問方法調用的目標以及傳遞給方法的參數,但卻無法控制方法本身的執行,另外:如果前置通知拋出異常,那么攔截器鏈(以及目標方法)的進一步執行將被中止,并且異常將回傳攔截器鏈

  • 后置返回通知(AfterReturningAdvice):在連接點的方法調用完成執行并返回一個值后執行后置返回通知,后置返回通知可以訪問方法調用的目標,傳遞給方法的參數以及返回值。由于方法在調用后置返回通知時已經執行,因此根本無法控制方法的調用,如果目標方法拋出異常,則不會運行后置返回通知,并且異常將照樣傳回調用堆棧

  • 后置通知(AfterAdvice):僅當被通知方法正常完成時才執行后置通知,然而無論被通知方法的結果如何,后置通知都會被執行,即使被通知方法失敗并拋出了異常,后置通知也會執行。

  • 環繞通知(MethodInterceptor):在Spring中,環繞通知使用方法攔截器的AOP Alliance標準進行建模,環繞通知允許在方法調用之前和之后執行,并且可以控制允許進行方法調用的點。

  • 異常通知(ThrowsAdvice):異常通知在方法調用返回后執行,但只有在該調用拋出異常時才執行,異常通知只能捕獲特定的異常,如果使用異常通知,則可以訪問拋出異常的方法,傳遞給調用的參數以及調用的目標。

  • 引入通知(IntroductionInterceptor):Spring將引入建模為特殊的攔截器,通過使用引入攔截器,可以指定由引入通知引入的方法的實現。

在這里只需要了解目前Spring所支持的幾種通知類型即可,根據實際業務場景再深入。


Pointcut 接口

上面的demo中我們給ProxyFactory設置了adivce以及targe,同時我們應該注意到我們代理了整個目標對象,但是在實際使用中不是很建議這種做法,通常我們會使用一個切入點來告知Spring,我們具體需要在哪些場景下以及哪些方法進行通知,在Spring中有如下一個接口:


public interface Pointcut {

    ClassFilter getClassFilter();

    MethodMatcher getMethodMatcher();

    Pointcut TRUE = TruePointcut.INSTANCE;

}

該接口標識了一個切入點和一個默認的Pointcut實例,并且提供了兩個方法,分別返回一個ClassFilter實例和MethodMatcher實例:


public interface ClassFilter {

    /**

     * Should the pointcut apply to the given interface or target class?

     * @param clazz the candidate target class

     * @return whether the advice should apply to the given target class

     */

    boolean matches(Class<?> clazz);

    ClassFilter TRUE = TrueClassFilter.INSTANCE;

}

可以看到在ClassFilter實例中存在一個matches(Class<?> clazz) 方法,通過注釋我們可以知道該方法是用來判定 Pointcut接口是否適用于該方法的類,入參表示需要檢查的類,如果切入點適用于該方法的類則返回true,否則返回false。


public interface MethodMatcher {

    boolean matches(Method method, @Nullable Class<?> targetClass); //靜態調用

    boolean isRuntime();

    boolean matches(Method method, @Nullable Class<?> targetClass, Object... args);//動態額外調用

    MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;

}

再來看MethodMatcher實例,在Spring中一共支持兩種類型的MethodMatcher,即靜態動態MethodMatcher,具體使用哪一種是由isRuntime()方法決定的,在使用MethodMatcher之前,Spring會先調用isRuntime()來確定MethodMatcher的類型,返回false表示靜態,true表示動態。

對于靜態切入點,Spring會針對目標上每一個方法調用一次 matches(Method method, @Nullable Class<?> targetClass);方法,并緩存返回值,在調用方法時只會做一次適配性檢查,方法的后續調用將不再調用matches(Method method, @Nullable Class<?> targetClass);方法。

對于動態切入點,在第一次適配性檢查并且matches(Method method, @Nullable Class<?> targetClass);返回為ture時Spring還會額外調用一次matches(Method method, @Nullable Class<?> targetClass, Object... args);方法對每個調用方法進行進一步檢查,可以看到重載的matches方法多了一個Objects... args參數,這意味著在動態切入點中提供了運行時的方法請求參數,我們可以在matches(Method method, @Nullable Class<?> targetClass, Object... args);進行入參的適配,比如支付金額大于1000才滿足切入點條件。

在這里我們以JDK動態代理為例子嘗試找了一下源碼,在AdvisedSupport中找到如下代碼,并且該方法在JDK動態代理的invoke方法和CGlib的getCallbacks方法中均有調用,在AdvisedSupportAdvisorChainFactory的默認實現為DefaultAdvisorChainFactory


    /** The AdvisorChainFactory to use */

    AdvisorChainFactory advisorChainFactory = new DefaultAdvisorChainFactory();

    public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, @Nullable 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;

    }

我們再跟到DefaultAdvisorChainFactory中一探究竟:


public class DefaultAdvisorChainFactory implements AdvisorChainFactory, Serializable {

    @Override

    public List<Object> getInterceptorsAndDynamicInterceptionAdvice(

            Advised config, Method method, @Nullable 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);

        Class<?> actualClass = (targetClass != null ? targetClass : method.getDeclaringClass());

        boolean hasIntroductions = hasMatchingIntroductions(config, actualClass);

        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(actualClass)) {

                    MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();

                    if (MethodMatchers.matches(mm, method, actualClass, hasIntroductions)) {

                        MethodInterceptor[] interceptors = registry.getInterceptors(advisor);

                        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(actualClass)) {

                    Interceptor[] interceptors = registry.getInterceptors(advisor);

                    interceptorList.addAll(Arrays.asList(interceptors));

                }

            }

            else {

                Interceptor[] interceptors = registry.getInterceptors(advisor);

                interceptorList.addAll(Arrays.asList(interceptors));

            }

        }

        return interceptorList;

    }

    /**

     * Determine whether the Advisors contain matching introductions.

     */

    private static boolean hasMatchingIntroductions(Advised config, Class<?> actualClass) {

        for (Advisor advisor : config.getAdvisors()) {

            if (advisor instanceof IntroductionAdvisor) {

                IntroductionAdvisor ia = (IntroductionAdvisor) advisor;

                if (ia.getClassFilter().matches(actualClass)) {

                    return true;

                }

            }

        }

        return false;

    }

}

可以看到,在該方法對ProxyConfig中的Advisor進行了遍歷并且如上面所說的邏輯進行isRuntime調用并生成MethodMatcher鏈供后續調用,對于此處的Advisor則是我們在調用ProxyFactoryaddAdvice時寫入的,具體邏輯如下:


@Override

    public void addAdvice(Advice advice) throws AopConfigException {

        int pos = this.advisors.size();

        addAdvice(pos, advice);

    }

    /**

     * Cannot add introductions this way unless the advice implements IntroductionInfo.

     */

    @Override

    public void addAdvice(int pos, Advice advice) throws AopConfigException {

        Assert.notNull(advice, "Advice must not be null");

        if (advice instanceof IntroductionInfo) {

            // We don't need an IntroductionAdvisor for this kind of introduction:

            // It's fully self-describing.

            addAdvisor(pos, new DefaultIntroductionAdvisor(advice, (IntroductionInfo) advice));

        }

        else if (advice instanceof DynamicIntroductionAdvice) {

            // We need an IntroductionAdvisor for this kind of introduction.

            throw new AopConfigException("DynamicIntroductionAdvice may only be added as part of IntroductionAdvisor");

        }

        else {

            addAdvisor(pos, new DefaultPointcutAdvisor(advice));

        }

    }


  其實對于靜態切入點和動態切入點從上面我就可以得出的是動態切入點會額外調用一個MethodMatcher用來做參數的適配,而靜態切入點在第一次調用之后進行了緩存所以相比之下靜態切入點的性能優于動態切入點,而動態切入點的靈活性高于靜態切入點。

Spring 默認提供的切入點

和上面的通知一樣,Pointcut接口通常也不需要我們自己去實現,Spring默認提供了八個該接口的實現類,其中兩個用作創建靜態和動態切入點的抽象類以及六個具體實現類,其中每個具體類有以下能力:

  • 一起構成多個切入點

  • 處理控制流切入點

  • 執行簡單的基于名稱的匹配

  • 使用正在表達式定義切入點

  • 使用AspectJ 表達式定義切入點

  • 定義在類或者方法級別查找特定注解的切入點

具體的描述如下:

  • AnnotationMatchingPointcut:此實現在類或方法上查找特定的Java注解,需要JDK5或更高。

  • AspectJExpressionPointcut:此實現使用織入器以AspectJ語法評估切入點的表達式。

  • ComposablePointcut:ComposablePointcut類使用諸如union()和intersection()等操作組合兩個或更多切入點。

  • ControlFlowPointcut:這是一種特殊的切入點,它們匹配另一個方法的控制流中所有方法,即任何作為另一個方法的結果而直接或間接調用方法。

  • DynamicMethodMatcherPointcut:此實現作為構建動態切入點的基類。

  • JdkRegexpMethodPointcut:該實現允許使用正則表達式定義切入點。

  • NameMatchMethodPointcut:通過該實現可以創建一個根據方法名稱進行簡單匹配的切入點。

  • StaticMethodMatcherPointcut:此實現作為構建靜態切入點的基類。

如上一共八種,具體的代碼就不貼出來了,使用前可以找到相應的源碼進行閱讀,其中AspectJExpressionPointcut實現,其實就是支持我們常用的類似:execution( com.apress.prospring5.ch5..sing (com.apress.prospring5.ch2.common.Guitar))) **這樣的表達式,該表達式標明一個切入點的判斷條件為:在包com.apress.prospring5.ch5中含有一個入參為com.apress.prospring5.ch2.common.Guitar的sing方法為適配切入點。

AOP框架服務

對于AOP,通常我們提倡配置優于手動,首先我們看看以聲明的方式配置AOP,有如下三個選項:

  • 使用ProxyFactoryBean:在Spring aop中,當根據定義的Spring bean創建AOP代理時,ProxyFactoryBean提供了一種聲明方式來配置Spring的ApplicationContext(以及底層的BeanFactory)。

配置示例:


    <bean id="agent" class="com.liuningyun.demo.chapter5.Agent"/>

    <bean id="advice" class="com.liuningyun.demo.chapter5.AgentDecoratorAdvice"/>

    <bean id="agentmockcall" class="com.liuningyun.demo.chapter5.AgentMockCall" p:agents-ref="proxyOne"/>

    <bean id="proxyOne" class="org.springframework.aop.framework.ProxyFactoryBean"

          p:target-ref="agent"

          p:interceptorNames-ref="interceptorAdviceNames"/>

    <util:list id="interceptorAdviceNames">

        <value>advice</value>

    </util:list>

  • 使用Spring aop命名空間:在Spring 2.0中引入了aop名稱空間,提供了一種簡化的方式來定義Spring中的切面以及DI需求,但實際命名空間的底層實現依然是ProxyFactoryBean,只是換了個殼子而已。

配置示例:


 <bean id="advice" class="com.liuningyun.demo.chapter5.AgentDecoratorAdvice"/>

    <aop:config>

        <aop:pointcut id="agentPoint" expression="execution(* com.liuningyun.demo..say*())"/>

        <aop:aspect ref="advice" >

            <aop:before method="beforeAdvice" pointcut-ref="agentPoint"/>

        </aop:aspect>

    </aop:config>

  • 使用@AspectJ樣式注解:基于AspectJ語法的注解,在初始化ApplicationContext的時候,仍然使用Spring的代理機制。

使用示例(需要配置<aop:aspect-autoproxy> 或則用啟用相同的注解 @EnableAspectJAutoProxy):


@Component

@Aspect

public class AgentDecoratorAdvice{

    @Pointcut("execution(* com.liuningyun.demo..say*())")

    public void sayExcution(){}

    @Before("sayExcution()")

    public void beforeAdvice(JoinPoint joinPoint) throws Throwable {

        System.out.println("beforeAdvice: " + joinPoint.getSignature().getDeclaringTypeName() + joinPoint.getSignature().getName());

    }

    @Around("sayExcution()")

    public void aroundAdvice(JoinPoint joinPoint) throws Throwable {

        System.out.println("AroundAdvice: " + joinPoint.getSignature().getDeclaringTypeName() + joinPoint.getSignature().getName());

    }

}


總結:那么回過頭來看看Spring的AOP到底是個什么玩意呢?Spring的AOP是一種面向切面的編程方式,并且在SpringAOP按照AOP Alliance實現了一套標準化的接口,SpringAOP中存在兩種代理模式,即CGLIB代理和JDK動態代理,CGLIB代理是為代理目標類生成新的字節碼文件,生成一個目標類的子類去進行代理(實際是覆蓋方法),所以不能代理final類,而JDK動態代理則是代理接口,使用JDK代理的任何對象都至少實現一個接口,生成的代理則是實現了該接口的對象,并且在Spring中這兩種代理模式在默認的情況下會根據代理對象的一些屬性進行自動選擇。同時,在SpringAOP中涉及的幾個基礎組件Advice,Pointcut以及Advisor其實Spring都有足夠多的實現類,提供了足夠靈活的切入點以及通知方式,包括靜態切入點的啟動緩存通知鏈策略,動態切入點的二次校驗規則等,所以SpringAOP其實給了我們很多選擇,但是魚和熊掌不可兼得,性能和靈活,根據實際業務需要選擇就好。


?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 在一個草原平地上,草兒葳蕤生長,暖烘烘的陽光灑向大地,七彩光映出來的一切是那么美好。一只毛色火紅的狐貍正坐在一棵大...
    公子凌希閱讀 717評論 1 8
  • 我的窗外是一片湖,還有兩棵樹。我搬進來的時候是八月,正值盛夏,兩棵樹長得郁郁蔥蔥。即便對面是一片森林,即便眼前是一...
    MsCee的慢時光閱讀 623評論 4 10
  • 背后 4101 董文斐 燈光暗下來。一束輕柔的白光在頭頂傾灑下來。我不敢抬頭,下面坐滿了聽眾,這會卻寂靜無聲。輕輕...
    稼軒李德智閱讀 540評論 0 1
  • 頭都想炸了,今天一早就在想要寫什么內容,就在想要交作業。但是我想不出來,困擾我的事情太多。 早上九點就要去上班,上...
    小藍兒閱讀 190評論 0 0