欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Spring AOP 對象內(nèi)部方法間的嵌套調(diào)用方式

 更新時間:2021年08月28日 15:15:06   作者:懋為  
這篇文章主要介紹了Spring AOP 對象內(nèi)部方法間的嵌套調(diào)用方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教

Spring AOP 對象內(nèi)部方法間的嵌套調(diào)用

前兩天面試的時候,面試官問了一個問題,大概意思就是一個類有兩個成員方法 A 和 B,兩者都加了事務(wù)處理注解,定義了事務(wù)傳播級別為 REQUIRE_NEW,問 A 方法內(nèi)部直接調(diào)用 B 方法時能否觸發(fā)事務(wù)處理機(jī)制。

答案有點(diǎn)復(fù)雜,Spring 的事務(wù)處理其實(shí)是通過AOP實(shí)現(xiàn)的,而實(shí)現(xiàn)AOP的方法有好幾種,對于通過 Jdk 和 cglib 實(shí)現(xiàn)的 aop 處理,上述問題的答案為否,對于通過AspectJ實(shí)現(xiàn)的,上述問題答案為是。

本文就結(jié)合具體例子來看一下

我們先定義一個接口

public interface AopActionInf {
    void doSomething_01();
    void doSomething_02();
}

以及此接口的一個實(shí)現(xiàn)類

public class AopActionImpl implements AopActionInf{
    public void doSomething_01() {
        System.out.println("AopActionImpl.doSomething_01()");
        //內(nèi)部調(diào)用方法 doSomething_02
        this.doSomething_02();
    }
    public void doSomething_02() {
        System.out.println("AopActionImpl.doSomething_02()");
    }
}

增加AOP處理

public class ActionAspectXML {
    public Object aroundMethod(ProceedingJoinPoint pjp) throws Throwable{
        System.out.println("進(jìn)入環(huán)繞通知");
        Object object = pjp.proceed();//執(zhí)行該方法
        System.out.println("退出方法");
        return object;
    }
}
<aop:aspectj-autoproxy/>
<bean id="actionImpl" class="com.maowei.learning.aop.AopActionImpl"/>
<bean id="actionAspectXML" class="com.maowei.learning.aop.ActionAspectXML"/>
<aop:config>
    <aop:aspect id = "aspectXML" ref="actionAspectXML">
        <aop:pointcut id="anyMethod" expression="execution(* com.maowei.learning.aop.AopActionImpl.*(..))"/>
        <aop:around method="aroundMethod" pointcut-ref="anyMethod"/>
    </aop:aspect>
</aop:config>

運(yùn)行結(jié)果如下:

這里寫圖片描述

下圖是斷點(diǎn)分析在調(diào)用方法doSomething_02時的線程棧,很明顯在調(diào)用doSomething_02時并沒有對其進(jìn)行AOP處理。

默認(rèn)情況下,Spring AOP使用Jdk的動態(tài)代理機(jī)制實(shí)現(xiàn),當(dāng)然也可以通過如下配置更改為cglib實(shí)現(xiàn),但是運(yùn)行結(jié)果相同,此處不再贅述。

<aop:aspectj-autoproxy proxy-target-class="true"/>

那有沒有辦法能夠觸發(fā)AOP處理呢?答案是有的,考慮到AOP是通過動態(tài)生成目標(biāo)對象的代理對象而實(shí)現(xiàn)的,那么只要在調(diào)用方法時改為調(diào)用代理對象的目標(biāo)方法即可。

我們將調(diào)用 doSomething_02 的那行代碼改成如下,并修改相應(yīng)配置信息:

public void doSomething_01() {
    System.out.println("AopActionImpl.doSomething_01()");
    ((AopActionInf) AopContext.currentProxy()).doSomething_02();
}
<aop:aspectj-autoproxy expose-proxy="true"/>

先來看一下運(yùn)行結(jié)果,

這里寫圖片描述

從運(yùn)行結(jié)果可以看出,嵌套調(diào)用方法已經(jīng)能夠?qū)崿F(xiàn)AOP處理了,同樣我們看一下線程調(diào)用棧信息,顯然 doSomething_02 方法被增強(qiáng)處理了(紅框中內(nèi)容)。

同一對象內(nèi)的嵌套方法調(diào)用AOP失效原因分析

舉一個同一對象內(nèi)的嵌套方法調(diào)用攔截失效的例子

首先定義一個目標(biāo)對象:

/**
 * @description: 目標(biāo)對象與方法
 * @create: 2020-12-20 17:10
 */
public class TargetClassDefinition {
    public void method1(){
        method2();
        System.out.println("method1 執(zhí)行了……");
    }
    public void method2(){
        System.out.println("method2 執(zhí)行了……");
    }
}

在這個類定義中,method1()方法會調(diào)用同一對象上的method2()方法。

現(xiàn)在,我們使用Spring AOP攔截該類定義的method1()和method2()方法,比如一個簡單的性能檢測邏輯,定義如下Aspect:

/**
 * @description: 性能檢測Aspect定義
 * @create: 2020-12-20 17:13
 */
@Aspect
public class AspectDefinition {
    @Pointcut("execution(public void *.method1())")
    public void method1(){}
    @Pointcut("execution(public void *.method2())")
    public void method2(){}
    @Pointcut("method1() || method2()")
    public void pointcutCombine(){}
    @Around("pointcutCombine()")
    public Object aroundAdviceDef(ProceedingJoinPoint pjp) throws Throwable{
        StopWatch stopWatch = new StopWatch();
        try{
            stopWatch.start();
            return pjp.proceed();
        }finally {
            stopWatch.stop();
            System.out.println("PT in method [" + pjp.getSignature().getName() + "]>>>>>>"+stopWatch.toString());
        }
    }
}

由AspectDefinition定義可知,我們的Around Advice會攔截pointcutCombine()所指定的JoinPoint,即method1()或method2()的執(zhí)行。

接下來將AspectDefinition中定義的橫切邏輯織入TargetClassDefinition并運(yùn)行,其代碼如下:

/**
 * @description: 啟動方法
 * @create: 2020-12-20 17:23
 */
public class StartUpDefinition {
    public static void main(String[] args) {
        AspectJProxyFactory weaver = new AspectJProxyFactory(new TargetClassDefinition());
        weaver.setProxyTargetClass(true);
        weaver.addAspect(AspectDefinition.class);
        Object proxy = weaver.getProxy();
        ((TargetClassDefinition) proxy).method1();
        System.out.println("-------------------");
        ((TargetClassDefinition) proxy).method2();
    }
}

執(zhí)行之后,得到如下結(jié)果:

method2 執(zhí)行了……
method1 執(zhí)行了……
PT in method [method1]>>>>>>StopWatch '': running time = 20855400 ns; [] took 20855400 ns = 100%
-------------------
method2 執(zhí)行了……
PT in method [method2]>>>>>>StopWatch '': running time = 71200 ns; [] took 71200 ns = 100%

不難發(fā)現(xiàn),從外部直接調(diào)用TargetClassDefinition的method2()方法的時候,因?yàn)樵摲椒ê灻ヅ銩spectDefinition中的Around Advice所對應(yīng)的Pointcut定義,所以Around Advice邏輯得以執(zhí)行,也就是說AspectDefinition攔截method2()成功了。但是,當(dāng)調(diào)用method1()時,只有method1()方法執(zhí)行攔截成功,而method1()方法內(nèi)部的method2()方法沒有執(zhí)行卻沒有被攔截。

原因分析

這種結(jié)果的出現(xiàn),歸根結(jié)底是Spring AOP的實(shí)現(xiàn)機(jī)制造成的。眾所周知Spring AOP使用代理模式實(shí)現(xiàn)AOP,具體的橫切邏輯會被添加到動態(tài)生成的代理對象中,只要調(diào)用的是目標(biāo)對象的代理對象上的方法,通常就可以保證目標(biāo)對象上的方法執(zhí)行可以被攔截。就像TargetClassDefinition的method2()方法執(zhí)行一樣。

不過,代理模式的實(shí)現(xiàn)機(jī)制在處理方法調(diào)用的時序方面,會給使用這種機(jī)制實(shí)現(xiàn)的AOP產(chǎn)品造成一個遺憾,一般的代理對象方法與目標(biāo)對象方法的調(diào)用時序如下所示:

    proxy.method2(){
        記錄方法調(diào)用開始時間;
        target.method2();
        記錄方法調(diào)用結(jié)束時間;
        計(jì)算消耗的時間并記錄到日志;
    }

