最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

理解JDK動態(tài)代理為什么必須要基于接口

 更新時間:2022年10月12日 16:47:33   作者:桐花思雨  
這篇文章主要介紹了理解JDK動態(tài)代理為什么必須要基于接口,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教

1. 前言

JDK 動態(tài)代理的應用還是非常廣泛的,例如在 Spring、MyBatis 以及 Feign 等很多框架中動態(tài)代理都被大量的使用,可以說學好 JDK 動態(tài)代理,對于我們閱讀這些框架的底層源碼還是很有幫助的

2. 一個簡單的例子

在分析原因之前,我們先完整的看一下實現 JDK 動態(tài)代理需要幾個步驟,首先需要定義一個接口

2.1. 定義接口

public interface Worker {
    void work();
}

2.2. 接口實現類

public class Programmer implements Worker {
    @Override
    public void work() {
        System.out.println("coding...");
    }
}

2.3. 自定義 Handler

自定義一個 Handler,實現 InvocationHandler 接口,通過重寫內部的 invoke() 方法實現邏輯增強

public class WorkHandler implements InvocationHandler {
    private final Object target;
    public WorkHandler(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equals("work")) {
            System.out.println("before work...");
            Object result = method.invoke(target, args);
            System.out.println("after work...");
            return result;
        }
        return method.invoke(target, args);
    }
}

2.4. 測試

在 main() 方法中進行測試,使用 Proxy 類的靜態(tài)方法 newProxyInstance() 生成一個代理對象并調用方法

public class MainTest {
    public static void main(String[] args) {
        Programmer programmer = new Programmer();
        Worker worker = (Worker) Proxy.newProxyInstance(
                programmer.getClass().getClassLoader(),
                programmer.getClass().getInterfaces(),
                new WorkHandler(programmer));
        worker.work();
    }
}

2.5. 輸出結果

before work...
coding...
after work...

3. 源碼分析

既然是一個代理的過程,那么肯定存在原生對象和代理對象之分,下面我們查看源碼中是如何動態(tài)的創(chuàng)建代理對象的過程。

上面例子中,創(chuàng)建代理對象調用的是 Proxy 類的靜態(tài)方法 newProxyInstance(),查看一下源碼

3.1. newProxyInstance() 方法

public class Proxy implements java.io.Serializable {
	protected InvocationHandler h;
	// 有參構造器,參數是 InvocationHandler 
	protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
    }
	@CallerSensitive
	public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
		throws IllegalArgumentException {
	
		// 如果h為空直接拋出空指針異常,之后所有的單純的判斷null并拋異常,都是此方法
		Objects.requireNonNull(h);
		// 拷貝類實現的所有接口
    	final Class<?>[] intfs = interfaces.clone();
		// 獲取當前系統(tǒng)安全接口
    	final SecurityManager sm = System.getSecurityManager();
    	if (sm != null) {
			// Reflection.getCallerClass 返回調用該方法的方法的調用類;loader:接口的類加載器
			// 進行包訪問權限、類加載器權限等檢查
			checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
		}
 
		// 查找或生成指定的代理類
		Class<?> cl = getProxyClass0(loader, intfs);
 
		// 用指定的調用處理程序調用它的構造函數
		try {
			if (sm != null) {
				checkNewProxyPermission(Reflection.getCallerClass(), cl);
        	}
	   		// 獲取代理類的構造函數對象
	    	// constructorParams是類常量,作為代理類構造函數的參數類型,常量定義如下:
	    	// private static final Class<?>[] constructorParams = { InvocationHandler.class };
			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;
                	}
            });
		}
			// 根據代理類的構造函數對象來創(chuàng)建需要返回的代理類對象
			return cons.newInstance(new Object[]{h});
		} // 省略 catch......
	}
}
  • 在 checkProxyAccess() 方法中,進行參數驗證
  • 在 getProxyClass0() 方法中,生成一個代理類 Class 或尋找已生成過的代理類的緩存
  • 通過 getConstructor() 方法獲取生成的代理類的構造方法
  • 通過 newInstance() 方法,生成最終的代理對象

上面這個過程中,獲取構造方法和生成代理對象都是利用的 Java 中的反射機制,而需要重點看的是生成代理類的方法 getProxyClass0()

3.1.1. getProxyClass0() 方法

