JDK動(dòng)態(tài)代理在Spring AOP中的實(shí)現(xiàn)

JDK動(dòng)態(tài)代理在Spring AOP中的實(shí)現(xiàn)

動(dòng)態(tài)代理機(jī)制

  • 通過(guò)實(shí)現(xiàn) InvocationHandler 接口創(chuàng)建自己的調(diào)用處理器

  • 通過(guò)為 Proxy 類指定 ClassLoader 對(duì)象和一組 interface 來(lái)創(chuàng)建動(dòng)態(tài)代理類

  • 通過(guò)反射機(jī)制獲得動(dòng)態(tài)代理類的構(gòu)造函數(shù)阶剑,其唯一參數(shù)類型是調(diào)用處理器接口類型

  • 通過(guò)構(gòu)造函數(shù)創(chuàng)建動(dòng)態(tài)代理類實(shí)例猜谚,構(gòu)造時(shí)調(diào)用處理器對(duì)象作為參數(shù)被傳入

AOP思想

  • OOP中引入封裝锚国、繼承和多態(tài)性等概念來(lái)建立一種對(duì)象層次結(jié)構(gòu)闻妓,用以模擬公共行為的一個(gè)集合。

  • AOP技術(shù)利用一種稱為“橫切”的技術(shù)却舀,解剖封裝的對(duì)象內(nèi)部夹厌,并將那些影響了多個(gè)類的公共行為封裝到一個(gè)可重用模塊列疗,這樣就能減少系統(tǒng)的重復(fù)代碼稽揭,降低模塊間的耦合度,并有利于未來(lái)的可操作性和可維護(hù)性肥卡。

  • AOP把軟件系統(tǒng)分為兩個(gè)部分:核心關(guān)注點(diǎn)和橫切關(guān)注點(diǎn)溪掀,業(yè)務(wù)處理的主要流程是核心關(guān)注點(diǎn),與之關(guān)系不大的部分是橫切關(guān)注點(diǎn)步鉴。

動(dòng)態(tài)代理源碼實(shí)現(xiàn) AopProxyFactory

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)) {
                // 表示在有接口實(shí)現(xiàn)的時(shí)候采用JDK動(dòng)態(tài)代理
                return new JdkDynamicAopProxy(config);
            }
            // 在沒(méi)有接口實(shí)現(xiàn)的時(shí)候采用Cglib動(dòng)態(tài)代理
            return new ObjenesisCglibAopProxy(config);
        }
        else {
            return new JdkDynamicAopProxy(config);
        }
    }

Spring中AOP的實(shí)現(xiàn)

程序自上而下執(zhí)行揪胃,與主業(yè)務(wù)邏輯的關(guān)系不大的橫切性問(wèn)題璃哟,aop面向切面編程,就是將主業(yè)務(wù)與橫切代碼分離喊递,做到解耦随闪。

常見(jiàn)實(shí)現(xiàn)有:

  • 統(tǒng)一日志處理
  • 統(tǒng)一異常處理
  • Spring事務(wù)管理

代碼實(shí)現(xiàn)

定義接口

/**
 * @ClassName: BuyService
 * @Description: 購(gòu)買基礎(chǔ)接口
 * @Author: 尚先生
 * @CreateDate: 2019/6/17 14:52
 * @Version: 1.0
 */
public interface BuyService {

    String buyPhone(BuyService buyService);

    String buyComputer(BuyService buyService);

}

定義實(shí)現(xiàn)類

/**
 * @ClassName: BusiServiceImpl
 * @Description: 購(gòu)買實(shí)現(xiàn)類
 * @Author: 尚先生
 * @CreateDate: 2019/6/17 14:53
 * @Version: 1.0
 */
@Service
public class BuyServiceImpl implements BuyService {

    @Intercept("buyPhone")
    @Override
    public String buyPhone(BuyService buyService) {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("==========當(dāng)前類描述 " + buyService.getClass().getName() + "=============" + " buyPhone");
        this.buyComputer(this);
        return "buy phone";
    }

    @Intercept("buyComputer")
    @Override
    public String buyComputer(BuyService buyService) {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("==========當(dāng)前類描述 " + buyService.getClass().getName() + "=============" + " buyComputer");
        return "buy computer";
    }
}

自定義攔截注解

/**
 * @ClassName: Intercept
 * @Description: 自定義攔截器注解
 * @Author: 尚先生
 * @CreateDate: 2019/6/17 16:28
 * @Version: 1.0
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Intercept {

    String value() default "";
}

自定義攔截器實(shí)現(xiàn)

/**
 * @ClassName: Interceptor
 * @Description: 攔截器實(shí)現(xiàn)類
 * @Author: 尚先生
 * @CreateDate: 2019/6/17 15:12
 * @Version: 1.0
 */
@Component
@Aspect
public class Interceptor {
    @Pointcut(value = "@annotation(com.learn.demo.java.proxy.Intercept)")
    public void buySomething() {
        System.out.println("===========自定義切入點(diǎn)===============");
    }
    @Around("buySomething()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        try {
            //通過(guò)獲取 Intercept 注解
            Method proxyMethod = ((MethodSignature) point.getSignature()).getMethod();
            Method targetMethod = point.getTarget().getClass().getMethod(proxyMethod.getName(), proxyMethod.getParameterTypes());
            Intercept intercept = targetMethod.getAnnotation(Intercept.class);
            String methodName = targetMethod.getName();
            //處理注解邏輯
            String value = intercept.value();
            System.err.println("=========== " + methodName + " 獲取前置攔截信息 ===========" + value);
            return point.proceed();
        } catch (Throwable e) {
            System.out.println("執(zhí)行異常"+ e.getMessage());
        }finally {
            System.err.println("=========== " + " 后置處理結(jié)果返回 ===========");
        }
        return "執(zhí)行異常,請(qǐng)查看詳細(xì)日志信息";
    }
}

