Java動態(tài)代理模式

前言

代理模式是提供了一種目標(biāo)對象另外的訪問方式,通過各種方式構(gòu)建出代理對象讓其去訪問目標(biāo)對象,這樣我們就可以在訪問屬性或者方法的開始之前或者結(jié)束之后做一些事情。在逆向的過程中沒有類的定義還可以更加隱匿自己的一些信息。

在java中代理模式主要有三種實現(xiàn)方式

  • 靜態(tài)代理
  • JDK動態(tài)代理
  • cglib動態(tài)代理

因為很多源碼中都用到了代理模式所以我這里專門寫下記錄一下

靜態(tài)代理

靜態(tài)代理要求代理對象和被代理對象實現(xiàn)相同接口或繼承相同類
其實就是在代理類中定義一個被代理類屬性,在構(gòu)造方法中初始化。再在需要代理的方法中調(diào)用原方法。

下面我們看看具體應(yīng)用

//IUser.java
package com.shark.bean;

public interface IUser {
    
    public void say();
}

//User.java
/**
被代理對象
**/
package com.shark.bean;

public class User implements IUser {

    public void say() {
        // TODO Auto-generated method stub
        System.out.println("I say!");
    }

}
/**
代理對象
UserProxy.java
**/
package com.shark.staticproxy;

import com.shark.bean.IUser;

public class UserProxy implements IUser {

    private IUser user;
    
    public UserProxy(IUser user) {
        // TODO Auto-generated constructor stub
        this.user = user;
    }
    
    public void say() {
        // TODO Auto-generated method stub
        System.out.println("Static Before invoke " );
        user.say();
        System.out.println("Static Before invoke " );
    }

}
/**
調(diào)用
**/
public static void testStatic() {
        User user = new User();

        IUser proxyInstance = new UserProxy(user);
        System.out.println(proxyInstance.getClass());

        proxyInstance.say();
    }

結(jié)果如下


image.png

缺點(diǎn):其實就是這個代理類與被代理類都需要實現(xiàn)同一個接口,有的時候沒有這種條件。并且接口方法一添加兩者都需要修改非常難維護(hù)。

JDK動態(tài)代理

JDK動態(tài)代理調(diào)用jdk的API,在代碼執(zhí)行時構(gòu)建代理類。關(guān)鍵api如下

//
/**
ClassLoader loader,:指定當(dāng)前目標(biāo)對象使用類加載器,獲取加載器的方法是固定的
Class<?>[] interfaces,:目標(biāo)對象實現(xiàn)的接口的類型,使用泛型方式確認(rèn)類型
InvocationHandler h:事件處理,執(zhí)行目標(biāo)對象的方法時,會觸發(fā)事件處理器的方法,會把當(dāng)前執(zhí)行目標(biāo)對象的方法作為參數(shù)傳入
**/
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )

第三個參數(shù)在執(zhí)行目標(biāo)方法的時候就會觸發(fā)這個處理器的invoke方法,這里面我們可以判斷是哪個方法,并獲得其參數(shù)和返回值。

/**
Object proxy,:代理對象
Method method,:調(diào)用的方法
Object[] args:方法傳入的參數(shù)
**/
public Object invoke(Object proxy, Method method,Object[] args)

它的原理就是運(yùn)行時解析到了需要加載的類的名稱,然后通過字節(jié)碼技術(shù)動態(tài)生成對應(yīng)的class字節(jié)碼,加載到內(nèi)存中得到class對象,進(jìn)一步得到實例對象。有興趣的同學(xué)可以看這段源碼

這里我使用了匿名類來當(dāng)參數(shù),因為要保存被代理對象,所以我這里定義了一個代理工廠

/**
ProxyFactory.java
**/
package com.shark.dynamicproxy;

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

public class ProxyFactory {

    // 維護(hù)一個目標(biāo)對象
    private Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }

    public Object getProxyInstance() {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(), new InvocationHandler() {

                    public Object invoke(Object proxy, Method method,
                            Object[] args) throws Throwable {
                        System.out.println("Before invoke " + method.getName());

                        method.invoke(target, args);

                        System.out.println("After invoke " + method.getName());
                        return null;
                    }
                });
    }
}

調(diào)用如下

public static void testDynamic() {
        User user = new User();

        IUser proxyInstance = (IUser) new ProxyFactory(user).getProxyInstance();
        System.out.println(proxyInstance.getClass());

        proxyInstance.say();
    }

運(yùn)行


image.png

缺點(diǎn):目標(biāo)對象一定要實現(xiàn)接口,否則不能用動態(tài)代理

Cglib代理

Cglib代理,也叫作子類代理,它是在內(nèi)存中構(gòu)建一個子類對象從而實現(xiàn)對目標(biāo)對象功能的擴(kuò)展.所以被代理類不能為final
在被代理類沒有實現(xiàn)接口的時候上面jdk動態(tài)代理就不太好使了,我們可以使用Cglib代理

Cglib包的底層是通過使用一個小而塊的字節(jié)碼處理框架ASM來轉(zhuǎn)換字節(jié)碼并生成新的類.不鼓勵直接使用ASM,因為它要求你必須對JVM內(nèi)部結(jié)構(gòu)包括class文件的格式和指令集都很熟悉.
ASM這個東西我們以后可以好好討論一下,這里先留個印象。

這里需要引入jar包,spring的就不用了因為里面自帶了。
asm-7.1.jar
cglib-3.3.0.jar

代碼示例

//CglibProxyFactory.java
package com.shark.cglibproxy;

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 CglibProxyFactory implements MethodInterceptor {

    // 維護(hù)目標(biāo)對象
    private Object target;

    public CglibProxyFactory(Object target) {
        this.target = target;
    }

    // 給目標(biāo)對象創(chuàng)建一個代理對象
    public Object getProxyInstance() {
        // 1.工具類
        Enhancer en = new Enhancer();
        // 2.設(shè)置父類
        en.setSuperclass(target.getClass());
        // 3.設(shè)置回調(diào)函數(shù)
        en.setCallback(this);
        // 4.創(chuàng)建子類(代理對象)
        return en.create();

    }

    public Object intercept(Object obj, Method method, Object[] args,
            MethodProxy proxy) throws Throwable {
        System.out.println("Cglib Before invoke " + method.getName());

        // 執(zhí)行目標(biāo)對象的方法
        Object returnValue = method.invoke(target, args);

        System.out.println("Cglib After invoke " + method.getName());
        return returnValue;
    }

}

實現(xiàn)MethodInterceptor 接口的intercept攔截方法
和jdk類似在調(diào)用代理對象的方法的時候,就和來到這個intercept方法

/**
Object obj:代理對象
Method method:被代理對象調(diào)用的方法
Object[] args:傳入的參數(shù)
MethodProxy proxy:代理方法
**/
public Object intercept(Object obj, Method method, Object[] args,MethodProxy proxy);

調(diào)用如下

public static void testCglib() {
        User user = new User();

        IUser proxyInstance = (IUser) new CglibProxyFactory(user).getProxyInstance();
        System.out.println(proxyInstance.getClass());

        proxyInstance.say();
    }

運(yùn)行


image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容