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

JDK動態(tài)代理接口和接口實現(xiàn)類深入詳解

 更新時間:2022年06月20日 15:38:37   作者:@fishv  
這篇文章主要介紹了JDK動態(tài)代理接口和接口實現(xiàn)類,JDK動態(tài)代理是代理模式的一種實現(xiàn)方式,因為它是基于接口來做代理的,所以也常被稱為接口代理,文中通過實例代碼介紹的非常詳細(xì),需要的朋友可以參考下

前言

在Java開發(fā)中,JDK動態(tài)代理是一種非常有用的技術(shù),它允許開發(fā)者在不修改目標(biāo)類代碼的情況下,為目標(biāo)類添加額外的功能。然而,JDK動態(tài)代理的使用有一些限制,特別是它只能代理接口和接口實現(xiàn)類。本文將深入探討這一限制的原因。

1.JDK動態(tài)代理原理

下面是一個簡單的動態(tài)代理的例子

 
/**
 * 要代理的接口
 */
public interface Target{
    String say();
}
/**
 * 真實調(diào)用對象
 */
public class RealTarget {
    public String invoke(){
        return "i'm proxy";
    }
}
/**
 * JDK代理類生成
 */
public class JDKProxy implements InvocationHandler {
    private Object target;
    JDKProxy(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] paramValues) {
        System.out.println("Before method invocation");
        Object result = ((RealTarget)target).invoke();
        System.out.println("After method invocation");
        return result;
    }
}
/**
 * 測試?yán)?
 */
public class TestProxy {
    public static void main(String[] args){
        // 構(gòu)建代理器
        JDKProxy proxy = new JDKProxy(new RealTarget());
        ClassLoader classLoader = ClassLoaderUtils.getCurrentClassLoader();
        // 把生成的代理類保存到文件
        System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
        // 生成代理類
        Target test = (Target) Proxy.newProxyInstance(classLoader, new Class[]{Target.class}, proxy);
        // 方法調(diào)用
        System.out.println(test.say());
    }
}

這段代碼想表達(dá)的意思就是:給 Target接口生成一個動態(tài)代理類,并調(diào)用接口 say() 方法,但真實返回的值居然是來自 RealTarget里面的 invoke() 方法返回值。你看,短短50行的代碼,就完成了這個功能,是不是還挺有意思的?

那既然重點是代理類的生成,那我們就去看下 Proxy.newProxyInstance 里面究竟發(fā)生了什么?

一起看下下面的流程圖,具體代碼細(xì)節(jié)你可以對照著 JDK 的源碼看(上文中有類和方法,可以直接定位),我是按照 1.7.X 版本梳理的。

在生成字節(jié)碼的那個地方,也就是 ProxyGenerator.generateProxyClass() 方法里面,通過代碼我們可以看到,里面是用參數(shù) saveGeneratedFiles 來控制是否把生成的字節(jié)碼保存到本地磁盤。同時為了更直觀地了解代理的本質(zhì),我們需要把參數(shù) saveGeneratedFiles 設(shè)置成true,但這個參數(shù)的值是由key為“sun.misc.ProxyGenerator.saveGeneratedFiles”的Property來控制的,動態(tài)生成的類會保存在工程根目錄下的 com/sun/proxy 目錄里面?,F(xiàn)在我們找到剛才生成的 $Proxy0.class,通過反編譯工具打開class文件,你會看到這樣的代碼:

package com.sun.proxy;
import com.proxy.Hello;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements Target {
  private static Method m3;
  private static Method m1;
  private static Method m0;
  private static Method m2;
  public $Proxy0(InvocationHandler paramInvocationHandler) {
    super(paramInvocationHandler);
  }
  public final String say() {
    try {
      return (String)this.h.invoke(this, m3, null);
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw new UndeclaredThrowableException(throwable);
    } 
  }
  public final boolean equals(Object paramObject) {
    try {
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw new UndeclaredThrowableException(throwable);
    } 
  }
  public final int hashCode() {
    try {
      return ((Integer)this.h.invoke(this, m0, null)).intValue();
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw new UndeclaredThrowableException(throwable);
    } 
  }
  public final String toString() {
    try {
      return (String)this.h.invoke(this, m2, null);
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw new UndeclaredThrowableException(throwable);
    } 
  }
  static {
    try {
      m3 = Class.forName("com.proxy.Target").getMethod("say", new Class[0]);
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      return;
    } catch (NoSuchMethodException noSuchMethodException) {
      throw new NoSuchMethodError(noSuchMethodException.getMessage());
    } catch (ClassNotFoundException classNotFoundException) {
      throw new NoClassDefFoundError(classNotFoundException.getMessage());
    } 
  }
}

我們可以看到 $Proxy0 類里面有一個跟 Target一樣簽名的 say() 方法,其中 this.h 綁定的是剛才傳入的 JDKProxy 對象,所以當(dāng)我們調(diào)用 Target.say() 的時候,其實它是被轉(zhuǎn)發(fā)到了JDKProxy.invoke()。到這兒,整個魔術(shù)過程就透明了。

2.回答JDK動態(tài)代理的疑問

為何只能代理具有接口的類

這是因為JDK動態(tài)代理的機制所限。在Java中,動態(tài)代理通過Proxy.newProxyInstance()方法實現(xiàn),此方法要求傳入一個接口類作為被代理對象。這源于JDK動態(tài)代理的底層實現(xiàn):它在程序運行時動態(tài)生成一個名為$Proxy0的代理類,該代理類繼承自java.lang.reflect.Proxy并實現(xiàn)了被代理的接口。由于Java不支持多重繼承,每個動態(tài)代理類都繼承自Proxy,因此只能代理接口,而無法代理具體實現(xiàn)類。

