欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java動(dòng)態(tài)代理四種實(shí)現(xiàn)方式詳解

 更新時(shí)間:2021年04月14日 17:26:11   作者:源點(diǎn)+  
這篇文章主要介紹了Java四種動(dòng)態(tài)代理實(shí)現(xiàn)方式,對(duì)于開始學(xué)習(xí)java動(dòng)態(tài)代理或者要復(fù)習(xí)java動(dòng)態(tài)代理的朋友來講很有參考價(jià)值,有感興趣的朋友可以參考一下

代理模式也是一種非常常見的設(shè)計(jì)模式。了解Spring框架的都知道,Spring AOP 使用的就是動(dòng)態(tài)代理模式。今天就來系統(tǒng)的重溫一遍代理模式。

在現(xiàn)實(shí)生活中代理是隨處可見的,當(dāng)事人因某些隱私不方便出面,或者當(dāng)事人不具備某些相關(guān)的專業(yè)技能,而需要一個(gè)職業(yè)人員來完成一些專業(yè)的操作, 也可能由于當(dāng)事人沒有時(shí)間處理事務(wù),而聘用代理人出面。而在軟件設(shè)計(jì)中,使用代理模式的地方也很多,由于安全原因,屏蔽客戶端直接訪問真實(shí)對(duì)象, 或者為了提升系統(tǒng)性能,使用代理模式實(shí)現(xiàn)延遲加載,還有就是AOP,對(duì)委托類的功能進(jìn)行增強(qiáng)等。

一、代理模式的結(jié)構(gòu)

代理模式的主要參與者有4個(gè),如下圖所示:

角色 作用
Subject 主題接口,定義了代理類和委托類的公共對(duì)外方法,也是代理類代理委托類的方法
RealSubject 委托類,真實(shí)主題,真正實(shí)現(xiàn)業(yè)務(wù)邏輯的類
Proxy 代理類,代理和封裝委托類
Client 客戶端,使用代理類和主題接口完成業(yè)務(wù)邏輯

loading="lazy" alt="" />角色作用Subject主題接口,定義了代理類和委托類的公共對(duì)外方法,也是代理類代理委托類的方法RealSubject委托類,真實(shí)主題,真正實(shí)現(xiàn)業(yè)務(wù)邏輯的類Proxy代理類,代理和封裝委托類Client客戶端,使用代理類和主題接口完成業(yè)務(wù)邏輯

二、代理模式的實(shí)現(xiàn)

代理模式一般分為靜態(tài)代理和動(dòng)態(tài)代理兩種:

  • 靜態(tài)代理,顧名思義,就是提前創(chuàng)建好代理類文件并在程序運(yùn)行前已經(jīng)編譯成字節(jié)碼。
  • 動(dòng)態(tài)代理,是指在運(yùn)行時(shí)動(dòng)態(tài)生成代理類,即代理類的字節(jié)碼將在運(yùn)行時(shí)生成并載入到ClassLoader中。

了解了兩種代理模式大概區(qū)別后,接下來就以一個(gè)短信發(fā)送功能增強(qiáng)的示例來詳細(xì)闡述兩種代理的實(shí)現(xiàn)方式。

1、靜態(tài)代理實(shí)現(xiàn)

第一步,定義主題接口,該接口只有一個(gè)send方法:

public interface ISender {
    public boolean send();
}

第二步,定義主題真正實(shí)現(xiàn)類:

public class SmsSender implements ISender {
    public boolean send() {
        System.out.println("sending msg");
        return true;
    }
}

第三步,創(chuàng)建代理類,封裝實(shí)現(xiàn)類:

public class ProxySender implements ISender {
    private ISender sender;
    public ProxySender(ISender sender){
        this.sender = sender;
    }
    public boolean send() {
        System.out.println("處理前");
        boolean result = sender.send();
        System.out.println("處理后");
        return result;
    }
}

第四步,客戶端調(diào)用:

@Test
public void testStaticProxy(){
    ISender sender = new ProxySender(new SmsSender());
    boolean result = sender.send();
    System.out.println("輸出結(jié)果:" + result);
}

以上就實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的靜態(tài)代理,很明顯,靜態(tài)代理需要為每個(gè)真實(shí)主題定義一個(gè)形式上完全一樣的封裝類,

如果真實(shí)主題方法有所修改,那代理類也需要跟著修改,不利于系統(tǒng)的維護(hù)。

2、動(dòng)態(tài)代理實(shí)現(xiàn)

與靜態(tài)代理相比,動(dòng)態(tài)代理有更多優(yōu)勢(shì),動(dòng)態(tài)代理不僅不需要定義代理類,甚至可以在運(yùn)行時(shí)指定代理類的執(zhí)行邏輯,從而大大提升系統(tǒng)的靈活性。

目前動(dòng)態(tài)代理類的生成方法有很多,有JDK自帶的動(dòng)態(tài)代理、CGLIB、Javassist和ASM庫等。

  • JDK動(dòng)態(tài)代理:內(nèi)置在JDK中,不需要引入第三方j(luò)ar,使用簡(jiǎn)單,但功能比較弱。
  • CGLIB/Javassist:這兩個(gè)都是高級(jí)的字節(jié)碼生成庫,總體性能比JDK動(dòng)態(tài)代理好,且功能強(qiáng)大。
  • ASM:低級(jí)字節(jié)碼生成工具,近乎使用bytecode編碼,對(duì)開發(fā)人員要求最高。當(dāng)然性能也是最好(相比前幾種也不是很大的提升,這里不做具體介紹)。

以下實(shí)例依然以SmsSenderISender作為被代理對(duì)象和接口進(jìn)行試驗(yàn)。

1) JDK動(dòng)態(tài)代理

JDK的動(dòng)態(tài)代理需要實(shí)現(xiàn)一個(gè)處理方法調(diào)用的Handler,用于實(shí)現(xiàn)代理方法的內(nèi)部邏輯,實(shí)現(xiàn)InvocationHandler接口。

public class JdkProxyHandler implements InvocationHandler {
    private Object target;
    public JdkProxyHandler(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);
        System.out.println("處理后");
        return result;
    }
}

客戶端調(diào)用:

@Test
public void testJdkProxy(){
    ISender sender = (ISender) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
            new Class[]{ISender.class},
            new JdkProxyHandler(new SmsSender()));
    boolean result = sender.send();
    System.out.println("代理對(duì)象:" + sender.getClass().getName());
    System.out.println("輸出結(jié)果:" + result);
}

輸出結(jié)果:

處理前

sending msg

處理后

代理對(duì)象:com.sun.proxy.$Proxy4

輸出結(jié)果:true

這樣實(shí)現(xiàn)一個(gè)簡(jiǎn)單的AOP就完成了,我們看到代理類的類型是com.sun.proxy.$Proxy4。那JDK是如何創(chuàng)建代理類?

首先從Proxy.newProxyInstance入手,來研究JDK是如何生成代理類:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

該方法有3個(gè)參數(shù):

  • loader:用哪個(gè)類加載器去加載代理對(duì)象,生成目標(biāo)對(duì)象的代理需要確保其類加載器相同,所以需要將目標(biāo)對(duì)象的類加載器作為參數(shù)傳遞。
  • interfaces:代理類需實(shí)現(xiàn)的接口列表,JDK動(dòng)態(tài)代理技術(shù)需要代理類和目標(biāo)對(duì)象都繼承自同一接口,所以需要將目標(biāo)對(duì)象的接口作為參數(shù)傳遞。
  • h:調(diào)用處理器,調(diào)用實(shí)現(xiàn)了InvocationHandler類的一個(gè)回調(diào)方法,對(duì)目標(biāo)對(duì)象的增強(qiáng)邏輯在這個(gè)實(shí)現(xiàn)類中。

具體代碼如下:

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h) throws IllegalArgumentException {
    //1.檢查
    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.
     */
    //獲取代理類類型
    Class<?> cl = getProxyClass0(loader, intfs);
    /*
     * Invoke its constructor with the designated invocation handler.
     */
    try {
        if (sm != null) {
            checkNewProxyPermission(Reflection.getCallerClass(), cl);
        }
        //通過反射創(chuàng)建代理對(duì)象
        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;
                }
            });
        }
        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);
    }
}

總結(jié):具體代碼細(xì)節(jié)就不在這里深究,但可以明顯的看出,JDK的動(dòng)態(tài)代理底層是通過Java反射機(jī)制實(shí)現(xiàn)的,并且需要目標(biāo)對(duì)象繼承自一個(gè)接口才能生成它的代理類。

2) CGLIB(Code Generation Library)動(dòng)態(tài)代理

使用CGLIB動(dòng)態(tài)代理前需要引入依賴:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

和JDK代理不同,CGLib動(dòng)態(tài)代理技術(shù)不需要目標(biāo)對(duì)象實(shí)現(xiàn)自一個(gè)接口,只需要實(shí)現(xiàn)一個(gè)處理代理邏輯的切入類,并實(shí)現(xiàn)MethodInterceptor接口。

定義真實(shí)主題實(shí)現(xiàn)類:

public class BdSender {
    public boolean send() {
        System.out.println("sending msg");
        return true;
    }
}

代理類邏輯處理類:

public class CglibProxyInterceptor implements MethodInterceptor {
    private Enhancer enhancer = new Enhancer();
    /**
     * 獲取代理類
     * @param clazz
     * @return
     */
    public Object getProxy(Class clazz) {
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        return enhancer.create();
    }
    public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("處理前");
        Object result = methodProxy.invokeSuper(object,args);
        System.out.println("處理后");
        return result;
    }
}

客戶端調(diào)用:

@Test
public void testCglibProxy(){
    BdSender sender = (BdSender) new CglibProxyInterceptor().getProxy(BdSender.class);
    boolean result = sender.send();
    System.out.println("代理對(duì)象:" + sender.getClass().getName());
    System.out.println("輸出結(jié)果:" + result);
}

輸出結(jié)果:

處理前

sending msg

處理后

代理對(duì)象:org.yd.proxy.BdSender$$EnhancerByCGLIB$$d65f9e34

輸出結(jié)果:true

總結(jié)CgLib的特點(diǎn):

  • 使用CGLib實(shí)現(xiàn)動(dòng)態(tài)代理,完全不受代理類必須實(shí)現(xiàn)接口的限制
  • CGLib底層采用ASM字節(jié)碼生成框架,使用字節(jié)碼技術(shù)生成代理類,比使用Java反射效率要高
  • CGLib不能對(duì)聲明為final的方法進(jìn)行代理,因?yàn)镃GLib原理是動(dòng)態(tài)生成被代理類的子類

3)Javassist動(dòng)態(tài)代理

Javassist是一個(gè)開源的分析、編輯和創(chuàng)建Java字節(jié)碼的類庫,可以直接編輯和生成Java生成的字節(jié)碼。

相對(duì)于bcel, asm等這些工具,開發(fā)者不需要了解虛擬機(jī)指令,就能動(dòng)態(tài)改變類的結(jié)構(gòu),或者動(dòng)態(tài)生成類。

使用avassist動(dòng)態(tài)代理前需要引入依賴:

<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.27.0-GA</version>
</dependency>

使用Javassist生成動(dòng)態(tài)代理可以有以下兩種方式:

  • 代理工廠創(chuàng)建:需要實(shí)現(xiàn)MethodHandler用于代理邏輯處理,實(shí)現(xiàn)與CGLib非常類似
  • 動(dòng)態(tài)代碼創(chuàng)建:可通過Java代碼生成字節(jié)碼,這種方式創(chuàng)建的動(dòng)態(tài)代理非常靈活,甚至可以在運(yùn)行時(shí)生成業(yè)務(wù)邏輯

