深入理解Spring AOP-基于API編程

PointCut

AOP標準中的Joinpoit可以有很多類型:構造方法調用劫映、字段的設置和獲取房午、方法調用和執(zhí)行等蹋绽。而Spring AOP中只支持方法執(zhí)行類型的Joinpoint拍霜,不過這已經(jīng)夠我們用了。

Spring AOP中通過接口org.springframework.aop.Pointcut來表示所有連接點Joinpoit的抽象挽铁。Pointcut接口的代碼定義如下:

public interface Pointcut {

    ClassFilter getClassFilter();

    MethodMatcher getMethodMatcher();

    Pointcut TRUE = TruePointcut.INSTANCE;

}

ClassFilter將用來匹配目標對象伟桅,MethodMatcher用來匹配將被執(zhí)行織入操作的相應方法。TruePointcut表示匹配所有對象叽掘。

public interface ClassFilter {

    /**
     * 當織入的目標對象的Class類型和Pointcut所規(guī)定的類型相同時贿讹,
     * 該方法返回true
     */
    boolean matches(Class<?> clazz);

    /**
     * 匹配所以類的ClassFilter實例.
     */
    ClassFilter TRUE = TrueClassFilter.INSTANCE;

}

MethodMatcher接口的代碼定義如下:

public interface MethodMatcher {

    /**
     * 判斷方法是否匹配,靜態(tài)的MethodMatcher調用
     */
    boolean matches(Method method, Class<?> targetClass);

    /**
     * 判斷MethodMatcher是否是動態(tài)的够掠,如果是動態(tài)的該方法返回TRUE,將會調用3個參數(shù)的matches方法茄菊。
     * 如果是靜態(tài)的疯潭,該方法返回FALSE,將會調用2個參數(shù)的matches方法面殖。
     */
    boolean isRuntime();

    /**
     * 判斷是否匹配方法竖哩,動態(tài)的MethodMatcher調用
     * 
     */
    boolean matches(Method method, Class<?> targetClass, Object... args);


    /**
     * 匹配所有方法的MethodMatcher實例
     */
    MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;

}

根據(jù)是否需要捕捉目標方法執(zhí)行時的參數(shù),可以將MethodMatcher分為動態(tài)和靜態(tài)兩種脊僚。在MethodMatcher類型的基礎上相叁,Pointcut可以分為兩類遵绰,即StaticMethodMatcherPointcutDynamicMethodMatcherPointcut。因為StaticMethodMatcherPointcut具有明顯的性能優(yōu)勢增淹,所以椿访,Spring為其提供了更多支持。

PointCut部分繼承圖

如果想要自定義PointCut我們可以根據(jù)實現(xiàn)需求 虑润,可以選擇繼承StaticMethodMatcherPointcut或者繼承DynamicMethodMatcherPointcut

Joinpoint和Pointcut的區(qū)別:這兩個概念差不多成玫,可以把他們當成一回事。一個Pointcut可以包含多個Joinpoint.

Advice(增強)

Spring中的Advice實現(xiàn)全部基于AOP Alliance規(guī)定的接口拳喻。

Advice部分繼承類圖

按照增強(advice)在目標對象方法連接點的位置哭当,可以將增強分為以下五類:

  1. 前置增強:org.springframework.aop.BeforeAdvice,在目標方法執(zhí)行前執(zhí)行冗澈;
  2. 后置增強:org.springframework.aop.AfterReturningAdvice钦勘,在目標方法調用后執(zhí)行;
  3. 環(huán)繞增強:org.aopalliance.intercept.MethodInterceptor亚亲,截取目標類方法的執(zhí)行彻采,并在前后添加橫切邏輯;
  4. 拋出異常增強:org.springframework.aop.ThrowsAdvice朵栖,目標方法拋出異常后執(zhí)行颊亮;
  5. Introduction增強:org.springframework.aop.introductioninterceptor

Spring AOP中的AfterReturningAdvice只有在方法正常返回時才會執(zhí)行,且不能更改方法的返回值陨溅。所以要想實現(xiàn)類似資源清理的橫切工作终惑,無法使用AfterReturningAdvice,而Spring AOP并沒有提供After Finally Advice门扇。如果要想實現(xiàn)資源清理的工作我們可以借助Around Advice雹有,它在Spring AOP的API編程實現(xiàn)中沒有對應的實現(xiàn)類,不過可以借助MethodInterceptor來實現(xiàn)Around Advice臼寄。下面來看看如何定義一個Around Advice

/**
 * 通過MethodInterceptor來實現(xiàn)Around Advice
 */
public class PerformanceMethodInterceptor implements MethodInterceptor{

    private final Logger logger = LoggerFactory.getLogger(PerformanceMethodInterceptor.class);

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        StopWatch stopWatch = new StopWatch();
        try {
            stopWatch.start();
            return invocation.proceed();
        } catch (Exception e){
            // do nothing
        } finally {
            stopWatch.stop();
            if (logger.isInfoEnabled()){
                logger.info(stopWatch.toString());
            }
        }
        return null;
    }
}

異常拋出增強類的定義接口是ThrowsAdvice霸奕,它是一個標志接口,內部沒有定義任何方法吉拳。不過我們在編寫ThrowsAdvice的實現(xiàn)類時质帅,必須要定義如下方法:

/**
 * 1. 方法名必須是afterThrowing
 * 2. 前三個參數(shù)(method,args,target)是可選的,不過必須是要么同時存在留攒,要么同時不存在
 * 3. 第四個參數(shù)必須存在煤惩,可以是Throwable或者其任何子類
 * 4. 可以存在多個符合規(guī)則的afterThrowing,Spring會自動選擇最匹配的
 */
public void afterThrowing(Method method,Object[] args,Object target,Throwable t)

ThrowsAdvice的實現(xiàn)如下:

public class MyThrowsAdvice implements ThrowsAdvice {

    private Logger logger = LoggerFactory.getLogger(MyThrowsAdvice.class);

    public void afterThrowing(Method method, Object[] args, Object target, Throwable t) {
        logger.error("發(fā)送異常啦",t);
    }

    public void afterThrowing(RuntimeException t) {
        logger.error("發(fā)生了運行時異常炼邀,異常信息:",t);
    }
}

Introduction

除了常見的Advice之外魄揉,還有一種特殊的Advice--Introduction。Introduction可以在不改變目標類的情況下拭宁,為目標類添加新的屬性以及行為洛退。要想為目標對象添加新的屬性和行為瓣俯,必須要先聲明對應的接口和實現(xiàn)類,然后可以通過攔截器IntroductionInterceptor實現(xiàn)添加兵怯。

Introduction相關類圖

下面來演示一下DelegatingIntroductionInterceptor的用法彩匕。

public class DelegatingIntroductionInterceptorSample {
    public static void main(String[] args) {
        IDancer dancer = new Dancer();
        DelegatingIntroductionInterceptor interceptor = new DelegatingIntroductionInterceptor(dancer);

        ProxyFactory weaver = new ProxyFactory(new Singer());
        weaver.setInterfaces(new Class[]{IDancer.class,ISinger.class});
        weaver.addAdvice(interceptor);
        Object proxy = weaver.getProxy();
        ((IDancer)proxy).dance();
        ((ISinger)proxy).sing();
    }
}

Aspect

我們知道@Aspect可以用來表示Aspect。不過在針對面向API編程的Spring AOP中摇零,Advisor用來表示Spring中的Aspect推掸。Advisor只能看成是一種特殊的Aspect,因為在Advisor中通常只持有一個Pointcut和一個Advice(實際的Aspect定義中可以有多個Pointcut和多個Advice)驻仅。

Advisor可以分為兩種:

  • PointcutAdvisor
  • IntroductionAdvisor

大部分Advisor都實現(xiàn)自PointcutAdvisor谅畅。下面是部分PointcutAdvisor子類的繼承圖
部分PointcutAdvisor繼承體系

Spring-AOP 切點/切面類型和創(chuàng)建切面

織入

織入就是為了創(chuàng)建代理對象。當有了切入點和橫切邏輯(advice)之后噪服,如何在目標對象(或方法)中加入橫切邏輯呢毡泻?這個時候我們需要借助織入器將橫切邏輯織入目標對象當中。

在Spring AOP中粘优,根據(jù)一次創(chuàng)建代理的個數(shù)仇味,可以分為創(chuàng)建單個代理的織入器和創(chuàng)建多個代理的織入器(即自動代理)。

Spring AOP中創(chuàng)建單個代理的織入器的類有:

  • ProxyFactory
  • ProxyFactoryBean
  • AspectJProxyFactory

AspectJ使用ajc編譯器作為織入器雹顺;Jboss aop使用自定義的ClassLoader作為織入器丹墨。

在介紹織入相關的內容之前,我們先來看一下相關類的繼承圖嬉愧。理解了這張圖贩挣,織入的原理(其實差不多也就是Spring AOP的原理)便能了然于心。
Spring AOP織入(單個)相關類圖

ProxyFactory

ProxyFactory的使用示例:

// 創(chuàng)建目標對象
IService target = new ServiceOne();
// 創(chuàng)建增強類對象(advice)
MethodInterceptor advice = new PerformanceMethodInterceptor();

ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(target);
proxyFactory.addAdvice(advice);
/* 或者
    ProxyFactory proxyFactory = new ProxyFactory();
    proxyFactory.addAdvisor(advisor);
 */
