手搓代碼-從Java動態(tài)代理到AOP

關(guān)鍵字 java 代理模式 動態(tài)代理 自定義注解 泛型使用 AOP springboot

1.java靜態(tài)代理

遵循代理模式的思想沮稚,當(dāng)一個對象需要完成某種操作封拧,但是對象本身有不方便去完成的時候眨八,對象可以委托給代理對象去完成相關(guān)操作拴曲。

示例代碼:
假設(shè):boss發(fā)現(xiàn)了系統(tǒng)的bug ,但是boss 修復(fù)不了坑鱼,于是讓可以修復(fù)問題的worker去修改智什。

第一步: 首先要有boss

public class Boss {
}

第二步:要有worker

public class Worker {
}

第三步:worker要有修復(fù)問題的能力蛾茉,這里把這個修復(fù)問題的能力抽象成一個接口

public interface FixBug {
    void fixBug();
}

第四步:那有能力修復(fù)問題的worker就要實現(xiàn)這個FixBug的接口,并實現(xiàn)fixBug方法撩鹿。

public class Worker implements FixBug {
    @Override
    public void fixBug() {
        System.out.println("worker 正在修復(fù) 問題 ....");
    }
}

第五步:boss要擁有這個worker谦炬,這里把worker當(dāng)做是boss的一個字段(屬性)。

public class Boss {
    // 這里要求的是一個有修復(fù)問題能力的worker所以就直接是定義FixBug的字段
    private FixBug worker;

    public Boss(FixBug fixBug) {
        this.worker = fixBug;
    }

    public void fixBug(){
        System.out.println("boss 要讓worker 去修改問題");
        this.worker.fixBug();
        System.out.println("boss 的worker 已經(jīng)修復(fù)了問題");
    }
}

第六步:測試

public class Main {
    public static void main(String[] args) {
        FixBug worker = new Worker();
        Boss boss = new Boss(worker);
        boss.fixBug();
    }
}
程序輸出:
boss 要讓worker 去修改問題
worker 正在修復(fù) 問題 ....
boss 的worker 已經(jīng)修復(fù)了問題

以上就是個人理解的代理模式节沦。

2.動態(tài)代理

動態(tài)代理的優(yōu)勢是不需要像靜態(tài)代理一樣硬編碼键思。jdk支持在編譯階段動態(tài)去生成代理對象。

依然是上面的那個場景甫贯。

第一步:還是這個修改bug的能力吼鳞。

public interface FixBug {
    void fixBug();
}

第二步:還是這個擁有修改bug能力的worker

public class Worker implements FixBug {
    @Override
    public void fixBug() {
        System.out.println("worker 正在修復(fù) 問題 ....");
    }
}

第三步:還是一個需要代理的Boss

public class Boss {
}

第四步:確定動態(tài)代理

public class Boss implements InvocationHandler {

    private FixBug worker;

    public Boss(FixBug fixBug) {
        this.worker = fixBug;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("worker開始修復(fù)問題了");
        Object invoke = method.invoke(worker, args);
        System.out.println("worker修復(fù)問題完成了");
        return invoke;
    }
}

到了這一步,其實是確定了代理關(guān)系叫搁。
Boss可以監(jiān)控到worker的方法執(zhí)行赔桌。也就是說這里boss變成了worker的代理了。
不過這個實例走到這里代理關(guān)系好像變成了Boss是worker的代理渴逻,不過也不是重點 疾党,重點是看動態(tài)代理的使用。

第五步:執(zhí)行查看效果

public class Main {
    public static void main(String[] args) {
        // 要被代理的對象
        FixBug worker = new Worker();
        // 生成代理
        Boss boss = new Boss(worker);
        // 這一步是獲取到一個被代理的對象 三個參數(shù) 第一個是被代理的對象ClassLoader 惨奕,第二個參數(shù)是 被代理的對象實現(xiàn)的接口雪位,第三個參數(shù)是 代理對象
        FixBug fixBug = (FixBug) Proxy.newProxyInstance(worker.getClass().getClassLoader(), worker.getClass().getInterfaces(), boss);
        // 被代理的對象執(zhí)行方法
        fixBug.fixBug();
    }
}
輸出:
worker開始修復(fù)問題了
worker 正在修復(fù) 問題 ....
worker修復(fù)問題完成了