代理工廠創(chuàng)建 — 代理邏輯處理類

public class JavassistProxyHandler implements MethodHandler {
    private ProxyFactory proxyFactory = new ProxyFactory();
    /**
     * 獲取代理對(duì)象
     * @param clazz 被代理類
     * @return
     * @throws Exception
     */
    public Object getProxy(Class clazz) throws Exception {
        proxyFactory.setSuperclass(clazz);
        Class<?> factoryClass = proxyFactory.createClass();
        Object proxy = factoryClass.newInstance();
        ((ProxyObject)proxy).setHandler(this);
        return proxy;
    }
    public Object invoke(Object object, Method method, Method method1, Object[] args) throws Throwable {
        System.out.println("處理前");
        Object result = method1.invoke(object,args);
        System.out.println("處理后");
        return result;
    }
}

客戶端調(diào)用:

@Test
public void testJavassistProxy() throws Exception {
    BdSender sender = (BdSender) new JavassistProxyHandler().getProxy(BdSender.class);
    boolean result = sender.send();
    System.out.println("代理對(duì)象:" + sender.getClass().getName());
    System.out.println("輸出結(jié)果:" + result);
}

輸出結(jié)果

處理前

sending msg

處理后

代理對(duì)象:org.yd.proxy.BdSender_$$_jvstbce_0

輸出結(jié)果:true

動(dòng)態(tài)代碼創(chuàng)建 — 代理邏輯處理類:

public static Object getProxy(Class clazz) throws Exception {
    ClassPool mPool = ClassPool.getDefault();
    CtClass c0 = mPool.get(clazz.getName());
    //定義代理類名稱
    CtClass mCtc = mPool.makeClass(clazz.getName() + "$$BytecodeProxy");
    //添加父類繼承
    mCtc.setSuperclass(c0);
    //添加類的字段信息
    CtField field = new CtField(c0, "real", mCtc);
    field.setModifiers(AccessFlag.PRIVATE);
    mCtc.addField(field);
    //添加構(gòu)造函數(shù)
    CtConstructor constructor = new CtConstructor(new CtClass[]{c0},mCtc);
    constructor.setBody("{$0.real = $1;}"); // $0代表this, $1代表構(gòu)造函數(shù)的第1個(gè)參數(shù)
    mCtc.addConstructor(constructor);
    //添加方法
    CtMethod ctMethod = mCtc.getSuperclass().getDeclaredMethod("send");
    CtMethod newMethod = new CtMethod(ctMethod.getReturnType(), ctMethod.getName(),ctMethod.getParameterTypes(), mCtc);
    newMethod.setBody("{" +
            "System.out.println(\"處理前\");" +
            "boolean result = $0.real.send();" +
            "System.out.println(\"處理后\");" +
            "return result;}");
    mCtc.addMethod(newMethod);
    //生成動(dòng)態(tài)類
    return mCtc.toClass().getConstructor(clazz).newInstance(clazz.newInstance());
}

客戶端調(diào)用:

@Test
public void testJavassisBytecodetProxy() throws Exception {
    BdSender sender = (BdSender) JavassistDynamicCodeProxy.getProxy(BdSender.class);
    boolean result = sender.send();
    System.out.println("代理對(duì)象:" + sender.getClass().getName());
    System.out.println("輸出結(jié)果:" + result);
}

輸出結(jié)果:

處理前

sending msg

處理后

代理對(duì)象:org.yd.proxy.BdSender$$BytecodeProxy

輸出結(jié)果:true

Javassist被用于struts2和hibernate中,都用來做動(dòng)態(tài)字節(jié)碼修改使用。一般開發(fā)中不會(huì)用到,但在封裝框架時(shí)比較有用。

以上介紹了靜態(tài)代理和動(dòng)態(tài)代理創(chuàng)建的幾種方法與優(yōu)缺點(diǎn)介紹,希望可以幫到大家。

