【java設計模式】(3)---代理模式(案例解析)


設計模式之代理模式

 

一、概述

1、什么是代理模式?

解釋第一遍:代理模式主要由三個元素共同構成:

  1)一個接口,接口中的方法是要真正去實現的。

  2)被代理類,實現上述接口,這是真正去執行接口中方法的類。

  3)代理類,同樣實現上述接口同時封裝被代理類對象,幫助被代理類去實現方法

解釋第二遍:  使用代理模式必須要讓代理類和目標類實現相同的接口,客戶端通過代理類來調用目標方法,代理類會將所有的方法調用分派到目標對象上反射執行,還可以在分派過程中添加"前置通知"和后置處理

(如在調用目標方法前校驗權限,在調用完目標方法后打印日志等)等功能。

解釋第三遍(如圖):

這樣應該是明白了,當然代理模式又分為靜態代理和動態代理,先看靜態代理。

 

一、靜態代理

直接舉例(網上有個很不錯的例子)

假如一個班的同學要向老師交班費,但是都是通過班長把自己的錢轉交給老師。這里,班長就是代理類(代理學生上交班費),學生就是被代理類或者理解為目標類。

  首先,我們創建一個Teacher接口。這個接口就是學生(被代理類),和班長(代理類)的公共接口,他們都有上交班費的行為。這樣,學生上交班費就可以讓班長來代理執行。

/**
 * 創建Teacher接口
 */
public interface Teacher {
    //上交班費
    void giveMoney();
}

Student類實現Teacher接口。Student可以具體實施上交班費的動作。

public class Student implements Teacher {
    private String name;
    public Student(String name) {
        this.name = name;
    }
    
    @Override
    public void giveMoney() {
       System.out.println(name + "上交班費100元");
    }
}

StudentsProxy類,這個類也實現了Teacher接口,由於實現了Teacher接口,同時持有一個學生對象,那么他可以代理學生類對象執行上交班費(執行giveMoney()方法)行為。

/**
 * 學生代理類,也實現了Teacher接口,保存一個學生實體,這樣既可以代理學生產生行為*/
public class StudentsProxy implements Teacher{
    //被代理的學生
    Student stu;

    public StudentsProxy(Teacher stu) {
        // 只代理學生對象
        if(stu.getClass() == Student.class) this.stu = (Student) stu;
    }

    //代理上交班費,調用被代理 學生的上交班費行為
    public void giveMoney() {
        System.out.println("張三家里是土豪,應該比其它人交更多的班費!");
        stu.giveMoney();
        System.out.println("張三班費交的最多,你就是班長了!");

    }
}

下面測試類ProxyTest,看如何使用代理模式:

public class ProxyTest {
    public static void main(String[] args) {
        //被代理的學生張三,他的班費上交有代理對象monitor(班長)完成
        Teacher zhangsan = new Student("張三");
        
        //生成代理對象,並將張三傳給代理對象
        Teacher monitor = new StudentsProxy(zhangsan);
        
        //班長代理上交班費
        monitor.giveMoney();
    }
}

運行結果:

總結下

        這里並沒有直接通過張三(被代理對象)來執行上交班費的行為,而是通過班長(代理對象)來代理執行了。這就是代理模式。代理模式最主要的就是有一個公共接口(Teacher),一個具體的類(Student),

一個代理類(StudentsProxy),代理類持有具體類的實例,代為執行具體類實例方法。 同時可以看到,代理類里除了實現目標類的方法,而且可以在執行前后添加其它方法來起到增強功能。這不就是AOP(面向切面

思想),對的AOP的實現就是基於代理模式,只不過它采用的是動態代理模式。

 

二、動態代理模式

 1、案例說明

public class MyProxy {
    //一個接口
    public interface IHello{
        void sayHello();
    }
    //目標類實現接口
    static class Hello implements IHello{
        public void sayHello() {
            System.out.println("sayHello......");
        }
    }
    //自定義代理類需要實現InvocationHandler接口
    static  class HWInvocationHandler implements InvocationHandler{
        //目標對象
        private Object target;
        public HWInvocationHandler(Object target){
            this.target = target;
        }
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("------插入前置通知代碼-------------");
            //執行相應的目標方法
            Object rs = method.invoke(target,args);
            System.out.println("------插入后置處理代碼-------------");
            return rs;
        }
    }
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
       //獲取動態代理類
        Class<?> proxyClazz = Proxy.getProxyClass(IHello.class.getClassLoader(),IHello.class);
        //獲得代理類的構造函數,並傳入參數類型InvocationHandler.class
        Constructor<?> constructor = proxyClazz.getConstructor(InvocationHandler.class);
        //通過構造函數來創建動態代理對象,將自定義的InvocationHandler實例傳入
        IHello iHello = (IHello) constructor.newInstance(new HWInvocationHandler(new Hello()));
        //通過代理對象調用目標方法
        iHello.sayHello();
    }
}

 運行結果:

Proxy類中還有個將2~4步驟封裝好的簡便方法來創建動態代理對象,其方法簽名為:newProxyInstance(ClassLoader loader,Class<?>[] instance, InvocationHandler h),如下例:

 (方式二)

public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
       
       IHello  ihello = (IHello) Proxy.newProxyInstance(IHello.class.getClassLoader(),  //加載接口的類加載器
               new Class[]{IHello.class},      //一組接口
               new HWInvocationHandler(new Hello())); //自定義的InvocationHandler
       ihello.sayHello();
   }

 看到這里我們應該思考三個問題?

     1)動態代理類是如何創建的?

     2) 它是如何調用MyProxy中的invoke的方法的? 

     3)  MyProxy中invoke中的invoke方法又是啥含義?