private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
                                           
	// 接口數不得超過 65535 個,這么大,足夠使用的了
	if (interfaces.length > 65535) {
		throw new IllegalArgumentException("interface limit exceeded");
	}
	// 如果緩存中有代理類了直接返回,否則將由代理類工廠ProxyClassFactory創(chuàng)建代理類
	return proxyClassCache.get(loader, interfaces);
}

如果緩存中已經存在了就直接從緩存中取,這里的 proxyClassCache 是一個 WeakCache 類型,如果緩存中目標 classLoader 和接口數組對應的類已經存在,那么返回緩存的副本。如果沒有就使用 ProxyClassFactory 去生成 Class 對象

3.1.1.1. get() 方法

// key:類加載器;parameter:接口數組
public V get(K key, P parameter) {
	// 檢查指定類型的對象引用不為空null。當參數為null時,拋出空指針異常
	Objects.requireNonNull(parameter);
	// 清除已經被 GC 回收的弱引用
	expungeStaleEntries();
	// 將ClassLoader包裝成CacheKey, 作為一級緩存的key
	Object cacheKey = CacheKey.valueOf(key, refQueue);
 
	// 獲取得到二級緩存
    ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
	// 沒有獲取到對應的值
	if (valuesMap == null) {
		ConcurrentMap<Object, Supplier<V>> oldValuesMap
                = map.putIfAbsent(cacheKey,
                                  valuesMap = new ConcurrentHashMap<>());
		if (oldValuesMap != null) {
                valuesMap = oldValuesMap;
        }
	}
 
	// 根據代理類實現的接口數組來生成二級緩存key
	Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
	// 通過subKey獲取二級緩存值
	Supplier<V> supplier = valuesMap.get(subKey);
	Factory factory = null;
	// 這個循環(huán)提供了輪詢機制, 如果條件為假就繼續(xù)重試直到條件為真為止
	while (true) {
		if (supplier != null) {
			// 在這里supplier可能是一個Factory也可能會是一個CacheValue
			// 在這里不作判斷, 而是在Supplier實現類的get方法里面進行驗證
            V value = supplier.get();
            if (value != null) {
            	return value;
            }
        }
		if (factory == null) {
            factory = new Factory(key, parameter, subKey, valuesMap);
        }
		if (supplier == null) {
			// 到這里表明subKey沒有對應的值, 就將factory作為subKey的值放入
        	supplier = valuesMap.putIfAbsent(subKey, factory);
           	if (supplier == null) {
                supplier = factory;
           	}
			// 否則, 可能期間有其他線程修改了值, 那么就不再繼續(xù)給subKey賦值, 而是取出來直接用
            } else {
			// 期間可能其他線程修改了值, 那么就將原先的值替換
            if (valuesMap.replace(subKey, supplier, factory)) {
                supplier = factory;
			} else {
                supplier = valuesMap.get(subKey);
            }
		}
	}
}

很明顯,重點關注下面代碼

Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));

3.1.1.1.1. apply() 方法