動態(tài)代理的特點是在編譯過程中 可以動態(tài)生成被代理的對象。

3.總結(jié)整理動態(tài)代理

總結(jié)下jdk動態(tài)代理的特點梨撞。

  1. 被代理對象需要有接口實現(xiàn)雹洗。
    比如:worker實現(xiàn)了FixBug接口香罐。

  2. 動態(tài)代理需要實現(xiàn)InvocationHandler接口,并可以獲取到被代理的對象时肿。
    比如:Boss實現(xiàn)了InvocationHandler接口庇茫,并在初始化時候傳入了要被代理的對象worker。

  3. 通過Proxy.newProxyInstance獲取到被代理的對象后螃成,通過被代理的對象執(zhí)行方法 會被代理對象攔截到港令。
    比如:

FixBug fixBug = (FixBug) Proxy.newProxyInstance(worker.getClass().getClassLoader(), worker.getClass().getInterfaces(), boss);
fixBug.fixBug();
輸出:
worker開始修復(fù)問題了
worker 正在修復(fù) 問題 ....
worker修復(fù)問題完成了

當(dāng)被添加了代理的對象 調(diào)用方法的時候,就會被Boss這個動態(tài)代理獲攔截到方法的執(zhí)行锈颗。

4.動態(tài)代理使用過程總結(jié)

  1. 想要添加動態(tài)代理的對象需至少要實現(xiàn)一個接口。

  2. 動態(tài)代理本身需要實現(xiàn)InvocationHandler咪惠,并可以接收到要被代理的對象,用于發(fā)起方法請求击吱。

  3. 如果要使動態(tài)代理生效,需要使用Proxy.newProxyInstance方法來獲取被添加代理的對象遥昧,并使用這個對象來發(fā)起方法調(diào)用覆醇。

5.封裝動態(tài)代理的使用過程

  1. 第一步: 需要添加動態(tài)代理的對象 可以使用Object來代表。
  2. 第二步: 動態(tài)代理對象的封裝
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class DynamicProxy implements InvocationHandler {

    private Object target;

    public DynamicProxy(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("進(jìn)入方法");
        Object invoke = method.invoke(target, args);
        System.out.println("即將結(jié)束方法");
        return invoke;
    }
}
  1. 第三步:封裝獲取添加代理對象的方法
   public static <T> T getProxyObject(T obj) throws Exception {
        Class<?>[] interfaces1 = obj.getClass().getInterfaces();
        if (interfaces1 == null || interfaces1.length == 0) throw new Exception("要求代理的對象要實現(xiàn)與其對應(yīng)的接口");

        DynamicProxy dynamicProxy = new DynamicProxy(obj);
        ClassLoader classLoader = obj.getClass().getClassLoader();
        Class<?>[] interfaces = obj.getClass().getInterfaces();
        return (T) Proxy.newProxyInstance(classLoader, interfaces, dynamicProxy);
    }
  1. 第四步:使用過程
public static void main(String[] args) throws Exception{
   FixBug fixBug = getProxyObject((FixBug) (new Worker()));
   fixBug.fixBug();
}
輸出結(jié)果:
進(jìn)入方法
worker 正在修復(fù) 問題 ....
即將結(jié)束方法

通過方法的封裝√砍簦現(xiàn)在可以快速實現(xiàn)動態(tài)代理永脓。唯一的問題就是動態(tài)代理攔截到方法之后,做的操作太單一鞋仍。
目前的處理,攔截到調(diào)用

  @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("進(jìn)入方法");
        Object invoke = method.invoke(target, args);
        System.out.println("即將結(jié)束方法");
        return invoke;
    }

6.如何豐富攔截到方法之后的處理常摧?

