1. 程式人生 > >java代理機制詳解(動態代理原理解析,簡單易懂!)

java代理機制詳解(動態代理原理解析,簡單易懂!)

一.代理機制概念

1.代理機制是一種設計模式,分為靜態代理動態代理.

2.特徵:代理類與委託類有同樣的介面,代理類主要負責為委託類預處理訊息、過濾訊息、把訊息轉發給委託類,以及事後處理訊息等。

代理類的物件本身並不真正實現服務,而是通過呼叫委託類的物件的相關方法,來提供特定的服務。

代理模式就是在訪問實際物件時引入一定程度的間接性,因為這種間接性,可以附加多種用途。(自身體驗是網路請求的統一處理!)

image

二:靜態代理

1.概念:在編譯時就已經將介面,被代理類,代理類等確定下來。在程式執行之前,代理類的.class檔案就已經生成。

舉例理解:
假如一個班的同學要向老師交班費,但是都是通過班長把自己的錢轉交給老師。這裡,班長就是代理學生上交班費

實現思路:這裡我們需要三個東西:

這裡寫圖片描述

/**
* 1.建立PushMoney介面
* @author ZhuK
*/
public interface PushMoney {
//上交班費
void giveMoney();
}

//2.學生類,實現交錢PushMoney介面
public class Student implements PushMoney {
private String name;
public Student(String name) {
    this.name = name;
}
@Override
public void giveMoney() {
   System.out.println(name + "上交班費50元");
}  } 

/**
* 3.學生代理類,也實現了PushMoney介面,在構造方法儲存一個學生實體,這樣可以代理學生產生行為
*
*/
public class StudentsProxy implements PushMoney{
//被代理的學生
Student stu;

public StudentsProxy(PushMoney stu) {
    // 只代理學生物件
    if(stu.getClass() == Student.class) {
        this.stu = (Student)stu;
    }
}

//代理上交班費,呼叫被代理學生的上交班費行為
public void giveMoney() {
    stu.giveMoney();
}
}

##### 好的,那下面我們在一個測試類中實現:

    public class StaticProxyTest {
    public static void main(String[] args) {
    //被代理的學生張三,他的班費上交有代理物件monitor(班長)完成
    Person zhangsan = new Student("張三");   
    //生成代理物件,並將張三傳給代理物件
    Person monitor = new StudentsProxy(zhangsan);

    //班長代理上交班費
    monitor.giveMoney();
}
}

執行結果:
image

這裡並沒有直接通過張三(被代理物件)來執行上交班費的行為,而是通過班長(代理物件)來代理執行了。這就是代理模式。
理模式最主要的就是有一個公共介面(Person),一個具體的類(Student),一個代理類(StudentsProxy),
代理模式就是在訪問實際物件時引入一定程度的間接性,因為這種間接性,可以附加多種用途。
這裡的間接性就是指不直接呼叫實際物件的方法,那麼我們在代理過程中就可以加上一些其他用途。
就這個例子來說,加入班長在幫張三上交班費之前想要先反映一下張三最近學習有很大進步,通過代理模式很輕鬆就能辦到:

public class StudentsProxy implements PushMoney{
//被代理的學生
Student stu;

public StudentsProxy(PushMoney stu) {
    // 只代理學生物件
    if(stu.getClass() == Student.class) {
        this.stu = (Student)stu;
    }
}

//代理上交班費,呼叫被代理學生的上交班費行為
public void giveMoney() {
    System.out.println("張三最近學習有進步!");
    stu.giveMoney();
}
}

可以看到,只需要在代理類中幫張三上交班費之前,執行其他操作就可以了。這種操作,也是使用代理模式的一個很大的優點。最直白的就是在Spring中的面向切面程式設計(AOP),我們能在一個切點之前執行一些操作,在一個切點之後執行一些操作,這個切點就是一個個方法。這些方法所在類肯定就是被代理了,在代理過程中切入了一些其他操作。

三.動態代理

1.動態代理

1.1代理類在程式執行時建立的代理方式被成為動態代理

1.2上面靜態代理的例子中,代理類(studentProxy)是自己定義好的物件student,
而是在執行時根據我們在Java程式碼中實現時的“指示”動態生成的。 (這裡有點像DIP設計思想,根據上面的需求去改實現底層)

1.3動態代理的優勢在於可以很方便的對抽象代理類的函式進行統一的處理,而不用修改每個代理類中的方法。

比如上面我們指定了班長指定了是代理學生,而當我們代理多個物件時呢?
比如這是我要分為兩個被代理物件,一個是男生,一個是女生,
那該如何去統一管理呢!所以這時候就用到我們的動態代理了!

例:
想要在每個代理的方法前加上一個處理方法,我就需要去每個實現處去新增,比如有10個就要加10,不夠優雅!:

 public void giveMoney() {
    //呼叫被代理方法前加入處理方法
    beforeMethod();
    stu.giveMoney();
 }

這裡只有一個giveMoney方法,就寫一次beforeMethod方法,但是如果除了giveMonney還有很多其他的方法,那就需要寫很多次beforeMethod方法,很麻煩。那看看下面動態代理如何實現。

###### 2.動態代理 簡單實現

 生成呼叫介面
 public interface PushMoney {

public void pushMoney();

}

男孩真實類
public class BoySubject implements PushMoney {
@Override
public void pushMoney() {
    System.out.println("我是男孩交錢");
}

}
女孩真實類
public class GirlSubject Subject implements PushMoney {
@Override
public void pushMoney() {
    System.out.println("我是女孩交錢");
}
}

