代理(Proxy)的解析

韓師學子--胖佳發表於2019-02-26

                       代理(Proxy)的解析

 

轉載:https://www.cnblogs.com/xdp-gacl/p/3971367.html
 

一、代理的概念

  動態代理技術是整個java技術中最重要的一個技術,它是學習java框架的基礎,不會動態代理技術,那麼在學習Spring這些框架時是學不明白的。

  動態代理技術就是用來產生一個物件的代理物件的。在開發中為什麼需要為一個物件產生代理物件呢?
  舉一個現實生活中的例子:歌星或者明星都有一個自己的經紀人,這個經紀人就是他們的代理人,當我們需要找明星表演時,不能直接找到該明星,只能是找明星的代理人。比如劉德華在現實生活中非常有名,會唱歌,會跳舞,會拍戲,劉德華在沒有出名之前,我們可以直接找他唱歌,跳舞,拍戲,劉德華出名之後,他乾的第一件事就是找一個經紀人,這個經紀人就是劉德華的代理人(代理),當我們需要找劉德華表演時,不能直接找到劉德華了(劉德華說,你找我代理人商談具體事宜吧!),只能是找劉德華的代理人,因此劉德華這個代理人存在的價值就是攔截我們對劉德華的直接訪問!
  這個現實中的例子和我們在開發中是一樣的,我們在開發中之所以要產生一個物件的代理物件,主要用於攔截對真實業務物件的訪問。那麼代理物件應該具有什麼方法呢?代理物件應該具有和目標物件相同的方法

  所以在這裡明確代理物件的兩個概念:
    1、代理物件存在的價值主要用於攔截對真實業務物件的訪問
    2、代理物件應該具有和目標物件(真實業務物件)相同的方法。劉德華(真實業務物件)會唱歌,會跳舞,會拍戲,我們現在不能直接找他唱歌,跳舞,拍戲了,只能找他的代理人(代理物件)唱歌,跳舞,拍戲,一個人要想成為劉德華的代理人,那麼他必須具有和劉德華一樣的行為(會唱歌,會跳舞,會拍戲),劉德華有什麼方法,他(代理人)就要有什麼方法,我們找劉德華的代理人唱歌,跳舞,拍戲,但是代理人不是真的懂得唱歌,跳舞,拍戲的,真正懂得唱歌,跳舞,拍戲的是劉德華,在現實中的例子就是我們要找劉德華唱歌,跳舞,拍戲,那麼只能先找他的經紀人,交錢給他的經紀人,然後經紀人再讓劉德華去唱歌,跳舞,拍戲。

二、java中的代理

2.1、"java.lang.reflect.Proxy"類介紹

  現在要生成某一個物件的代理物件,這個代理物件通常也要編寫一個類來生成,所以首先要編寫用於生成代理物件的類。在java中如何用程式去生成一個物件的代理物件呢,java在JDK1.5之後提供了一個"java.lang.reflect.Proxy"類,通過"Proxy"類提供的一個newProxyInstance方法用來建立一個物件的代理物件,如下所示:
 

static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 

 

newProxyInstance方法用來返回一個代理物件,這個方法總共有3個引數,ClassLoader loader用來指明生成代理物件使用哪個類裝載器,Class<?>[] interfaces用來指明生成哪個物件的代理物件,通過介面指定,InvocationHandler h用來指明產生的這個代理物件要做什麼事情。所以我們只需要呼叫newProxyInstance方法就可以得到某一個物件的代理物件了。

2.2、編寫生成代理物件的類

  在java中規定,要想產生一個物件的代理物件,那麼這個物件必須要有一個介面,所以我們第一步就是設計這個物件的介面,在介面中定義這個物件所具有的行為(方法)

  1、定義物件的行為介面

package cn.gacl.proxy;

/**
* @ClassName: Person
* @Description: 定義物件的行為
* @author: 孤傲蒼狼
* @date: 2014-9-14 下午9:44:22
*
*/ 
public interface Person {

    /**
    * @Method: sing
    * @Description: 唱歌
    * @Anthor:孤傲蒼狼
    *
    * @param name
    * @return
    */ 
    String sing(String name);
    /**
    * @Method: sing
    * @Description: 跳舞
    * @Anthor:孤傲蒼狼
    *
    * @param name
    * @return
    */ 
    String dance(String name);
}

2、定義目標業務物件類
 

package cn.gacl.proxy;

/**
* @ClassName: LiuDeHua
* @Description: 劉德華實現Person介面,那麼劉德華會唱歌和跳舞了
* @author: 孤傲蒼狼
* @date: 2014-9-14 下午9:22:24
*
*/ 
public class LiuDeHua implements Person {

    public String sing(String name){
        System.out.println("劉德華唱"+name+"歌!!");
        return "歌唱完了,謝謝大家!";
    }
    