private static final class ProxyClassFactory
        implements BiFunction<ClassLoader, Class<?>[], Class<?>> {
    
	// 代理類的前綴名都以 $Proxy 開始
	private static final String proxyClassNamePrefix = "$Proxy";
 
	// 使用唯一的編號給作為代理類名的一部分,如 $Proxy0,$Proxy1 等
	private static final AtomicLong nextUniqueNumber = new AtomicLong();
 
	@Override
	public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
 
   		Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
        for (Class<?> intf : interfaces) {
                
        	// 驗證指定的類加載器(loader)加載接口所得到的Class對象(interfaceClass)是否與intf對象相同
			Class<?> interfaceClass = null;
            try {
				interfaceClass = Class.forName(intf.getName(), false, loader);
			} catch (ClassNotFoundException e) {
			}
			if (interfaceClass != intf) {
				throw new IllegalArgumentException(
                	intf + " is not visible from class loader");
			}
                
            // 驗證該Class對象是不是接口
            if (!interfaceClass.isInterface()) {
				throw new IllegalArgumentException(
                	interfaceClass.getName() + " is not an interface");
			}
                
            // 驗證該接口是否重復
            if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
            	throw new IllegalArgumentException(
                	"repeated interface: " + interfaceClass.getName());
            }
		}
	    // 聲明代理類所在包
        String proxyPkg = null;    
        int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
 
        // 驗證所有非公共的接口在同一個包內;公共的就無需處理
        for (Class<?> intf : interfaces) {
        	int flags = intf.getModifiers();
            if (!Modifier.isPublic(flags)) {
            	accessFlags = Modifier.FINAL;
                String name = intf.getName();
                int n = name.lastIndexOf('.');
				// 截取完整包名
                String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                if (proxyPkg == null) {
                	proxyPkg = pkg;
                } else if (!pkg.equals(proxyPkg)) {
                	throw new IllegalArgumentException(
                    	"non-public interfaces from different packages");
                }
        	}
		}
 		// 1.根據規(guī)則生成文件名
		if (proxyPkg == null) {
			/*如果都是public接口,那么生成的代理類就在com.sun.proxy包下如果報		java.io.FileNotFoundException: com\sun\proxy\$Proxy0.class 
			(系統(tǒng)找不到指定的路徑。)的錯誤,就先在你項目中創(chuàng)建com.sun.proxy路徑*/
        	proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
		}
        long num = nextUniqueNumber.getAndIncrement();
	    // 代理類的完全限定類名,如 com.sun.proxy.$Proxy0.calss
        String proxyName = proxyPkg + proxyClassNamePrefix + num;
 
        // 2.生成代理的字節(jié)碼數組
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
        // 3.生成 Class 
        try {
        	return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
        } catch (ClassFormatError e) {
            throw new IllegalArgumentException(e.toString());
        }
	}
}

在 apply() 方法中,主要做了下面 3 件事

  • 根據規(guī)則生成文件名
  • 利用 ProxyGenerator.generateProxyClass() 方法生成代理的字節(jié)碼數組
  • 調用方法 defineClass0() 生成 Class

3.1.2. getConstructor() 和 newInstance() 方法

返回代理類的 Class 后的流程,獲取構造方法和生成代理對象都是利用的 Java 中的反射機制

4. 代理對象長啥樣

4.1. 代理對象長啥樣

創(chuàng)建代理對象流程的源碼分析完了,我們可以先通過 debug 來看看上面生成的這個代理對象究竟是個什么

和源碼中看到的規(guī)則一樣,是一個 Class 為 $Proxy0 的對象。再看一下代理對象的 Class 的詳細信息

類的全限定類名是 com.sun.proxy.$Proxy0,在上面我們提到過,這個類是在運行過程中動態(tài)生成的

4.2. $Proxy0 反編譯

看一下反編譯后 $Proxy0.java 文件的內容,下面的代碼中,我只保留了核心部分,省略了無關緊要的 equals()、toString()、hashCode() 方法的定義

public final class $Proxy0 extends Proxy implements Worker{
    public $Proxy0(InvocationHandler invocationhandler){
        super(invocationhandler);
    }
    public final void work(){
        try{
            super.h.invoke(this, m3, null);
            return;
        }catch(Error _ex) { }
        catch(Throwable throwable){
            throw new UndeclaredThrowableException(throwable);
        }
    }
    private static Method m3;
    static {
        try{           
            m3 = Class.forName("com.hydra.test.Worker").getMethod("work", new Class[0]);   
            //省略其他Method
        }//省略catch
    }
}

這個臨時生成的代理類 $Proxy0 中主要做了下面的幾件事

  • 在這個類的靜態(tài)代碼塊中,通過 反射機制 初始化了多個靜態(tài)方法 Method 變量,除了接口中的方法還有 equals()、toString()、hashCode() 這三個方法
  • 代理類 $Proxy0 繼承了父類 Proxy,在其實例化的過程中會調用父類的構造器,而父類 Proxy 中的構造器中傳入的 InvocationHandler 對象實際上是我們自定義的 WorkHandler 的實例。此時就可以調用 WorkHandler 的 invoke() 方法了
  • 同時,代理類 $Proxy0 也實現了自定義的接口 Worker,并重寫了 work() 方法,在 work()方法內又調用了 InvocationHandler 的 invoke() 方法,也就是實際上調用了 WorkHandler 的 invoke() 方法

到這里,整體的流程就分析完了,我們可以用一張圖來簡要總結上面的過程

5. JDK 動態(tài)代理為什么要有接口

