天天看點

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

一.代理機制概念

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

2.特征:代理類與委托類有同樣的接口,代理類主要負責為委托類預處理消息、過濾消息、把消息轉發給委托類,以及事後處理消息等。

代理類的對象本身并不真正實作服務,而是通過調用委托類的對象的相關方法,來提供特定的服務。

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

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

二:靜态代理

1.概念:在編譯時就已經将接口,被代理類,代理類等确定下來。在程式運作之前,代理類的.class檔案就已經生成。

舉例了解:

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

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

java代理機制詳解(動态代理原了解析,簡單易懂!)
/**
* 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();
}
}
           

運作結果:

java代理機制詳解(動态代理原了解析,簡單易懂!)
這裡并沒有直接通過張三(被代理對象)來執行上交班費的行為,而是通過班長(代理對象)來代理執行了。這就是代理模式。
理模式最主要的就是有一個公共接口(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方法,

是不是很神奇,不用急,我們下面會來解析一下他的原理!

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

四.動态代理原了解析

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);
    }
}
           
java代理機制詳解(動态代理原了解析,簡單易懂!)
java代理機制詳解(動态代理原了解析,簡單易懂!)
總結:>生成的代理類:$Proxy0 extends Proxy implements Person,我們看到代理類繼承了Proxy類,是以也就決定了java動态代理隻能對接口進行代理,Java的繼承機制注定了這些動态代理類們無法實作對class的動态代理。

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

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