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

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

 更新時(shí)間:2022年06月20日 15:38:37   作者:@fishv  
這篇文章主要介紹了JDK動態(tài)代理接口和接口實(shí)現(xiàn)類,JDK動態(tài)代理是代理模式的一種實(shí)現(xiàn)方式,因?yàn)樗腔诮涌趤碜龃淼?所以也常被稱為接口代理,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下

前言

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

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

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

 
/**
 * 要代理的接口
 */
public interface Target{
    String say();
}
/**
 * 真實(shí)調(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() 方法,但真實(shí)返回的值居然是來自 RealTarget里面的 invoke() 方法返回值。你看,短短50行的代碼,就完成了這個功能,是不是還挺有意思的?

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

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

在生成字節(jié)碼的那個地方,也就是 ProxyGenerator.generateProxyClass() 方法里面,通過代碼我們可以看到,里面是用參數(shù) saveGeneratedFiles 來控制是否把生成的字節(jié)碼保存到本地磁盤。同時(shí)為了更直觀地了解代理的本質(zhì),我們需要把參數(shù) saveGeneratedFiles 設(shè)置成true,但這個參數(shù)的值是由key為“sun.misc.ProxyGenerator.saveGeneratedFiles”的Property來控制的,動態(tài)生成的類會保存在工程根目錄下的 com/sun/proxy 目錄里面。現(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() 的時(shí)候,其實(shí)它是被轉(zhuǎn)發(fā)到了JDKProxy.invoke()。到這兒,整個魔術(shù)過程就透明了。

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

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

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

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

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

為何這樣設(shè)計(jì)

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

其他實(shí)現(xiàn)方式

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

3.總結(jié)

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

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

相關(guān)文章

最新評論