在代理對象方法中,無論如何添加橫切邏輯,不管添加多少橫切邏輯,最終還是需要調(diào)用目標(biāo)對象上的同一方法來執(zhí)行最初所定義的方法邏輯。

如果目標(biāo)對象中原始方法調(diào)用依賴于其他對象,我們可以為目標(biāo)對象注入所需依賴對象的代理,并且可以保證想用的JoinPoint被攔截并織入橫切邏輯。而一旦目標(biāo)對象中的原始方法直接調(diào)用自身方法的時候,也就是說依賴于自身定義的其他方法時,就會出現(xiàn)如下圖所示問題:

在代理對象的method1()方法執(zhí)行經(jīng)歷了層層攔截器后,最終會將調(diào)用轉(zhuǎn)向目標(biāo)對象上的method1(),之后的調(diào)用流程全部都是在TargetClassDefinition中,當(dāng)method1()調(diào)用method2()時,它調(diào)用的是TargetObject上的method2()而不是ProxyObject上的method2()。而針對method2()的橫切邏輯,只織入到了ProxyObject上的method2()方法中。所以,在method1()中調(diào)用的method2()沒有能夠被攔截成功。

解決方案

當(dāng)目標(biāo)對象依賴于其他對象時,我們可以通過為目標(biāo)對象注入依賴對象的代理對象,來解決相應(yīng)的攔截問題。

當(dāng)目標(biāo)對象依賴于自身時,我們可以嘗試將目標(biāo)對象的代理對象公開給它,只要讓目標(biāo)對象調(diào)用自身代理對象上的相應(yīng)方法,就可以解決內(nèi)部調(diào)用的方法沒有被攔截的問題。

Spring AOP提供了AopContext來公開當(dāng)前目標(biāo)對象的代理對象,我們只要在目標(biāo)對象中使用AopContext.currentProxy()就可以取得當(dāng)前目標(biāo)對象所對應(yīng)的代理對象。重構(gòu)目標(biāo)對象,如下所示:

import org.springframework.aop.framework.AopContext;
/**
 * @description: 目標(biāo)對象與方法
 * @create: 2020-12-20 17:10
 */
public class TargetClassDefinition {
    public void method1(){
        ((TargetClassDefinition) AopContext.currentProxy()).method2();
//        method2();
        System.out.println("method1 執(zhí)行了……");
    }
    public void method2(){
        System.out.println("method2 執(zhí)行了……");
    }
}

要使AopContext.currentProxy()生效,需要在生成目標(biāo)對象的代理對象時,將ProxyConfig或者它相應(yīng)的子類的exposeProxy屬性設(shè)置為true,如下所示:

/**
 * @description: 啟動方法
 * @create: 2020-12-20 17:23
 */
public class StartUpDefinition {
    public static void main(String[] args) {
        AspectJProxyFactory weaver = new AspectJProxyFactory(new TargetClassDefinition());
        weaver.setProxyTargetClass(true);
        weaver.setExposeProxy(true);
        weaver.addAspect(AspectDefinition.class);
        Object proxy = weaver.getProxy();
        ((TargetClassDefinition) proxy).method1();
        System.out.println("-------------------");
        ((TargetClassDefinition) proxy).method2();
    }
}
<!-- 在XML文件中的開啟方式 -->
<aop:aspectj-autoproxy expose-proxy="true" />

再次執(zhí)行代碼,即可實(shí)現(xiàn)所需效果:

method2 執(zhí)行了……
PT in method [method2]>>>>>>StopWatch '': running time = 180400 ns; [] took 180400 ns = 100%
method1 執(zhí)行了……
PT in method [method1]>>>>>>StopWatch '': running time = 24027700 ns; [] took 24027700 ns = 100%
-------------------
method2 執(zhí)行了……
PT in method [method2]>>>>>>StopWatch '': running time = 64200 ns; [] took 64200 ns = 100%

后記

