天天看點

Java動态代理的兩種實作方法:JDK動态代理和CGLIB動态代理

代理模式

  • 代理模式是23種設計模式的一種,指一個對象A通過持有另一個對象B,可以具有B同樣的行為的模式。為了對外開放協定,B往往實作了一個接口,A也會去實作接口。但B是真正的實作類,A則比較“虛”,A借用了B的方法去實作接口的方法。A雖然為“僞軍”,但它可以增強B,在調用B的方法前後都做些其他的事情。Spring AOP就是使用了動态代理完成了代碼的動态植入。
  • 使用動态代理的好處還不止這些,一個工程如果依賴另一個工程給的接口,但是另一個工程的接口不穩定,經常變更協定,就可以使用一個代理,接口變更時,隻需要修改代理,不需要修改業務代碼。從這個意義上說,所有調外界的接口,我們都可以這麼做,不讓外界的代碼對我們的代碼有侵入,這叫防禦式程式設計。
  • 上述例子中,類A寫死持有B,就是B的靜态代理。如果A代理的對象是不确定的,就是動态代理。動态代理目前有兩種常見的實作,JDK動态代理和CGLIB動态代理。

JDK動态代理

JDK動态代理是JRE提供的類庫,可以直接使用,不依賴第三方。先看一下JDK動态代理的使用代碼,在了解原理。

首先,有個明星接口,有唱和跳兩個方法。

package proxy;

public interface Star {
    String sing(String name);

    String dance(String name);
}


      

再有一個明星實作類-劉德華。

package proxy;

public class LiuDeHua implements Star {
    public String sing(String name) {
        System.out.println("給我一杯忘情水!");
        return "唱完";
    }

    public String dance(String name) {
        System.out.println("開心的馬骝");

        return "跳完";
    }
}


      

明星演出前需要有人收錢,由于要準備演出,自己不做這個工作,一般交給一個經紀人。便于了解,它的名字以Proxy結尾,但他不是代理類,原因是它沒有實作我們的明星接口,無法對外服務,它僅僅是一個wrapper。

package proxy;

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

public class StarProxy implements InvocationHandler {
    //目标類,被代理對象
    private Object target;

    public void setTarget(Object target) {
        this.target = target;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //這裡可以做增強
        System.out.println("收錢");
        Object result = method.invoke(target, args);
        return result;
    }

    //生成代理類
    public Object CreateProxyedObj() {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }
}


      

上述例子中,方法CreatProxyedObj傳回的對象才是我們的代理類,它需要三個參數,前兩個參數的意思是在同一個classloader下通過接口建立出一個對象,該對象需要一個屬性,也就是第三個參數,它是一個InvocationHandler。需要注意的是這個CreatProxyedObj方法不一定非得在我們的StarProxy類中,往往放在一個工廠類中。上述代理的代碼使用過程一般如下:

1、new一個目标對象

2、new一個InvocationHandler,将目标對象set進去

3、通過CreatProxyedObj建立代理對象,強轉為目标對象的接口類型即可使用,實際上生成的代理對象實作了目标接口。

package proxy;

public class Client {
    public static void main(String[] args) {
        Star ldh = new LiuDeHua();
        StarProxy proxy = new StarProxy();
        proxy.setTarget(ldh);
        Star proxyedObj = (Star) proxy.CreateProxyedObj();
        proxyedObj.sing("劉德華");
    }
}


      

Proxy(jdk類庫提供)根據B的接口生成一個實作類,我們成為C,它就是動态代理類(該類型是 $Proxy+數字 的“新的類型”)。生成過程是:由于拿到了接口,便可以獲知接口的所有資訊(主要是方法的定義),也就能聲明一個新的類型去實作該接口的所有方法,這些方法顯然都是“虛”的,它調用另一個對象的方法。當然這個被調用的對象不能是對象B,如果是對象B,我們就沒法增強了,等于饒了一圈又回來了。

是以它調用的是B的包裝類,這個包裝類需要我們來實作,但是jdk給出了限制,它必須實作InvocationHandler,上述例子中就是StarProxy, 這個接口裡面有個方法,它是所有Target的所有方法的調用入口(invoke),調用之前我們可以加自己的代碼增強。

看下我們的實作,我們在InvocationHandler裡調用了對象B(target)的方法,調用之前增強了B的方法。

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
    {
        // 這裡增強
        System.out.println("收錢");
        
        Object result = method.invoke(target, args);
        
        return result;
    }

      

是以可以這麼認為C代理了InvocationHandler,InvocationHandler代理了我們的類B,兩級代理。

整個JDK動态代理的秘密也就這些,簡單一句話,動态代理就是要生成一個包裝類對象,由于代理的對象是動态的,是以叫動态代理。由于我們需要增強,這個增強是需要留給開發人員開發代碼的,是以代理類不能直接包含被代理對象,而是一個InvocationHandler,該InvocationHandler包含被代理對象,并負責分發請求給被代理對象,分發前後均可以做增強。從原理可以看出,JDK動态代理是“對象”的代理。

CGLIB動态代理

代理的目的是構造一個和被代理的對象有同樣行為的對象,一個對象的行為是在類中定義的,對象隻是類的執行個體。是以構造代理,不一定非得通過持有包裝對象這一方式。

通過繼承可以繼承父類所有得公開方法,然後可以重寫這些方法,在重寫時對這些方法增強,這就是CGLIB的思想。根據裡氏代換原則(LSP),父類需要出現的地方,子類可以出現,是以CGLIB實作的代理也是可以被正常使用的。

package proxy;
 
import java.lang.reflect.Method;
 
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
 
public class CglibProxy implements MethodInterceptor
{
    // 根據一個類型産生代理類,此方法不要求一定放在MethodInterceptor中
    public Object CreatProxyedObj(Class<?> clazz)
    {
        Enhancer enhancer = new Enhancer();
        
        enhancer.setSuperclass(clazz);
        
        enhancer.setCallback(this);
        
        return enhancer.create();
    }
    
    @Override
    public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable
    {
        // 這裡增強
        System.out.println("收錢");
        
        return arg3.invokeSuper(arg0, arg2);
    } 
}

      

從代碼可以看出,它和jdk動态代理有所不同,對外表現上看CreatProxyedObj,它隻需要一個類型clazz就可以産生一個代理對象, 是以說是“類的代理”,且創造的對象通過列印類型發現也是一個新的類型。不同于jdk動态代理,jdk動态代理要求對象必須實作接口(三個參數的第二個參數),cglib對此沒有要求。

cglib的原理是這樣,它生成一個繼承B的類型C(代理類),這個代理類持有一個MethodInterceptor,我們setCallback時傳入的。C重寫所有B中的方法(方法名一緻),然後在C中,建構名叫“CGLIB”+“父 類 方 法 名 父類方法名父類方法名”的方法(下面叫cglib方法,所有非private的方法都會被建構),方法體裡隻有一句話super.方法名(),可以簡單的認為保持了對父類方法的一個引用,友善調用。

繼續閱讀