其實如果不看上面的分析,我們也應該知道,要擴展一個類有常見的兩種方式,繼承父類或實現接口。這兩種方式都允許我們對方法的邏輯進行增強,但現在不是由我們自己來重寫方法,而是要想辦法讓 JVM 去調用 InvocationHandler 中的 invoke() 方法,也就是說代理類需要和兩個東西關聯(lián)在一起

  • 被代理類
  • InvocationHandler

而 JDK 動態(tài)代理處理這個問題的方式是選擇繼承父類 Proxy,并把 InvocationHandler 保存在父類的對象中

public class Proxy implements java.io.Serializable {
    protected InvocationHandler h;
    
    protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
    }
    
    // ......
}

通過父類 Proxy 的構造方法,保存了創(chuàng)建代理對象過程中傳進來的 InvocationHandler 的實例,使用 protected 修飾保證了它可以在子類中被訪問和使用。

但是同時,因為 Java 是單繼承的,因此在代理類 $Proxy0 繼承了 Proxy 后,其只能通過實現目標接口的方式來實現方法的擴展,達到我們增強目標方法邏輯的目的

以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關文章

  • log4j的配置文件詳細解析

    log4j的配置文件詳細解析

    以下小編主要為大家介紹一下log4j的配置文件各個配置項的含義。需要的朋友可以過來參考下
    2013-08-08
  • 集成apollo動態(tài)日志取締logback-spring.xml配置

    集成apollo動態(tài)日志取締logback-spring.xml配置

    這篇文章主要為大家介紹了集成apollo動態(tài)日志取締logback-spring.xml配置的過程內容詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助
    2022-02-02
  • Java流程控制之循環(huán)結構for,增強for循環(huán)

    Java流程控制之循環(huán)結構for,增強for循環(huán)

    這篇文章主要介紹了Java流程控制之循環(huán)結構for,增強for循環(huán),for循環(huán)是編程語言中一種循環(huán)語句,而循環(huán)語句由循環(huán)體及循環(huán)的判定條件兩部分組成,其表達式為:for(單次表達式;條件表達式;末尾循環(huán)體){中間循環(huán)體;},下面我們倆看看文章內容的詳細介紹
    2021-12-12
  • ibatis結合oracle批量插入三種方法的測評

    ibatis結合oracle批量插入三種方法的測評

    今天小編就為大家分享一篇關于ibatis結合oracle批量插入三種方法的測評,小編覺得內容挺不錯的,現在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2018-12-12
  • Java實現JDBC連接數據庫簡單案例

    Java實現JDBC連接數據庫簡單案例

    這篇文章主要介紹了Java實現JDBC連接數據庫簡單案例,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-08-08
  • Java如何實現多個線程之間共享數據

    Java如何實現多個線程之間共享數據

    這篇文章主要介紹了Java如何實現多個線程之間共享數據,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • java字符串與格式化輸出的深入分析

    java字符串與格式化輸出的深入分析

    本篇文章是對java字符串與格式化輸出進行了詳細的分析介紹,需要的朋友參考下
    2013-06-06
  • 為什么說HashMap線程不安全

    為什么說HashMap線程不安全

    本文主要介紹了為什么說HashMap線程不安全,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-04-04
  • 詳解Spring Cloud Alibaba Sidecar多語言微服務異構

    詳解Spring Cloud Alibaba Sidecar多語言微服務異構

    這篇文章主要介紹了詳解Spring Cloud Alibaba Sidecar多語言微服務異構,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-11-11
  • 詳解基于Spring Boot與Spring Data JPA的多數據源配置

    詳解基于Spring Boot與Spring Data JPA的多數據源配置

    本篇文章主要介紹了詳解基于Spring Boot與Spring Data JPA的多數據源配置,非常具有實用價值,需要的朋友可以參考下
    2017-05-05

最新評論

电白县| 富源县| 密山市| 鹤山市| 通山县| 屏边| 东乡族自治县| 恩平市| 丁青县| 宣武区| 三河市| 道真| 贵州省| 曲阜市| 阿尔山市| 临邑县| 巢湖市| 潞西市| 射洪县| 三都| 旅游| 台南市| 来宾市| 大化| 承德县| 南汇区| 东兰县| 化隆| 个旧市| 资兴市| 红桥区| 遂溪县| 那坡县| 赤水市| 嘉义市| 桂林市| 阿尔山市| 阳原县| 江孜县| 社会| 津南区|