1. 程式人生 > 實用技巧 >【朝花夕拾】寫一個動態代理類

【朝花夕拾】寫一個動態代理類

什麼是動態代理:不管我們的物件是什麼,我都可以通過這樣一個類去生成一個相應的代理物件,幫助開發者去完成功能,和目標物件(被代理者)沒有任何關係

準備步驟:

1.建立有一個介面,裡面定義了一個或多個抽象方法

package com.atguigu.spring.proxy;

public interface Math {
    int add(int a,int b);
    int sub(int a,int b);
    int mul(int a,int b);
    int div(int a,int b);
}

2.建立一個實現類(即我們的被代理類),實現這個介面,並重寫了方法

package com.atguigu.spring.proxy;

public class MathImpl implements Math {
    @Override
    public int add(int a, int b) {
        return a+b;
    }

    @Override
    public int sub(int a, int b) {
        return a-b;
    }

    @Override
    public int mul(int a, int b) {
        return a*b;
    }

    @Override
    
public int div(int a, int b) { return a/b; } }

開始寫代理類步驟:

1.建立一個能夠獲取代理類的工具類

思路分析:動態代理的目的是獲取一個代理類來完成工作

獲取代理類java有一個通用的方法就是使用Proxy類的靜態方法newProxyInstance

  該方法返回一個代理類,所以是有返回值的,一般為Object,和本文首行的動態代理的定義相呼應,但是需要傳入三個引數

    引數1:類載入器 引數2:被代理者介面的Class物件陣列 引數3:一個代理類處理器

    引數1分析:我們要載入一個類需要類載入器,而代理類就是我們需要載入的類,所以我們需要傳入一個類載入器

         那麼這個類載入器傳什麼比較好,因為工具類也是由類載入器載入的,所以我們直接this.getClass().getClassLoader()來獲取類載入器,

         其實這個類載入器屬於應用程式類載入器,如果你使用工具類中存在的被代理者物件的getClass().getClassLoader()也是一樣的效果

    引數2分析:代理類需要完成的是和被代理者相同的事,只不過加了一些其他的業務邏輯,那麼被代理者繼承了介面,重寫了方法,同理,我代理類

          也需要繼承它繼承過的介面,這樣被代理者重寫過的方法,代理類也需要重寫,就能達到呼叫相同業務邏輯的目的

    引數3分析:一個代理類處理器,這個處理器就是幫助我們完成業務邏輯的,我們一般使用匿名內部類來完成傳參,匿名內部類中需要重寫一個invoke

  方法,該方法有三個形參,分別會傳入代理類物件,重寫的方法物件和方法的引數。可能會疑問為什麼這裡的方法物件不是陣列呢,因為

  我們呼叫一個方法是一個個呼叫的,不能一下子把所有方法全呼叫了,就比如使用方法是mathImpl.add(),總不能mathimpl.add[]()或者

  mathimpl.add().sub().div()這樣的傳。第三個引數就是介面方法中可能需要傳遞的引數了。invoke()方法的形參傳完之後,invoke()方法的方法體

  就是進行方法呼叫,這裡是反射的知識,即method.invoke(被呼叫的方法所屬的物件,引數),有返回值或者沒返回值都return一下

  迴歸以上,在上面這個方法呼叫獲取代理類的時候,我們有一個點沒有提到,那就是被代理者,在上面的newProxyInstance()的引數二和method.invoke()裡面的引數

  都會用到被代理者的物件,這也說明了工具類中需要持有被代理者的物件,用於呼叫它的方法,因為動態代理的核心業務邏輯仍是被代理類的方法完成的。所以工具

  類中需要定義一個被代理物件的屬性,並寫出有參構造,才能在建立代理工具類的同時賦值一個被代理者物件

程式碼如下

package com.atguigu.spring.proxy;

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

public class ProxyUtil {
    private MathImpl mathImpl;

    public ProxyUtil(MathImpl mathImpl) {
        this.mathImpl = mathImpl;
    }


    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), mathImpl.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                return method.invoke(mathImpl,args);
            }
        });
    }
}

2.測試類

package com.atguigu.spring.proxy;

public class ProxyTest {
    public static void main(String[] args) {

        ProxyUtil proxyUtil = new ProxyUtil(new MathImpl());
        Math proxy = (Math)proxyUtil.getProxy();
        System.out.println(proxy.add(1,1));
    }
}

程式碼分析,首先建立了一個代理類物件,給物件中的屬性:被代理者物件 賦值,之後呼叫獲取代理類物件的方法,這個代理類物件是實現了和被代理類物件MathImpl相同的介面Math的,因此需要進行型別轉換,之後呼叫方法就可以

原理如下,看上去有些亂,其實理解了很簡單,畫的不好見諒