生成代理類並實現InvocationHandler介面
每一個動態代理類都必須要實現InvocationHandler這個介面,
並且每個代理類的例項都關聯到了一個handler,當我們通過代理物件呼叫一個方法的時候,
這個方法的呼叫就會被轉發為由InvocationHandler這個介面的 invoke 方法來進行呼叫。

public class DynamicProxy implements InvocationHandler {
//  這個就是我們要代理的真實物件
private Object subject;

// 構造方法,給我們要代理的真實物件賦初值
public DynamicProxy(Object subject) {
    this.subject = subject;
}

//在下面的例子TEST中實現真實類介面時會轉移到這裡!
public Object invoke(Object object, Method method, Object[] args) throws Throwable {
    //   在代理真實物件前我們可以新增一些自己的操作

    System.out.println("Method:before" + method);

    // 當代理物件呼叫真實物件的方法時,其會自動的跳轉到代理物件關聯的handler物件的invoke方法來進行呼叫
    method.invoke(subject, args);

    //   在代理真實物件後我們也可以新增一些自己的操作
    System.out.println("after rent house");

    return null;
}
}

最後在測試類中我們來實現這個代理

public class Test{
public static void     main(String[] args) {
    // 我們要代理的真實物件
Subject bogSubject = new BoySubject();
Subject girlSubject = new GirlSubject();
     我們要代理哪個真實物件,就將該物件傳進去,最後是通過該真實物件來呼叫其方法的

InvocationHandler handlerBoy
= new DynamicProxy(boySubject);

InvocationHandler handlerGirl
=new DynamicProxy(girlSubject);

    /*
     * 通過Proxy的newProxyInstance方法來建立我們的代理物件,
     * 我們來看看其三個引數 第一個引數 handler.getClass().getClassLoader()
     * 我們這裡使用handler這個類的ClassLoader物件來載入我們的代理物件
     * 第二個引數boySubject.getClass().getInterfaces(),我們這裡為代理物件提供的介面是真實物件所實行的介面,
     * 表示我要代理的是該真實物件,這樣我就能呼叫這組介面中的方法了 
     * 第三個引數handler, 我們這裡將這個代理物件關聯到了上方的InvocationHandler這個物件上
     */
Subject boySubjectProxy = (Subject) Proxy.newProxyInstance(
handlerBoy.getClass().getClassLoader(),  
boySubject.getClass().getInterfaces(),
handlerBoy);
System.out.println(subject.getClass().getName());
boySubjectProxy.pushMoney();

Subject girlSubjectProxy = (Subject) Proxy.newProxyInstance(
handlerGirl.getClass().getClassLoader(),
girlSubject.getClass().getInterfaces(),
handlerGirl);
System.out.println(subject.getClass().getName());
girlSubjectProxy.pushMoney();

}

~~好,到這裡我們就成功的把男女代理物件的交錢操作給完成了,
**並且這裡會呼叫DynamicProxy 實現InvocationHandler介面的invoce方法,
並在執行前後呼叫我們寫進去的操作.**

上面可以看到,執行Test類–>

1.建立一個被代理的物件boySubject真實物件
2.然後再建立一個InvocationHandler物件handleBoy通過我們自定義實現的InvocationHandler介面比如:DynamicProxy

3.建立一個代理物件boySubjectProxy通過Proxy的newProxyInstance(Proxy是一個代理實現集合類,裡面包含了很多代理的實現方法,newProxyInstance是用的最多的一個)

那當我們呼叫代理物件boySubjectProxy和girlSubjectProxy的pushMoney方法
就會自動轉移到InvocationHandle的invoke方法,
是不是很神奇,不用急,我們下面會來解析一下他的原理!

這裡寫圖片描述

四.動態代理原理解析

1、Java動態代理創建出來的動態代理類

上面我們利用Proxy類的newProxyInstance方法建立了一個動態代理物件,檢視該方法的原始碼,發現它只是封裝了建立動態代理類的步驟(重要~):

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
    throws IllegalArgumentException
{
    Objects.requireNonNull(h);
*/
    //*注意~~~* 
    final Class<?>[] intfs = interfaces.clone();
    final SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }

    /*
     * Look up or generate the designated proxy class.
     */
    //*重要1~~~* 
    Class<?> cl = getProxyClass0(loader, intfs);

    /*
     * Invoke its constructor with the designated invocation handler.
     */
    try {
        if (sm != null) {
            checkNewProxyPermission(Reflection.getCallerClass(), cl);
        }

    //重要2~~~
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        final InvocationHandler ih = h;
        if (!Modifier.isPublic(cl.getModifiers())) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    cons.setAccessible(true);
                    return null;
                }
            });
        }
        //重要3~~~

        return cons.newInstance(new Object[]{h});
    } catch (IllegalAccessException|InstantiationException e) {
        throw new InternalError(e.toString(), e);
    } catch (InvocationTargetException e) {
        Throwable t = e.getCause();
        if (t instanceof RuntimeException) {
            throw (RuntimeException) t;
        } else {
            throw new InternalError(t.toString(), t);
        }
    } catch (NoSuchMethodException e) {
        throw new InternalError(e.toString(), e);
    }
}

由於CSDN部落格長度限制,只能上圖了
由於CSDN部落格長度限制,只能上圖了

總結:>生成的代理類:$Proxy0 extends Proxy implements Person,我們看到代理類繼承了Proxy類,所以也就決定了java動態代理只能對介面進行代理,Java的繼承機制註定了這些動態代理類們無法實現對class的動態代理。

上面的動態代理的例子,其實就是AOP的一個簡單實現了,在目標物件的方法執行之前和執行之後進行了處理,對方法耗時統計。Spring的AOP實現其實也是用了Proxy和InvocationHandler這兩個東西的。

              --菜鳥一個"希望共同學習一起在路上!"