23種設計模式——代理模式

代理模式(結構型模式)

代理模式(Proxy Pattern):所謂代理模式,就是爲A提供一個B對象以控制對A的訪問。這種類型的設計模式屬於結構型模式。在代理模式中,我們創建具有現有對象的對象,並執行現有對象的相關方法,以便向外界提供功能接口。一般代理模式主要解決直接訪問對象不合適的情形。比如說:要訪問的對象在遠程的機器上。在面向對象系統中,有些對象由於某些原因(比如對象創建開銷很大,或者某些操作需要安全控制,或者需要進程外的訪問),直接訪問會給使用者或者系統結構帶來很多麻煩,我們可以在訪問此對象時加上一個對此對象的訪問層,即使用代理模式。

代理模式的應用實例:windows裏面的快捷方式,spring aop,遠程代理,虛擬代理,防火牆代理等。

代理是一種常用的設計模式,其目的就是爲其他對象提供一個代理以控制對某個對象的訪問。代理類負責爲委託類預處理消息,過濾消息並轉發消息,以及進行消息被委託類執行後的後續處理。

在代理設計模式中,雖然可以實現在不修改真實方法的源碼基礎上完成功能的擴展,但是我需要改變調用方式由直接調用真實對象的真實方法,變成調用代理對象的代理方法。爲了方便我們進行替換,一般我們會將代理方法和真實方法設計得方法名和形參和返回值一樣,爲了規範代碼的編寫,由如下兩種實現:

(1)JDK動態代理真實對象和代理對象實現相同的接口。JDK動態代理只能代理接口,真實對象必須有父接口。

(2)Cglib動態代理代理對象繼承真實對象。Cglib動態代理採用ASM字節碼生成框架,使用字節碼技術生成代理類,效率更高,但是CGLib不能對final修飾的方法進行代理,因爲Cglib是動態生成被代理類的子類,而final修飾的方法不能被繼承。

1.基於接口的JDK動態代理

在java的動態代理機制中,有兩個重要的類和接口。一個是InvocationHandler、另一個則是Proxy,這一個類和接口是實現動態代理所必須用到的。

(1)InvocationHandler:每一個動態代理類都必須要實現InvocationHandler這個接口,並且每個代理類的實例都關聯了一個handler(處理器),當我們動態代理對象調用一個方法的時候,這個方法的調用就會被轉發爲由InvocationHandler這個接口的invoke()方法來進行調用。

//每次生成動態代理類對象時都需要指定一個實現了InvocationHandler接口的調用處理器對象
    public class InvocationHandlerImpl implements InvocationHandler {
        private Object subject; // 這個就是我們要代理的真實對象,也就是真正執行業務邏輯的類
        public InvocationHandlerImpl(Object subject) {// 通過構造方法傳入這個被代理對象
            this.subject = subject;
        }
        /**
         * 該方法負責集中處理動態代理類上的所有方法調用
         * 調用處理器根據這三個參數進行預處理或分派到委託類實例上反射執行
         * @param proxy 最終生成的代理類實例
         * @param method 被調用的方法對象
         * @param args 調用上面method時傳入的參數
         * @return method對應的方法的返回值
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Object result = null;
            System.out.println("當前調用的方法是" + method.getName());
            //當代理對象調用真實對象的方法時,其會自動的跳轉到代理對象關聯的handler對象的invoke方法來進行調用
            result = method.invoke(subject, args);// 需要指定被代理對象和傳入參數
            System.out.println(method.getName() + "方法的返回值是" + result);
            return result;// 返回method方法執行後的返回值
        }
    }

在源碼中invok()方法並沒有被顯示的調用,而是在生成的代理方法中調用了。在生成的代理方法中是這樣的。

    public class 代理類 extends Proxy implements 父接口{
            public final void bye(){
                super.h.invoke(this,m3,null)
            }
    }

(2)Proxy:這個類的作用就是用來動態創建一個代理對象的類,它提供了許多的方法,但是我們用的最多的就是newProxyInstance()這個方法:

  /**
         * loader:一個ClassLoader對象,定義了由哪個ClassLoader來對生成的代理對象進行加載
         * interfaces:一個Interfaces對象的數組,表示的是我將要給我需要代理的對象提供一組聲明接口,
         *            如果我提供了一組接口給他,那麼這個代理對象就宣稱實現了該接口(多態),這樣
         *            我就能調用這組接口中的方法了
         * h:一個InvocationHandler對象,表示的是當我這個動態代理對象在調用方法的時候,會關聯到
         *    哪一個InvocationHand-ler對象上
         */
        public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,
            InvocationHandler h) throws IllegalArgumentException{}