如果想要去豐富一類操作的處理。一定是通過抽象出這類操作的共同點威创,定義為接口落午,由接口的實現(xiàn)類去做具體的實現(xiàn)。在使用過程中通過選擇不同的實現(xiàn)類來實現(xiàn)對于操作的靈活處理肚豺。

在我們現(xiàn)在的例子中溃斋,可以抽離出三個方法。
1.進(jìn)入方法時
2.退出方法前
3.結(jié)果處理

抽離出接口則是

public interface MethodAroundHandler {
    // 進(jìn)入接口
    void enterMethod();

    // 退出方法前
    void beforReturnMethod();
    // 結(jié)果處理
    <T>T dealResult(T obj);

}

做一個簡單的實現(xiàn)類

public class SimpleMethodAround implements MethodAroundHandler {
    @Override
    public void enterMethod() {
        System.out.println("進(jìn)入方法");
    }

    @Override
    public void beforReturnMethod() {
        System.out.println("方法退出前");
    }

    @Override
    public <T> T dealResult(T obj) {
        System.out.println("攔截到方法的返回值:"+obj);
        return obj;
    }
}

修改之前的DynamicProxy 動態(tài)代理

public class DynamicProxy implements InvocationHandler {

    private Object target;

    public DynamicProxy(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        SimpleMethodAround simpleMethodAround = new SimpleMethodAround();
        simpleMethodAround.enterMethod();
        Object invoke = method.invoke(target, args);
        simpleMethodAround.beforReturnMethod();
        if (invoke != null) simpleMethodAround.dealResult(invoke);
        return invoke;
    }
}

依然還是原來的main方法


public class Main {

    public static <T> T getProxyObject(T obj) throws Exception {
        Class<?>[] interfaces1 = obj.getClass().getInterfaces();
        if (interfaces1 == null || interfaces1.length == 0) throw new Exception("要求代理的對象要實現(xiàn)與其對應(yīng)的接口");
        DynamicProxy dynamicProxy = new DynamicProxy(obj);
        ClassLoader classLoader = obj.getClass().getClassLoader();
        Class<?>[] interfaces = obj.getClass().getInterfaces();
        return (T) Proxy.newProxyInstance(classLoader, interfaces, dynamicProxy);
    }

    public static void main(String[] args) throws Exception{
        FixBug fixBug = getProxyObject((FixBug) (new Worker()));
        fixBug.fixBug();
    }
}
輸出結(jié)果:
進(jìn)入方法
worker 正在修復(fù) 問題 ....
方法退出前

總結(jié)這次的處理吸申。
把原來寫在DynamicProxy中的方法攔擊成功的轉(zhuǎn)移并抽象成了接口梗劫,這樣可以很方便的根據(jù)不同的情況來切換接口實現(xiàn)類。

下面應(yīng)該考慮的是如何去動態(tài)的切換接口實現(xiàn)類截碴。

7.通過注解的方式動態(tài)切換接口實現(xiàn)類

現(xiàn)在需要動態(tài)切換接口實現(xiàn)類的地方是在DynamicProxy 的invoke方法中梳侨。
先回回顧下這個方法

public class DynamicProxy implements InvocationHandler {

    // 被代理的對象
    private Object target;
    // 初始化時傳入對象
    public DynamicProxy(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 初始化接口實現(xiàn)類
        SimpleMethodAround simpleMethodAround = new SimpleMethodAround();
        // 調(diào)用接口方法
        simpleMethodAround.enterMethod();
        // 調(diào)用原方法
        Object invoke = method.invoke(target, args);
        // 調(diào)用接口方法
        simpleMethodAround.beforReturnMethod();
        // 如果歐返回值 可以調(diào)用最后一個接口放方法
        if (invoke != null) simpleMethodAround.dealResult(invoke);
        // 最后返回結(jié)果
        return invoke;
    }
}

在invoke 可以拿到要調(diào)用的方法Method,這樣就可以使用方法注解來指定使用哪個接口實現(xiàn)類日丹,進(jìn)而可以根據(jù)不同的注解來實例化不同的接口實現(xiàn)類猫妙。

這里修改invoke方法實現(xiàn)。主要是修改接口實現(xiàn)類的實例化過程聚凹。

@Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 如果是被注解標(biāo)注的方法 才會被攔截
        if (method.isAnnotationPresent(DynamicProxyMethod.class)) {
            // 獲取到注解
            DynamicProxyMethod annotation = method.getAnnotation(DynamicProxyMethod.class);
            // 獲取到接口實現(xiàn)類
            MethodAroundHandler methodAroundHandler = (MethodAroundHandler) annotation.type().newInstance();
            // 調(diào)用接口方法
            methodAroundHandler.enterMethod();
            Object invoke = method.invoke(target, args);
            methodAroundHandler.beforReturnMethod();
            if (invoke != null) methodAroundHandler.dealResult(invoke);
            return invoke;
        }
        return method.invoke(target, args);
    }

定義方法注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DynamicProxyMethod {
    Class type() default SimpleMethodAround.class;
}

給原來的fixbug方法添加注解

public interface FixBug {
    @DynamicProxyMethod // 默認(rèn)是SimpleMethodAround.class
    void fixBug();
}

還是原來的Main方法

public class Main {

    public static <T> T getProxyObject(T obj) throws Exception {
        Class<?>[] interfaces1 = obj.getClass().getInterfaces();
        if (interfaces1 == null || interfaces1.length == 0) throw new Exception("要求代理的對象要實現(xiàn)與其對應(yīng)的接口");
        DynamicProxy dynamicProxy = new DynamicProxy(obj);
        ClassLoader classLoader = obj.getClass().getClassLoader();
        Class<?>[] interfaces = obj.getClass().getInterfaces();
        return (T) Proxy.newProxyInstance(classLoader, interfaces, dynamicProxy);
    }

    public static void main(String[] args) throws Exception{
        FixBug fixBug = getProxyObject((FixBug) (new Worker()));
        fixBug.fixBug();
    }
}
輸出:
進(jìn)入方法
worker 正在修復(fù) 問題 ....
方法退出前

到此 基本實現(xiàn)了對于方法的攔截 實現(xiàn)了方法進(jìn)入時 返回前 以及 對結(jié)果的處理

8.整理代碼

注解定義

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DynamicProxyMethod {
    Class type() default SimpleMethodAround.class;
}

方法攔截接口定義

public interface MethodAroundHandler {

    void enterMethod();

    void beforReturnMethod();

    <T>T dealResult(T obj);

}

兩個MethodAroundHandler接口實現(xiàn)

public class SimpleMethodAround implements MethodAroundHandler {
    @Override
    public void enterMethod() {
        System.out.println("進(jìn)入方法");
    }

    @Override
    public void beforReturnMethod() {
        System.out.println("方法退出前");
    }

    @Override
    public <T> T dealResult(T obj) {
        System.out.println("攔截到方法的返回值:"+obj);
        return obj;
    }
}

public class SimpleMethodAround2 implements MethodAroundHandler {
    @Override
    public void enterMethod() {
        System.out.println("222 進(jìn)入方法");
    }

    @Override
    public void beforReturnMethod() {
        System.out.println("222 方法退出前");
    }

    @Override
    public <T> T dealResult(T obj) {
        System.out.println("222 攔截到方法的返回值:"+obj);
        return obj;
    }
}

要攔截的對象要實現(xiàn)一個接口

public interface FixBug {

    @DynamicProxyMethod // 注解標(biāo)注要使用哪個MethodAroundHandler接口實現(xiàn)類
    void fixBug();

    @DynamicProxyMethod(type = SimpleMethodAround2.class) // 注解標(biāo)注要使用哪個MethodAroundHandler接口實現(xiàn)類
    String coding();
}

要被動態(tài)代理的對象

public class Worker implements FixBug {

    @Override
    public void fixBug() {
        System.out.println("worker 正在修復(fù) 問題 ....");
    }

    @Override
    public String coding() {
        return "worker 正在coding .....";
    }
}