自定義攔截器配置類

/**
 * @ClassName: AspectJConfig
 * @Description: 開(kāi)啟Spring對(duì)AspectJ的支持
 * @Author: 尚先生
 * @CreateDate: 2019/6/17 18:39
 * @Version: 1.0
 */
@Configuration
@ComponentScan("com.learn.demo.java.proxy")
@EnableAspectJAutoProxy
public class AspectJConfiguration {
}

啟動(dòng)引導(dǎo)類

/**
 * @ClassName: Bootstrap
 * @Description: 啟動(dòng)測(cè)試類
 * @Author: 尚先生
 * @CreateDate: 2019/6/17 14:58
 * @Version: 1.0
 */
public class Bootstrap {
    public static void main(String[] args) {
        // spring 采用的 jdk 動(dòng)態(tài)代理
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

        context.scan("com.learn.demo.java.proxy");

        context.register(Interceptor.class);

        context.refresh();

        BuyService bean = context.getBean("buyServiceImpl", BuyService.class);

        String phone = bean.buyPhone(bean);

        System.err.println("=========Bootstrap.class============== " + phone);

        // 輸出代理對(duì)象 class 文件
        createProxyClassFile();
    }

    /**
     * 生成代理文件
     */
    private static void createProxyClassFile() {
        String name = "ProxyBuyService";
        byte[] data = ProxyGenerator.generateProxyClass(name,
                new Class[] {BuyService.class});
        FileOutputStream out = null;
        try {
            out = new FileOutputStream("D://" + name + ".class");
            out.write(data);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null != out)
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
        }
    }
}

執(zhí)行結(jié)果

=========== buyPhone 獲取前置攔截信息 ===========buyPhone
==========當(dāng)前類描述 com.sun.proxy.$Proxy22============= buyPhone
===========  后置處理結(jié)果返回 ===========
=========Bootstrap.class============== buy phone
==========當(dāng)前類描述 com.learn.demo.java.proxy.BuyServiceImpl============= buyComputer

流程分析

  • 從執(zhí)行結(jié)果中可以明確看到在buyPhone()中執(zhí)行的對(duì)象com.sun.proxy.$Proxy22骚勘,而后執(zhí)行this.buyComputer(this);執(zhí)行的對(duì)象變?yōu)閏om.learn.demo.java.proxy.BuyServiceImpl铐伴,所以在面向切面編程時(shí)只會(huì)攔截buyPhone()方法。

  • 主要是生成的代理對(duì)象跟被代理對(duì)象不是同一個(gè)俏讹,所以后者調(diào)用類方法就像是直接調(diào)用当宴,不會(huì)走切面。

  • 代碼已經(jīng)在GitHub中更新泽疆,更多詳情可關(guān)注dwyanewede户矢。更多JDK動(dòng)態(tài)代理,可參見(jiàn)上篇文章:JDK動(dòng)態(tài)代理實(shí)現(xiàn)原理

更多優(yōu)秀文章

java界的小學(xué)生
https://blog.csdn.net/shang_xs

公眾號(hào)推薦

在這里插入圖片描述
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末殉疼,一起剝皮案震驚了整個(gè)濱河市梯浪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌瓢娜,老刑警劉巖挂洛,帶你破解...
    沈念sama閱讀 211,639評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異恋腕,居然都是意外死亡抹锄,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門荠藤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)伙单,“玉大人,你說(shuō)我怎么就攤上這事哈肖∥怯” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,221評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵淤井,是天一觀的道長(zhǎng)布疼。 經(jīng)常有香客問(wèn)我,道長(zhǎng)币狠,這世上最難降的妖魔是什么游两? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,474評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮漩绵,結(jié)果婚禮上贱案,老公的妹妹穿的比我還像新娘。我一直安慰自己止吐,他們只是感情好宝踪,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布侨糟。 她就那樣靜靜地躺著,像睡著了一般瘩燥。 火紅的嫁衣襯著肌膚如雪秕重。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,816評(píng)論 1 290
  • 那天厉膀,我揣著相機(jī)與錄音溶耘,去河邊找鬼。 笑死站蝠,一個(gè)胖子當(dāng)著我的面吹牛汰具,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播菱魔,決...
    沈念sama閱讀 38,957評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼留荔,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了澜倦?” 一聲冷哼從身側(cè)響起聚蝶,我...
    開(kāi)封第一講書(shū)人閱讀 37,718評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎藻治,沒(méi)想到半個(gè)月后碘勉,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,176評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡桩卵,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評(píng)論 2 327
  • 正文 我和宋清朗相戀三年验靡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片雏节。...
    茶點(diǎn)故事閱讀 38,646評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡胜嗓,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出钩乍,到底是詐尸還是另有隱情辞州,我是刑警寧澤,帶...
    沈念sama閱讀 34,322評(píng)論 4 330
  • 正文 年R本政府宣布寥粹,位于F島的核電站变过,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏涝涤。R本人自食惡果不足惜媚狰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望阔拳。 院中可真熱鬧哈雏,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,755評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)罪针。三九已至彭羹,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間泪酱,已是汗流浹背派殷。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,987評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留墓阀,地道東北人毡惜。 一個(gè)月前我還...
    沈念sama閱讀 46,358評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像斯撮,于是被迫代替她去往敵國(guó)和親经伙。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容