帶着這三問題我們具體分析下。

2、動態代理類是如何創建的?

具體源碼我就不分析了,我們用上面的第一種方式,進行斷點查看(括號中的就是通過IDEA斷點顯示的信息)。

        //1、獲取動態代理類(proxyClazz:class com.sun.proxy.$Proxy0 )
        Class<?> proxyClazz = Proxy.getProxyClass(IHello.class.getClassLoader(),IHello.class);
        //2、獲得代理類的構造函數(constructor: public com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler)
        Constructor<?> constructor = proxyClazz.getConstructor(InvocationHandler.class);
        //3、通過構造函數來創建動態代理對象(iHello:cn.edu.zju.grs.alufer.albert.jz.dao.MyProxy$Hello@16b4a017)
        IHello iHello = (IHello) constructor.newInstance(new HWInvocationHandler(new Hello()));

通過斷點發現:

     (1)第一步已經獲得了代理對象,代理對象的名稱是:$Proxy0

    (2)第二步我們發現代理類的構造函數需要傳入一個java.lang.reflect.InvocationHandler類

     (3)第三步通過構造函數創建對象,構造函數里放到就是HWInvocationHandler類,而它是實現InvocationHandler接口的,所以沒毛病。

總的來講這三步都是通過類的反射機制來實現創建類的。

3、如何調用MyProxy中的invoke的方法

這里就需要看下$Proxy0.class進行反編譯后的代碼

//代理類名為$Proxy0,繼承Proxy,實現IHello接口
public final class $Proxy0 extends Proxy implements IHello {   
    //變量,都是private static Method  XXX
    private static Method m3;  
    private static Method m1;
    private static Method m0;
    private static Method m2;
    //代理類的構造函數,其參數正是是InvocationHandler實例,Proxy.newInstance方法就是通過通過這個構造函數來創建代理實例的
    public $Proxy0(InvocationHandler var1)  {
        super(var1);
    }
    //接口代理方法
    public final void sayHello()  {
        try {
            
            //這里才是關鍵,通過這里終於知道是如何調用MyProxy中的invoke方法的
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
    //以下Object中的三個方法
    public final boolean equals(Object var1)  {
        try {
            return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }
    public final int hashCode()  {
        try {
            return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
    public final String toString()   {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
    //對變量進行一些初始化工作
    static {
        try {
            //我們發現m3就代表着sayHello()方法
            m3 = Class.forName("com.mobin.proxy.IHello").getMethod("sayHello", new Class[0]);
            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

看到這里是不是和前面斷點的代碼對象的獲得的信息是一樣的。(比如代理類名就加$Proxy0,構造方法也和上面分析的一樣)。總算明白了。

4、MyProxy中invoke中的invoke方法又是啥含義?

其實到這一步就已經變得簡單了 Object rs = method.invoke(target,args);執行的方法就是目標類中的sayHello()方法,這個也就一個反射知識。

我針對這個舉例:

有一個 A類 
package com.jincou.study;

public class A {
    
    public void foo(String name) {
    System.out.println("Hello, " + name);
    }
}

TestClassLoad來反射調用A上的方法

public class TestClassLoad {
    
    public static void main(String[] args) throws Exception {
      
        //獲得代理類
        Class<?> clz = Class.forName("com.jincou.study.A");
        //獲得代理類對象
        Object o = clz.newInstance();
        //獲得foo方法
         Method m = clz.getMethod("foo", String.class);
        //執行foot方法
         m.invoke(o,"張三");
   }
}

運行結果

那么到這里你就應該明白了,在MyProxy中invoke中的

    //這個method就是sayHello()方法,target是指new Hello()對象(也就是目標類對象),參數為null
    Object rs = method.invoke(target,args);

 是不是和上面最后一步一個意思,總算明白了。

這里順便看下invoke源碼:

 //這個源碼只要知道一點就是Object... args,它表示的是可變參數所以他可以是空或者多個
     @CallerSensitive
        public Object invoke(Object obj, Object... args)
            throws IllegalAccessException, IllegalArgumentException,
               InvocationTargetException
        {
            if (!override) {
                if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                    Class<?> caller = Reflection.getCallerClass();
                    checkAccess(caller, clazz, obj, modifiers);
                }
            }
            MethodAccessor ma = methodAccessor;             // read volatile
            if (ma == null) {
                ma = acquireMethodAccessor();
            }
            return ma.invoke(obj, args);
        }

總算理解完了,這篇文章並沒有很深入的講底層源碼,看源碼自己都會被繞進去,到頭來可能還是一頭霧水。這樣的分析還是蠻清晰的。

最后總結下使用動態代理的五大步驟

     1)通過實現InvocationHandler接口來自定義自己的InvocationHandler;
 
     2)通過Proxy.getProxyClass獲得動態代理類
 
     3)通過反射機制獲得代理類的構造方法,方法簽名為getConstructor(InvocationHandler.class)
 
     4)通過構造函數獲得代理對象並將自定義的InvocationHandler實例對象傳為參數傳入
 
     5)通過代理對象調用目標方法

 

 

想太多,做太少,中間的落差就是煩惱。想沒有煩惱,要么別想,要么多做。中校【5】  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM