超簡單,超詳細,超快速,一下就懂Java(JDK中的)代理模式

代理模式是指,爲其他對象提供一種代理以控制這個對象的訪問.在某些情況下,一個對象不適合或者不能直接引用另一個對象,而代理對象可以在客戶類和目標類對象之間起到中介的作用.


1.1靜態代理

先上代碼有個直觀感受

1.創建一個接口

//主業務方法:本接口中的方法將要被代理增強
public interface ISomeService {
    String doFirst();
    void doSecond();
}

2.再創建一個類實現這個接口(我們稱他爲目標類)

//目標類:代理類需要增強的類
public class ISomeServiceImpl implements ISomeService {
    @Override
    public String doFirst() {
        System.out.println("執行doFirst");
        return "abcde";
    }
    @Override
    public void doSecond() {
        System.out.println("執行doSeconed");
    }
}

現在我想怎麼做呢?我想讓兩個ISomeServiceImpl的實例化對象分別執行這兩個方法,但是第二個對象在執行doFirst()的時候能夠把打印出來的字母變成大寫(功能增強).
於是我們的代理濃重登場
3.創建一個代理類

//靜態代理類
public class ServiceProxy implements ISomeService {     //實現相同的接口

    private ISomeService target;
    public ServiceProxy() {
        target = new ISomeServiceImpl();                 //先來一個ISomeService的實例化對象
    }
    @Override
    public String doFirst() {
        // 調用目標對象的目標方法,該方法返回全小寫字母
        String result = target.doFirst();
        // 增強:在這裏將目標方法全小寫字母變成全大寫
        result = result.toUpperCase();
        return result;
    }
    @Override
    public void doSecond() {
        target.doSecond();                                //在這裏並沒有做更多的操作,代理的方法調用目標類的方法
    }
}

4.建個test來測試測試吧

public class MyTest {
    public static void main(String[] args) {
        // 創建一個目標對象
        ISomeService iservice = new ISomeServiceImpl();
        System.out.println(iservice.doFirst());          // print:"執行doFirst"和"abcde"
        iservice.doSecond();                             // 執行doSeconed,print:執行doSeconed

        //同時我再創建一個代理類的話..
        ISomeService pservice = new ServiceProxy();
        System.out.println(pservice.doFirst());          // print:執行doFirst  ABCDE
        pservice.doSecond();                             // 還是print:執行doSeconed
    }
}

看了這個例子,是不是覺得靜態代理不再神祕了?那麼接下來,再把它小改進:在代理類中增加一個有參構造

//靜態代理類
public class ServiceProxy implements ISomeService {
    private ISomeService target;//還是先創建一個目標內的對象,代理人?哈哈
    public ServiceProxy() {
        target = new ISomeServiceImpl();
    }
    public ServiceProxy(ISomeService target) {//創建一個有參構造,傳入目標類對象
        super();
        this.target = target;
    }
    @Override
    //下面和之前一樣
    public String doFirst() {
        // 調用目標對象的目標方法,該方法返回全小寫字母
        String result = target.doFirst();
        // 增強:將目標方法全小寫字母變成全大寫
        result = result.toUpperCase();
        return result;
    }
    @Override
    public void doSecond() {
        target.doSecond();
    }
}

對代理類做了這樣的改變後,在測試類我們就可以這樣做了:

public class MyTest {
    public static void main(String[] args) {
        ISomeService target = new ISomeServiceImpl();
        ISomeService service = new ServiceProxy(target);
        System.out.println(service.doFirst());// print:執行doFirst ABCDE
        //假設我還想調代理前的方法..
        System.out.println(target.doFirst()); //print:執行doFirst abcde
        service.doSecond();
    }
}

是不是感覺代理類和目標類的關係更加緊密0
了呢?

1.2動態代理

動態代理和靜態代理的區別就是:靜態代理有代理類,動態代理麼有代理類.沒有了律師團,怎麼幫我打官司呢?當然只好請律師了
接口和實現類不變,但是我把ServiceProxy類刪掉
直接上test類