動態(tài)代理實現(xiàn)

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class DynamicProxy implements InvocationHandler {

    private Object target;

    public DynamicProxy(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 如果是被注解標(biāo)注的方法 才會被攔截
        if (method.isAnnotationPresent(DynamicProxyMethod.class)) {
            // 獲取到注解
            DynamicProxyMethod annotation = method.getAnnotation(DynamicProxyMethod.class);
            // 獲取到接口實現(xiàn)類
            MethodAroundHandler methodAroundHandler = (MethodAroundHandler) annotation.type().newInstance();
            // 調(diào)用接口方法
            methodAroundHandler.enterMethod();
            Object invoke = method.invoke(target, args);
            methodAroundHandler.beforReturnMethod();
            if (invoke != null) methodAroundHandler.dealResult(invoke);
            return invoke;
        }
        return method.invoke(target, args);
    }
}

Main函數(shù)


public class Main {

    public static <T> T getProxyObject(T obj) throws Exception {
        Class<?>[] interfaces1 = obj.getClass().getInterfaces();
        if (interfaces1 == null || interfaces1.length == 0) throw new Exception("要求代理的對象要實現(xiàn)與其對應(yīng)的接口");
        DynamicProxy dynamicProxy = new DynamicProxy(obj);
        ClassLoader classLoader = obj.getClass().getClassLoader();
        Class<?>[] interfaces = obj.getClass().getInterfaces();
        return (T) Proxy.newProxyInstance(classLoader, interfaces, dynamicProxy);
    }

    public static void main(String[] args) throws Exception{
        FixBug fixBug = getProxyObject((FixBug) (new Worker()));
        fixBug.fixBug();
        fixBug.coding();
    }
}
最終輸出:
進(jìn)入方法
worker 正在修復(fù) 問題 ....
方法退出前
222 進(jìn)入方法
222 方法退出前
222 攔截到方法的返回值:worker 正在coding .....

9. 最后MARK下

這個思路的實現(xiàn)割坠,我封裝了springboot-starter-dynamic-aop齐帚。
與文章中所講的思路略微有些不同。
主要是利用了spring的Bean容器特性彼哼。來幫助創(chuàng)建動態(tài)代理对妄。
githup地址: https://github.com/LH-0811/lh-springboot-starter-dynamic-aop

10.最后的最后

寫了很多,希望大家能提出意見敢朱。指出我在思想上的不足剪菱。期待共同進(jìn)步!拴签!謝謝大家孝常。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市蚓哩,隨后出現(xiàn)的幾起案子构灸,更是在濱河造成了極大的恐慌,老刑警劉巖岸梨,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件喜颁,死亡現(xiàn)場離奇詭異,居然都是意外死亡曹阔,警方通過查閱死者的電腦和手機(jī)半开,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赃份,“玉大人寂拆,你說我怎么就攤上這事∽ズ” “怎么了漓库?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長园蝠。 經(jīng)常有香客問我渺蒿,道長,這世上最難降的妖魔是什么彪薛? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任茂装,我火速辦了婚禮,結(jié)果婚禮上善延,老公的妹妹穿的比我還像新娘少态。我一直安慰自己,他們只是感情好易遣,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布彼妻。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪侨歉。 梳的紋絲不亂的頭發(fā)上屋摇,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機(jī)與錄音幽邓,去河邊找鬼炮温。 笑死,一個胖子當(dāng)著我的面吹牛牵舵,可吹牛的內(nèi)容都是我干的柒啤。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼畸颅,長吁一口氣:“原來是場噩夢啊……” “哼担巩!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起没炒,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤涛癌,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后窥浪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡笛丙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年漾脂,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片胚鸯。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡骨稿,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出姜钳,到底是詐尸還是另有隱情坦冠,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布哥桥,位于F島的核電站辙浑,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏拟糕。R本人自食惡果不足惜判呕,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望送滞。 院中可真熱鬧侠草,春花似錦、人聲如沸犁嗅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至功蜓,卻和暖如春园爷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背霞赫。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工腮介, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人端衰。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓叠洗,卻偏偏與公主長得像,于是被迫代替她去往敵國和親旅东。 傳聞我的和親對象是個殘疾皇子灭抑,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345

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