Java學習筆記:靜態代理,JDK代理和CGLIB代理

這篇文章不對JDK代理和CGLIB代理的內部實現細節講解,這只是簡單提一下如何使用,以及代理技術在實際中的用處。

先說一下靜態代理。

明確一個原則,寫出的代碼投入到生產中後,最好不要對代碼再進行修改。

比如我們在一個賬戶類中有一個取錢的函數。

public interface Count{
    public void getMoney();
}
public class CountImpl implements Count{
    @Override
    public void getMoney() {
        System.out.println("取錢");
    }
}

寫好這個類之後,前期確定沒問題,於是代碼就被投入到實際應用之中。但是過了一段時間後發現,應該在取錢這個操作的前後都加入一些操作更有利於用戶的體驗。如果可以對代碼直接修改的話,寫出來的代碼就應該是這樣。

public class CountImpl implements Count{
	public void getMoney() {
            System.out.println("取錢之前的操作");
            System.out.println("取錢");
            System.out.println("取錢之後的操作");
	}
}

但前面說了,代碼投入到應用中後不要輕易修改。爲了不修改源代碼就完成我們新的需求,就可以用到代理技術。下面是靜態代理的實現。

public interface Count{
    public void getMoney();
}

public class CountImpl implements Count{
    @Override
    public void getMoney() {
        System.out.println("取錢");
    }
}

public Class CountProxy implements Count{
    private Count count;
    public CountProxy(Count count){
        this.count = count;
    }
    public getMoney(){
        System.out.println("取錢之前的操作");
        count.getMoney();
        System.out.println("取錢之後的操作");
    }
}

第三個類,CountProxy就是代理類。在後端定義了代理類之後,在前端調用時,就可以實現不修改原來的函數而多增加一些操作,代碼如下。

Count count = new CountImpl();
CountProxy proxy = new CountProxy(count);
proxy.getMoney();

輸出結果
在這裏插入圖片描述


JDK動態代理

JDK動態代理是基於接口實現的動態代理。通俗地說就是被代理的類必須實現了一個接口。被實現的接口中的方法就是需要被代理的方法。下面將舉例說明。

我們定義一個接口,接口中定義了兩個方法。(JDK中,被代理類將繼承這個接口,接口中定義的所有方法將會被代理。)

public interface Interface1{
    public void test1();
    public void test2();
}

然後用一個類實現這個接口,這個類就是被代理類了。

public class Target implements Interface1{
    public void test1(){
        System.out.println("test1");
    }
    public void test2(){
        System.out.println("test2");
    }
}

寫完後投入生產後運行一段時間後發現,兩者都需要在執行之前後執行之後都需要執行一些相同的操作才符合業務需要,比如下面這樣

操作1test1();
操作2;

操作1test2();
操作2

如果使用靜態代理的方法,那麼就要寫兩個靜態代理的函數,而且兩者的相似度極高。這時候,爲了簡化操作我們就可以使用JDK動態代理簡化代碼量。

下面使用JDK動態代理。JDK代理依靠的是InvocationHandler接口和Object Proxy.newProxyInstance(ClassLoader, Class<?>[],InvocationHandler)方法。其中,實現InvocationHandler接口,在invoke()中定義方法被代理後要做的事。而Proxy的靜態方法newProxyInstance將返回代理類。

public class TargetProxy implements InvocationHandler{
    private Object target = null;
    public Object bind(Object target) {
        this.target = target;
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), 
				target.getClass().getInterfaces(), this);
	}
    public Object invoke(Object proxy, Method method, Object[] args)throws Throwable{
        System.out.println("進入代理前的操作");
        //這相當於是在調用被代理方法
        Object obj = method.invoke(target,  args);
        System.out.println("進入代理後的操作");
        return obj;
    }
}

上面的代碼算是套路模板了,使用JDK動態代理就基本可以照搬上面的代碼,我們只需要在invoke()函數中定製自己的操作即可。

TargetProxy就是Target的代理類,invoke()函數是用定製代理操作的。bind()是用於確定需要被代理的對象。

其中具體的函數的實現可能會再以後的源碼部分說,這篇文章只是介紹如何使用JDK動態代理。下面是使用的方式

TargetProxy targetProxy = new TargetProxy();
Interface1 proxy = (Interface1) targetProxy.bind(new Target());
proxy.test1();
proxy.test2();

bind()的參數是被代理的對象,也就是target。該函數返回代理類proxy。proxy代理Target,也就是說,Target實現的接口中的所有方法都被代理了。其中proxy的類型,也就是bind()方法返回的類型,只能是target實現的接口類型,這什麼意思勒,比如下面這兩個接口

public interface Interface1 {
    public void test1();
}

public interface Interface2{
    public void test2();
}

Target都實現這兩個接口

public class JdkClass implements Interface2, Interface1{
    @Override
    public void test1() {
        System.out.println("test1");
    }

    @Override
    public void test2() {
        System.out.println("test2");
    }
}

那麼bind()函數返回的代理類的類型可以被看成Interface1,也可以被看成Interface2,而且只能被看成這兩個接口。

public class Main implements InvocationHandler {
    private Object target = null;
    public Object bind(Object target){
        this.target = target;
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("進入代理錢的方法");
        Object obj = method.invoke(target, args);
        System.out.println("進入代理後的方法");
        return obj;
    }
    public static void main(String[] args) {
        Main main = new Main();
        //bind返回值可以被看成Interface1類型
        Interface1 proxy1 = (Interface1) main.bind(new JdkClass());
        proxy1.test1();
        //bind返回值可以被看成Interface2類型
        Interface2 proxy2 = (Interface2) main.bind(new JdkClass());
        proxy2.test2();
    }
}

輸出結果是
在這裏插入圖片描述






CGLIB動態代理

CGLIB動態代理和JDK動態代理幾乎一樣,其使用方式也是固定的,功能和JDK動態代理完全一樣。唯一不同的點是,JDK代理的類必須實現至少一個接口,而CGLIB沒有這個限制,完全可以代理一個普通的類。

CGLIG是使用動態字節碼技術拓展對象行爲,其原理是我們爲被代理對象生成一個子類。通過子類複寫父類的行爲,對父類的行爲拓展。這樣就實現了和代理模式一樣的效果。這是子類就相當於代理類,父類則是被代理類。

被代理類要實現net.sf.cglib.proxy.Callback這個接口,但是多數情況下,我們會直接實現net.sf.cglib.proxy.MethodInterceptor這個接口(它拓展了Callback)。接口中的Object intercept(Object, Method, Object[], MethodProxy)定義的是方法被代理後的具體操作,地位和InvocationHandler中的invoke方法一樣。

之後我們使用Enhancer創建出代理類,就可以實現代理效果。

CGLIB具體使用方式如下。

先定義一個Target類作爲需要被代理的真實對象

public class Target{
    public void test(){
        System.out.println("test");
    }
}

現在定義代理類,代理類的寫法也是固定的,只需要定製intercept()函數中的內容即可。

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibProxy implements MethodInterceptor {
	//Enhancer生成代理類
    public Object getProxy(Class claz) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(claz);
        enhancer.setCallback(this);
        return enhancer.create();
    }
    
    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("進入代理前的方法");
        Object result = methodProxy.invokeSuper(proxy, args);
        System.out.println("進入代理後的方法");
        return result;
    }
}

運行代理,實現效果和JDK代理一樣。

CglibProxy cglibProxy = new CglibProxy();
Target target = (Target) cglibProxy.getProxy(Target.class);
target.test();
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章