IService proxy = (IService) proxyFactory.getProxy();
System.out.println(target.getClass());
System.out.println(proxy.getClass());

Spring AOP借助動態(tài)代理技術没酣,可以創(chuàng)建基于接口的代理王财;借助CGLIB,可以創(chuàng)建基于類的代理裕便。一般根據(jù)代理對象class的輸出System.out.println(proxy.getClass());绒净,可以觀察出代理對象是通過什么技術生成的。

class $Proxy0  表示代理對象是通過動態(tài)代理生成的偿衰;
$$EnhancerByCGLIB$$9e62fc83  表示代理對象是通過cglib生成的挂疆;

默認情況下,Spring AOP會使用動態(tài)代理基于接口生成代理對象下翎,當出現(xiàn)下列情況會使用CGLIB基于類生成代理對象囱嫩。

  1. 目標類沒有實現(xiàn)任何接口;
  2. ProxyFactory的proxyTargetClass屬性值被設置為true漏设;
  3. ProxyFactory的optimize屬性值被設置為true。

ProxyFactoryBean

ProxyFactoryBean和ProxyFactory的使用沒有太大的區(qū)別今妄。一般ProxyFactory是脫離IOC容器來使用郑口,而ProxyFactoryBean則與IOC容器結合使用鸳碧。

    <!-- 目標對象 -->
    <bean id="targetObject" class="cn.zgc.aop.apis.advice.advices.TargetObject"/>
    <!-- 前置增強 -->
    <bean id="myBeforeAdvice" class="cn.zgc.aop.apis.advice.advices.MyBeforeAdvice"/>
    <!-- 后置增強 -->
    <bean id="myAfterReturningAdvice" class="cn.zgc.aop.apis.advice.advices.MyAfterReturningAdvice"/>
    <!-- 拋出異常增強 -->
    <bean id="myThrowsAdvice" class="cn.zgc.aop.apis.advice.advices.MyThrowsAdvice"/>
    <!-- 環(huán)繞增強 -->
    <bean id="myAroundAdvice" class="cn.zgc.aop.apis.advice.advices.MyAroundAdvice"/>

    <bean id="targetProxy" class="org.springframework.aop.framework.ProxyFactoryBean"
          p:proxyTargetClass="true"
          p:target-ref="targetObject"
          p:interceptorNames="myAroundAdvice,myBeforeAdvice,myAfterReturningAdvice"
    />

自動代理

Spring AOP為我們提供了自動代理機制,讓容器為我們自動生成代理犬性,這樣我們就不用針對每個目標對象若想生成代理對象瞻离,都需要配置相應的ProxyFactoryBean。在Spring內部乒裆,通過借助BeanPostProcessor完成自動代理這項工作套利。

BeanPostProcessor類可以在對象實例化前為其生成代理對象并返回,而不是實例化后的目標對象本身鹤耍,從而達到代理對象自動生成的目的

常用的自動代理類

  • BeanNameAutoProxyCreator
  • DefaultAdvisorAutoProxyCreator
  • AnnotationAwareAspectJAutoProxyCreator

看看下面的類繼承圖肉迫,可以發(fā)現(xiàn)自動代理類都實現(xiàn)了BeanPostProcessor

自動代理類的繼承關系圖

BeanNameAutoProxyCreator

BeanNameAutoProxyCreator通過beanNames屬性指定目標對象集合,通過interceptorNames屬性指定advice稿黄。只要在IOC容器中注冊BeanNameAutoProxyCreator喊衫,就能為目標對象創(chuàng)建出代理。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       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.xsd">

    <!-- 通過Bean名稱自動創(chuàng)建代理 -->

    <!-- 目標Bean -->
    <bean id="serviceOne" class="cn.zgc.aop.apis.weave.autoCreateProxy.BeanNameAutoProxyCreator.ServiceOne"/>
    <bean id="serviceTwo" class="cn.zgc.aop.apis.weave.autoCreateProxy.BeanNameAutoProxyCreator.ServiceTwo"/>
    <!-- 增強 -->
    <bean id="privilegeDetectionAdvice" class="cn.zgc.aop.apis.weave.autoCreateProxy.BeanNameAutoProxyCreator.PrivilegeDetectionAdvice"/>

    <!-- 自動代理-->
    <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
        <property name="beanNames">
            <list>
                <value>service*</value>
            </list>
        </property>
        <property name="interceptorNames">
            <list>
                <value>privilegeDetectionAdvice</value>
            </list>
        </property>
    </bean>
</beans>

DefaultAdvisorAutoProxyCreator