其實我們所說的DynamicProxy是這樣一種class:它是在運行時生成的class,在生成它時你必須提供一組interfce給它,然後該class就宣稱它實現了這些interface。如此一來,我們可以把該class的實例當做這些interface中的任何一個來用。當然,這個DynaicProxy其實就是一個Proxy,它不會做實質性的工作,在生成它的實例時你必須提供一個handler,由它接管時機的工作。

(3)示例

//需要動態代理的接口
    public interface Subject {
        public void hello(String name);
        public String bye();
    }
    //被代理類
    public class RealSubject implements Subject{
        @Override
        public void hello(String name) {
            System.out.println("hello "+name);
        }
        @Override
        public String bye() {
            System.out.println("bye");
            return "bye";
        }
    }
    //test
    public class Test {
        public static void main(String[] args) {
            // 被代理的對象
            Subject realSubject = new RealSubject();
            /**
             * 通過InvocationHandlerImpl的構造器生成一個InvocationHandler對象,
             * 需要傳入被代理對象作爲參數
             */
            InvocationHandler handler = new InvocationHandlerImpl(realSubject);
            ClassLoader loader = realSubject.getClass().getClassLoader();
            Class[] interfaces = realSubject.getClass().getInterfaces();
            // 需要指定類裝載器、一組接口及調用處理器生成動態代理類實例
            Subject subject = (Subject) Proxy.newProxyInstance(loader, interfaces, handler);
            System.out.println("動態代理對象的類型:" + subject.getClass().getName());
            subject.hello("Tom");
            subject.bye();
        }
    }

2.基於繼承的CGLIB動態代理

前面我們詳細的介紹了JDK自身的AOP所繫統的一種動態代理的實現,它的實現相對而言是簡單的,但是卻有一個非常致命性的缺陷,就是隻能爲接口中的方法完成代理,而委託類自己的方法或者父類中的方法都不可能被代理。CGLB應運而生,它是一個高性能的,底層基於ASM框架的一個代碼生成框架,它完美的解決了JDK版本的動態代理只能爲藉口方法代理的單一性不足問題。CGLIB的底層是基於EnhancerMethodInterceptor這兩個類。

(1)Enhancer:這是CGLIB動態代理中的字節碼生成器。首先將被代理的類Student設置成父類,然後設置代理對象的回調對象MyMethodInterceptor,最後執行enhancer.create()動態生成一個代理類,並從Object強制轉型爲父類型Student。

    public class Test{
        public static void main(String[] args){
            Enhancer enhancer = new Enhancer();  
            enhancer.setSuperclass(Student.class);  
            enhancer.setCallback(new MyMethodInterceptor());  
            Student s = (Student)enhancer.create();  
            s.speak();  
            s.run();  
            s.study();  
        }
    }

(2)MethodInterceptor:一個攔截器(父接口Interceptor)。在調用目標方法時,CGLib會回調MethodInterceptor接口方法攔截,來實現你自己的代理邏輯,類似於JDK中的InvocationHandler接口。

    public class MyMethodInterceptor implements MethodInterceptor{
        //obj真實對象 method真實方法 arg真實方法形參列表 proexy代理方法
        public Object intercept(Object obj,Method method,Object[] arg,MethodInterceptor proxy){
            System.out.println("Before:"+method);
            Object object = proxy.invokeSuper(obj,arg);
            System.out.println("After:"+method);
            return object;
        }
    }

(3)示例

    //父類
    public class Father{
        public void sayHello(){
            System.out.println("this is father");
        }
    }
    //父接口
    public interface Rerson{
        void speack();
        void run();
    }
    //委託類(真實對象)
    public class Student extends Father implements Person{
        public void study(){
            System.out.println("i can study,,");
        }
        @Override
        public void speak(){
            System.out.println("i am a student,i can speak..");
        }
        @Override
        public void run(){
            System.out.println("i am a student,i can run..");
        }
    }

 

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