public class MyTest {
    public static void main(String[] args) {
        final ISomeService target = new ISomeServiceImpl(); // 在內部類中不能引用一個非final的變量,這點需要注意

        //重點來了:
        ISomeService service = (ISomeService) Proxy.newProxyInstance(target
                .getClass().getClassLoader(),      // 目標類的類加載器
                 target.getClass().getInterfaces(),// 目標類所實現的所有接口
                new InvocationHandler() {// 口怕,第三個參數居然是匿名內部類..其實就是一個實現了InvocationHandler接口的類

                    // proxy:代理對象
                    // method:目標方法
                    // args:目標方法的參數列表
                    @Override
                    public Object invoke(Object proxy, Method method,
                            Object[] args) throws Throwable {
                        Object result = method.invoke(target, args);// 這裏運用到了反射的知識
                        String stresult = result.toString().toUpperCase();
                        return stresult;
                    }
                });

        System.out.println(service.doFirst());// print:"執行doFirst"和"ABCDE"
        System.out.println(target.doFirst());
    }

下面我們把那個最長的方法處理出來看看:

newProxyInstance(ClassLoader loader, 
                 Class[] interfaces, 
                 InvocationHandler h)

其中:
interfaces - 代理類要實現的接口列表
h - 指派方法調用的調用處理程序
返回一個指定接口的代理類實例,該接口可以將方法調用指派到指定的調用處理程序。

在前面靜態代理例子中:

ISomeService service = new ServiceProxy(target);

我們通過代理類的構造方法得到一個代理類實例,通過這個代理對象執行它自己類中代理的方法從而得到目的.

那我們回過頭來看看動態代理:
newProxyInstance()方法同樣可以返回一個指定接口的代理類實例,只不過是Object類型的,所以要轉型爲代理類接口.

ISomeService service = (ISomeService)Proxy.newProxyInstance(ClassLoader loader,
                                                            Class<?>[] interfaces,
                                                            InvocationHandler h)

接着我們再重點關注一下第三個參數:
再簡單說一下:第三個參數實際上是一個InvocationHandler的接口的實例,單接口不允許直接創建實例,所以我們創建一個匿名類不理來實現這個接口
進一步發現,這個InvocationHandler接口實際只有一個方法:

invoke(Object proxy, Method method,Object[] args)

invoke方法在代理實例上處理方法調用並返回結果。在與方法關聯的代理實例上調用方法時,將在調用處理程序上調用此方法。 (這是JDK的中文API文檔說的…)
這就是方法增強的關鍵:
有invoke,有method,是不是想到了反射?
於是我們使用反射的invoke方法:

method.invoke(target, args);

這裏傳入代理對象以及參數,反射的method在newProxyInstance中傳入的interfaces確定.具體就看最後調用哪個了.對反射有疑問的戳這裏(Java反射的使用入門詳解)
簡言之,這一個方法執行了,就相當於目標類的方法執行了.
在本例子中,doFirst()是返回一個String類型,不過這裏要求用Object來接收.

Object result = method.invoke(target, args);

接下來就進行增強:

String stresult = result.toString().toUpperCase();

最後將增強後的結果返回

return stresult;//終於把原來的doFirst()方法增強了

到這裏就解釋得差不多了.現在我調service.doFirst()得到的絕對是大寫了哈哈哈!!!



但是還沒完!我用service.doSecond()時卻報了空指針異常..怎麼回事呢?
原來我們在執行反射的invoke方法時,默認它是有返回值的(不如上面的result).
但doSeconed()卻沒有返回值,所以result=null.
然後null執行result.toString().toUpperCase()就報錯了.
如何解決?
當然是在執行方法增強之前先判斷一下result是否爲空就好,不爲空才執行(就是套一個if)

                    @Override
                    public Object invoke(Object proxy, Method method,
                            Object[] args) throws Throwable {
                        Object result = method.invoke(target, args);// 這裏運用到了反射的知識
                        if (result != null)
                            result = result.toString().toUpperCase();
                        return result;
                    }

就是這樣,喵~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章