到此這篇關(guān)于Java四種動(dòng)態(tài)代理實(shí)現(xiàn)方式的文章就介紹到這了,更多相關(guān)Java動(dòng)態(tài)代理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 詳解spring boot 以jar的方式啟動(dòng)常用shell腳本

    詳解spring boot 以jar的方式啟動(dòng)常用shell腳本

    本篇文章主要介紹了詳解spring boot 以jar的方式啟動(dòng)常用shell腳本,具有一定的參考價(jià)值,有興趣的可以了解一下
    2017-09-09
  • Java中的單向鏈表詳解

    Java中的單向鏈表詳解

    這篇文章主要介紹了Java中的單向鏈表詳解,單向鏈表又叫單鏈表,是鏈表的一種,由節(jié)點(diǎn)構(gòu)成,head指針指向第一個(gè)稱為表頭節(jié)點(diǎn),而終止指向最后一個(gè)null指針,需要的朋友可以參考下
    2024-01-01
  • IDEA引MAVEN項(xiàng)目jar包依賴導(dǎo)入問題解決方法

    IDEA引MAVEN項(xiàng)目jar包依賴導(dǎo)入問題解決方法

    這篇文章主要介紹了IDEA引MAVEN項(xiàng)目jar包依賴導(dǎo)入問題解決,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-11-11
  • maven多模塊打包注意事項(xiàng)詳解

    maven多模塊打包注意事項(xiàng)詳解

    這篇文章主要為大家介紹了maven多模塊打包注意事項(xiàng)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-07-07
  • Spring Boot統(tǒng)一返回體的踩坑記錄

    Spring Boot統(tǒng)一返回體的踩坑記錄

    這篇文章主要給大家介紹了關(guān)于Spring Boot統(tǒng)一返回體踩坑的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-05-05
  • JAVA通過Filter實(shí)現(xiàn)允許服務(wù)跨域請(qǐng)求的方法

    JAVA通過Filter實(shí)現(xiàn)允許服務(wù)跨域請(qǐng)求的方法

    這里的域指的是這樣的一個(gè)概念:我們認(rèn)為若協(xié)議 + 域名 + 端口號(hào)均相同,那么就是同域即我們常說的瀏覽器請(qǐng)求的同源策略。這篇文章主要介紹了JAVA通過Filter實(shí)現(xiàn)允許服務(wù)跨域請(qǐng)求,需要的朋友可以參考下
    2018-11-11
  • Spring Boot中使用Spring-data-jpa實(shí)現(xiàn)數(shù)據(jù)庫增刪查改

    Spring Boot中使用Spring-data-jpa實(shí)現(xiàn)數(shù)據(jù)庫增刪查改

    本篇文章主要介紹了Spring Boot中使用Spring-data-jpa實(shí)現(xiàn)增刪查改,非常具有實(shí)用價(jià)值,需要的朋友可以參考下。
    2017-03-03
  • SPRINGMVC 406問題解決方案

    SPRINGMVC 406問題解決方案

    這篇文章主要介紹了SPRINGMVC 406問題解決方案,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-11-11
  • Java增強(qiáng)for循環(huán)的增刪操作代碼

    Java增強(qiáng)for循環(huán)的增刪操作代碼

    Foreach循環(huán)(Foreach loop)是計(jì)算機(jī)編程語言中的一種控制流程語句,通常用來循環(huán)遍歷數(shù)組或集合中的元素,本文通過實(shí)例演示普通for循環(huán)和foreach循環(huán)使用,java增強(qiáng)for循環(huán)的操作代碼感興趣的朋友一起看看吧
    2024-02-02
  • mybatis中的擴(kuò)展實(shí)現(xiàn)源碼解析

    mybatis中的擴(kuò)展實(shí)現(xiàn)源碼解析

    這篇文章主要介給大家紹了關(guān)于mybatis中擴(kuò)展實(shí)現(xiàn)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-01-01

最新評(píng)論