    public String dance(String name){
        System.out.println("劉德華跳"+name+"舞!!");
        return "舞跳完了,多謝各位觀眾!";
    }
}


3、建立生成代理物件的代理類
 

package cn.gacl.proxy;

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

/**
* @ClassName: LiuDeHuaProxy
* @Description: 這個代理類負責生成劉德華的代理人
* @author: 孤傲蒼狼
* @date: 2014-9-14 下午9:50:02
*
*/ 
public class LiuDeHuaProxy {

    //設計一個類變數記住代理類要代理的目標物件
    private Person ldh = new LiuDeHua();
    
    /**
    * 設計一個方法生成代理物件
    * @Method: getProxy
    * @Description: 這個方法返回劉德華的代理物件:Person person = LiuDeHuaProxy.getProxy();//得到一個代理物件
    * @Anthor:孤傲蒼狼
    *
    * @return 某個物件的代理物件
    */ 
    public Person getProxy() {
        //使用Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)返回某個物件的代理物件
        return (Person) Proxy.newProxyInstance(LiuDeHuaProxy.class
                .getClassLoader(), ldh.getClass().getInterfaces(),
                new InvocationHandler() {
                    /**
                     * InvocationHandler介面只定義了一個invoke方法,因此對於這樣的介面,我們不用單獨去定義一個類來實現該介面,
                     * 而是直接使用一個匿名內部類來實現該介面,new InvocationHandler() {}就是針對InvocationHandler介面的匿名實現類
                     */
                    /**
                     * 在invoke方法編碼指定返回的代理物件乾的工作
                     * proxy : 把代理物件自己傳遞進來 
                     * method:把代理物件當前呼叫的方法傳遞進來 
                     * args:把方法引數傳遞進來
                     * 
                     * 當呼叫代理物件的person.sing("冰雨");或者 person.dance("江南style");方法時,
                     * 實際上執行的都是invoke方法裡面的程式碼,
                     * 因此我們可以在invoke方法中使用method.getName()就可以知道當前呼叫的是代理物件的哪個方法
                     */
                    @Override
                    public Object invoke(Object proxy, Method method,
                            Object[] args) throws Throwable {
                        //如果呼叫的是代理物件的sing方法
                        if (method.getName().equals("sing")) {
                            System.out.println("我是他的經紀人,要找他唱歌得先給十萬塊錢!!");
                            //已經給錢了,經紀人自己不會唱歌,就只能找劉德華去唱歌!
                            return method.invoke(ldh, args); //代理物件呼叫真實目標物件的sing方法去處理使用者請求
                        }
                        //如果呼叫的是代理物件的dance方法
                        if (method.getName().equals("dance")) {
                            System.out.println("我是他的經紀人,要找他跳舞得先給二十萬塊錢!!");
                            //已經給錢了,經紀人自己不會唱歌,就只能找劉德華去跳舞!
                            return method.invoke(ldh, args);//代理物件呼叫真實目標物件的dance方法去處理使用者請求
                        }

                        return null;
                    }
                });
    }
}

測試程式碼:
 

package cn.gacl.proxy;

public class ProxyTest {
    
    public static void main(String[] args) {
        
        LiuDeHuaProxy proxy = new LiuDeHuaProxy();
        //獲得代理物件
        Person p = proxy.getProxy();
        //呼叫代理物件的sing方法
        String retValue = p.sing("冰雨");
        System.out.println(retValue);
        //呼叫代理物件的dance方法
        String value = p.dance("江南style");
        System.out.println(value);
    }
}


 執行結果如下:


Proxy類負責建立代理物件時,如果指定了handler(處理器),那麼不管使用者呼叫代理物件的什麼方法,該方法都是呼叫處理器的invoke方法。
  由於invoke方法被呼叫需要三個引數:代理物件、方法、方法的引數,因此不管代理物件哪個方法呼叫處理器的invoke方法,都必須把自己所在的物件、自己(呼叫invoke方法的方法)、方法的引數傳遞進來。
 

自己所在的物件、自己(呼叫invoke方法的方法)、方法的引數傳遞進來。

三、動態代理應用

  在動態代理技術裡,由於不管使用者呼叫代理物件的什麼方法,都是呼叫開發人員編寫的處理器的invoke方法(這相當於invoke方法攔截到了代理物件的方法呼叫)。並且,開發人員通過invoke方法的引數,還可以在攔截的同時,知道使用者呼叫的是什麼方法,因此利用這兩個特性,就可以實現一些特殊需求,例如:攔截使用者的訪問請求,以檢查使用者是否有訪問許可權、動態為某個物件新增額外的功能。

3.1、在字元過濾器中使用動態代理解決中文亂碼

3.2、在字元過濾器中使用動態代理壓縮伺服器響應的內容後再輸出到客戶端


詳情見首頁頁面的轉載,有點懶,哈哈哈哈哈
 

相關文章