要想使用DefaultAdvisorAutoProxyCreator的話杆怕,需要在配置文件中注冊(配置)DefaultAdvisorAutoProxyCreator和相關Advisor的bean族购,并且要想DefaultAdvisorAutoProxyCreator的自動配置生效的話,必須得配置Advisor陵珍,因為只有Advisor當中才即包含Pointcut又包含Advice寝杖,有了這兩個信息之后,DefaultAdvisorAutoProxyCreator就能通過Advisor的信息自動為目標對象生成代理互纯。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       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.xsd">

    <!-- 通過Advisor自動創(chuàng)建代理 -->

    <!-- 目標Bean -->
    <bean id="serviceOne" class="cn.zgc.aop.apis.weave.autoCreateProxy.BeanNameAutoProxyCreator.ServiceOne"/>
    <bean id="serviceTwo" class="cn.zgc.aop.apis.weave.autoCreateProxy.BeanNameAutoProxyCreator.ServiceTwo"/>
    <!-- 增強 -->
    <bean id="privilegeDetectionAdvice" class="cn.zgc.aop.apis.weave.autoCreateProxy.BeanNameAutoProxyCreator.PrivilegeDetectionAdvice"/>

    <!-- Aspect-->
    <bean id="privilegeAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
        <property name="pointcut">
            <bean class="org.springframework.aop.support.NameMatchMethodPointcut">
                <property name="mappedNames">
                    <list>
                        <value>service*</value>
                    </list>
                </property>
            </bean>
        </property>

        <property name="advice" ref="privilegeDetectionAdvice"/>
    </bean>

    <!-- 自動代理 -->
    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>

</beans>

AnnotationAwareAspectJAutoProxyCreator

AnnotationAwareAspectJAutoProxyCreator可以自動將@AspectJ注解切面類織入目標Bean中瑟幕。注意,要想使用@AspectJ需要導入aspectjweaver的jar包伟姐。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       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.xsd">

    <!-- 通過配置使用@AspectJ -->

    <!-- 目標Bean -->
    <bean id="service" class="cn.zgc.aop.apis.weave.autoCreateProxy.AnnotationAwareAspectJAutoProxyCreator.ServiceOne"/>
    <!-- 使用了@AspectJ注解的切面類 -->
    <bean class="cn.zgc.aop.apis.weave.autoCreateProxy.AnnotationAwareAspectJAutoProxyCreator.PrivilegeDetectionAspect"/>
    <!-- 自動代理創(chuàng)建器收苏,自動將@AspectJ注解切面類織入目標Bean中 -->
    <bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator">
        <!-- true:使用cglib生成代理對象,false(默認):使用動態(tài)代理生成代理對象 -->
        <property name="proxyTargetClass" value="true"/>
    </bean>

</beans>

建議

基于API編程的方式對于我們理解Spring AOP的原理很有幫助愤兵,我們應該對其要有所了解鹿霸,但是不推薦再使用這種方式了。

參考
《Spring揭秘 》.王福強

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末秆乳,一起剝皮案震驚了整個濱河市懦鼠,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌屹堰,老刑警劉巖肛冶,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異扯键,居然都是意外死亡睦袖,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進店門荣刑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來馅笙,“玉大人伦乔,你說我怎么就攤上這事《埃” “怎么了烈和?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長皿淋。 經(jīng)常有香客問我招刹,道長,這世上最難降的妖魔是什么窝趣? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任疯暑,我火速辦了婚禮,結果婚禮上高帖,老公的妹妹穿的比我還像新娘缰儿。我一直安慰自己,他們只是感情好散址,可當我...
    茶點故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布乖阵。 她就那樣靜靜地躺著,像睡著了一般预麸。 火紅的嫁衣襯著肌膚如雪瞪浸。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天吏祸,我揣著相機與錄音对蒲,去河邊找鬼。 笑死贡翘,一個胖子當著我的面吹牛蹈矮,可吹牛的內容都是我干的。 我是一名探鬼主播鸣驱,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼泛鸟,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了踊东?” 一聲冷哼從身側響起北滥,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎闸翅,沒想到半個月后再芋,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡坚冀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年济赎,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,001評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡司训,死狀恐怖华蜒,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情豁遭,我是刑警寧澤,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布贺拣,位于F島的核電站蓖谢,受9級特大地震影響,放射性物質發(fā)生泄漏譬涡。R本人自食惡果不足惜闪幽,卻給世界環(huán)境...
    茶點故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望涡匀。 院中可真熱鬧盯腌,春花似錦、人聲如沸陨瘩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽舌劳。三九已至帚湘,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間甚淡,已是汗流浹背大诸。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留贯卦,地道東北人资柔。 一個月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像撵割,于是被迫代替她去往敵國和親贿堰。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,955評論 2 355

推薦閱讀更多精彩內容