雖然通過將目標(biāo)對象的代理對象賦給目標(biāo)對象實(shí)現(xiàn)了我們的目的,但解決的方式不夠雅觀,我們的目標(biāo)對象都直接綁定到了Spring AOP的具體API上了。因此,在開發(fā)中應(yīng)該盡量避免“自調(diào)用”的情況。

以上為個人經(jīng)驗(yàn),希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • java正則表達(dá)式獲取指定HTML標(biāo)簽的指定屬性值且替換的方法

    java正則表達(dá)式獲取指定HTML標(biāo)簽的指定屬性值且替換的方法

    下面小編就為大家?guī)硪黄猨ava正則表達(dá)式獲取指定HTML標(biāo)簽的指定屬性值且替換的方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-12-12
  • Java工廠模式優(yōu)雅地創(chuàng)建對象以及提高代碼復(fù)用率和靈活性

    Java工廠模式優(yōu)雅地創(chuàng)建對象以及提高代碼復(fù)用率和靈活性

    Java工廠模式是一種創(chuàng)建型設(shè)計(jì)模式,通過定義一個工廠類來封裝對象的創(chuàng)建過程,將對象的創(chuàng)建和使用分離,提高代碼的可維護(hù)性和可擴(kuò)展性,同時可以實(shí)現(xiàn)更好的代碼復(fù)用和靈活性
    2023-05-05
  • java 如何給對象中的包裝類設(shè)置默認(rèn)值

    java 如何給對象中的包裝類設(shè)置默認(rèn)值

    這篇文章主要介紹了java 如何給對象中的包裝類設(shè)置默認(rèn)值,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • SpringMVC中的DispatcherServlet請求分析

    SpringMVC中的DispatcherServlet請求分析

    這篇文章主要介紹了SpringMVC中的DispatcherServlet請求分析, DispatcherServlet作為一個Servlet,那么當(dāng)有請求到Tomcat等Servlet服務(wù)器時,會調(diào)用其service方法,再調(diào)用到其父類GenericServlet的service方法,需要的朋友可以參考下
    2024-01-01
  • 使用jquery 的ajax 與 Java servlet的交互代碼實(shí)例

    使用jquery 的ajax 與 Java servlet的交互代碼實(shí)例

    這篇文章主要介紹了使用jquery 的ajax 與 Java servlet的交互代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-09-09
  • 利用反射獲取Java類中的靜態(tài)變量名及變量值的簡單實(shí)例

    利用反射獲取Java類中的靜態(tài)變量名及變量值的簡單實(shí)例

    下面小編就為大家?guī)硪黄梅瓷浍@取Java類中的靜態(tài)變量名及變量值的簡單實(shí)例。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-12-12
  • Spring6整合JUnit的詳細(xì)步驟

    Spring6整合JUnit的詳細(xì)步驟

    這篇文章主要介紹了Spring6整合JUnit的詳細(xì)步驟,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-05-05
  • Spring定時任務(wù)中@PostConstruct被多次執(zhí)行異常的分析與解決

    Spring定時任務(wù)中@PostConstruct被多次執(zhí)行異常的分析與解決

    這篇文章主要給大家介紹了關(guān)于Spring定時任務(wù)中@PostConstruct被多次執(zhí)行異常的分析與解決方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。
    2017-10-10
  • Java超詳細(xì)講解三大特性之一的封裝

    Java超詳細(xì)講解三大特性之一的封裝

    封裝是一個非常廣泛的概念,小到一個屬性的封裝,大到一個框架或者一個項(xiàng)目的封裝,下面這篇文章主要給大家介紹了關(guān)于java中封裝的那點(diǎn)事,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-05-05
  • 利用Java實(shí)現(xiàn)word導(dǎo)入導(dǎo)出富文本(含圖片)的詳細(xì)代碼

    利用Java實(shí)現(xiàn)word導(dǎo)入導(dǎo)出富文本(含圖片)的詳細(xì)代碼

    這篇文章主要為大家詳細(xì)介紹了利用Java實(shí)現(xiàn)word導(dǎo)入導(dǎo)出富文本(含圖片),文中的示例代碼講解詳細(xì),對大家的學(xué)習(xí)或工作有一定的幫助,感興趣的小伙伴可以學(xué)習(xí)一下
    2024-02-02

最新評論