JDK動態(tài)代理能否代理類

JDK中的Proxy類主要用于保存動態(tài)代理的處理器InvocationHandler。理論上,如果不通過Proxy類而直接在動態(tài)生成的代理類內(nèi)部設(shè)置處理器,可能實現(xiàn)對類的動態(tài)代理。然而,JDK的設(shè)計者并未采取這種方式,這主要是出于設(shè)計上的考慮和限制。

為何這樣設(shè)計

  • 使用場景與需求:動態(tài)代理的主要用途是在不修改原始實現(xiàn)的前提下,對方法進(jìn)行攔截以實現(xiàn)功能增強或擴(kuò)展。在實際開發(fā)中,基于接口編程是常見模式,因此基于接口實現(xiàn)動態(tài)代理符合需求和場景。當(dāng)然,也存在沒有實現(xiàn)接口的類,此時JDK動態(tài)代理無法滿足需求。
  • 代碼重用與擴(kuò)展性:在Java中,類的設(shè)計更注重共性能力的抽象,以提高代碼的重用性和擴(kuò)展性。動態(tài)代理也遵循這一原則,它封裝了代理類的生成邏輯、接口判斷以及InvocationHandler的持有等,將這些抽象邏輯放在Proxy父類中是一個合理的選擇。

其他實現(xiàn)方式

對于需要代理沒有接口的類,可以選擇使用CGLIB動態(tài)代理。CGLIB通過動態(tài)生成被代理類的子類,并重寫非final修飾的方法,在子類中攔截父類方法的調(diào)用,從而實現(xiàn)動態(tài)代理。這種方式彌補了JDK動態(tài)代理只能代理接口的不足。

3.總結(jié)

JDK動態(tài)代理是Java中一種強大而靈活的技術(shù),它允許在不修改原始代碼的情況下對目標(biāo)對象的方法進(jìn)行功能增強。然而,由于其基于接口的代理機制,它只能代理接口和接口實現(xiàn)類。對于需要代理沒有實現(xiàn)接口的類的情況,可以考慮使用CGLIB動態(tài)代理等替代方案。在實際開發(fā)中,應(yīng)根據(jù)具體需求選擇合適的代理機制,以實現(xiàn)最佳的性能和可維護(hù)性。

到此這篇關(guān)于JDK動態(tài)代理接口和接口實現(xiàn)類深入詳解的文章就介紹到這了,更多相關(guān)JDK動態(tài)代理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • JAVA項目字典與緩存搭配使用方法解析

    JAVA項目字典與緩存搭配使用方法解析

    這篇文章主要介紹了JAVA項目字典與緩存搭配使用方法解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-09-09
  • Feign遠(yuǎn)程調(diào)用Multipartfile參數(shù)處理

    Feign遠(yuǎn)程調(diào)用Multipartfile參數(shù)處理

    這篇文章主要介紹了Feign遠(yuǎn)程調(diào)用Multipartfile參數(shù)處理,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • spring拓展之如何定義自己的namespace

    spring拓展之如何定義自己的namespace

    這篇文章主要介紹了spring拓展之如何定義自己的namespace方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • Spring Boot 整合 TKMybatis 二次簡化持久層代碼的實現(xiàn)

    Spring Boot 整合 TKMybatis 二次簡化持久層代碼的實現(xiàn)

    這篇文章主要介紹了Spring Boot 整合 TKMybatis 二次簡化持久層代碼的實現(xiàn),本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-01-01
  • Win10系統(tǒng)下配置java環(huán)境變量的全過程

    Win10系統(tǒng)下配置java環(huán)境變量的全過程

    這篇文章主要給大家介紹了關(guān)于Win10系統(tǒng)下配置java環(huán)境變量的相關(guān)資料,文中通過圖文介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-11-11
  • Java遞歸調(diào)用如何實現(xiàn)數(shù)字的逆序輸出方式

    Java遞歸調(diào)用如何實現(xiàn)數(shù)字的逆序輸出方式

    這篇文章主要介紹了Java遞歸調(diào)用如何實現(xiàn)數(shù)字的逆序輸出方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-04-04
  • Mybatis如何通過注解開啟使用二級緩存

    Mybatis如何通過注解開啟使用二級緩存

    這篇文章主要介紹了Mybatis基于注解開啟使用二級緩存,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-11-11
  • idea?compile項目正常啟動項目的時候build失敗報“找不到符號”等問題及解決方案

    idea?compile項目正常啟動項目的時候build失敗報“找不到符號”等問題及解決方案

    這篇文章主要介紹了idea?compile項目正常,啟動項目的時候build失敗,報“找不到符號”等問題,這種問題屬于lombok編譯失敗導(dǎo)致,可能原因是依賴jar包沒有更新到最新版本,需要的朋友可以參考下
    2023-10-10
  • Java如何使用ReentrantLock實現(xiàn)長輪詢

    Java如何使用ReentrantLock實現(xiàn)長輪詢

    這篇文章主要介紹了如何使用ReentrantLock實現(xiàn)長輪詢,對ReentrantLock感興趣的同學(xué),可以參考下
    2021-04-04
  • SpringBoot集成ElasticSearch的示例代碼

    SpringBoot集成ElasticSearch的示例代碼

    Elasticsearch是用Java語言開發(fā)的,并作為Apache許可條款下的開放源碼發(fā)布,是一種流行的企業(yè)級搜索引擎,本文給大家介紹SpringBoot集成ElasticSearch的示例代碼,感興趣的朋友一起看看吧
    2022-02-02

最新評論