大廠高級工程師面試必問系列:Java動態代理機制和實現原理詳解

本文已參與好文召集令活動,點擊查看:後端、大前端雙賽道投稿,2萬元獎池等你挑戰!前端

代理模式

  • Java動態代理運用了設計模式中經常使用的代理模式
  • 代理模式:
    • 目的就是爲其餘對象提供一個代理用來控制對某個真實對象的訪問
  • 代理類的做用:
    • 爲委託類預處理消息
    • 過濾消息並轉發消息
    • 進行消息被委託類執行後的後續處理
    在這裏插入圖片描述

經過代理層這一中間層,有效的控制對於真實委託類對象的直接訪問,同時又能夠實現自定義的控制策略,好比Spring中的AOP機制,這樣使得在設計上得到更大的靈活性java

  • 代理的基本構成:

在這裏插入圖片描述

  • 代理模式中有Subject角色 ,RealSubject角色和Proxy角色:
    • Subject: 負責定義RealSubjectProxy角色應該實現的接口
    • RealSubject: 用來真正完成業務服務功能
    • Proxy: 負責將自身的Request請求,調用RealSubject對應的request功能實現業務功能,自身不作真正的業務
  • 靜態代理模式:
    • 當在代碼階段規定這種代理關係 ,Proxy類經過編譯器編譯成class文件,當系統運行時,此class已經存在
    • 這種靜態代理模式能夠訪問沒法訪問的資源,加強現有的接口業務功能方面有很大的優勢.可是大量的使用這種靜態代理,會使系統內的類規模增大,而且不易維護
    • 因爲ProxyRealSubject的功能本質上是相同的 ,Proxy只是中介的做用,這種代理在系統中的存在,會形成代碼冗餘
  • 爲了解決靜態代理模式的問題,就有了動態建立Proxy:
    • 在運行狀態中,須要代理的地方,根據SubjectRealSubject, 動態地建立一個Proxy
    • Proxy使用完以後,就會銷燬,這樣就能夠避免Proxy角色的class在系統中的冗餘問題

Java動態代理

  • java.lang.reflect.Proxy:
    • Java動態代理機制的主類
    • 提供一組靜態方法爲一組接口動態的生成對象和代理類
// 該方法用於獲取指定代理對象所關聯的調用處理器
public static InvocationHandler getInvocationHandler(Object proxy);

// 該方法用於獲取關聯於指定類裝載器和一組接口的動態代理類的類對象
public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces); 

// 該方法用於判斷指定類對象是不是一個動態代理類
public static boolean isProxyClass(Class<?> cl);

// 該方法用於爲指定類裝載器,一組接口以及調用處理器生成動態代理類實例
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h);
複製代碼
  • java.lang.reflect.InvocationHandler:
    • 調用處理器接口,自定義invoke方法用於實現對真正委託類的代理訪問
/** * 該方法負責集中處理動態代理類上的全部方法調用 * * @param proxy 代理實例 * @param method 被調用的方法對象 * @param args 調用參數 * @return 返回調用處理器根據三個參數進行預處理或者分派到委託類實例上反射執行的對象 */
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
複製代碼
  • java.lang.ClassLoader:
    • 類裝載器
    • 將類的字節碼裝載到Java虛擬機即JVM中,併爲其定義類對象,而後該類才能被使用
    • Proxy類與普通類的惟一區別就是 :Proxy類字節碼是由JVM在運行時動態生成的而不是預存在於任何一個.calss文件中
    • 每次生成動態代理類對象時都須要指定一個類裝載器對象

Java動態代理機制

Java動態代理建立對象的過程:後端

  • 經過實現InvocationHandler接口建立本身的調用處理器
/* * InvocationHandlerImpl實現了InvocationHandler接口 * 並能實現方法調用從代理類到委託類的分派轉發向委託類實例的引用,用於真正執行分派轉發過來的方法調用 */
 InvocationHandler handler = new InvocationHandlerImpl(...);
複製代碼
  • 經過爲Proxy類指定ClassLoader對象和一組interface來建立動態代理類
// 經過Proxy爲包括Interface接口在內的一組接口動態建立代理類的類對象
Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... });
複製代碼
  • 經過反射機制得到動態代理類的構造函數,其惟一參數類型是調用處理器接口類型
// 經過反射從生成的類對象得到構造函數對象
Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class });
複製代碼
  • 經過構造函數建立動態代理類實例,構造時調用處理器對象做爲參數被傳入
// 經過構造函數建立動態代理類實例
Interface proxy = (Interface)constructor.newInstance(new Object[] { handler });
複製代碼

爲了簡化對象建立過程 ,Proxy類中使用newInstanceProxy封裝了步驟2-步驟4, 所以只須要兩個步驟便可完成代理對象的建立設計模式

// InvocationHandlerImpl實現了InvocationHandler接口,並能實現方法調用從代理類到委託類的分派轉發
InvocationHandler handler = new InvocationHandlerImpl(...); 
// 經過 Proxy 直接建立動態代理類的實例
Interface proxy = (Interface)Proxy.newProxyInstance(classLoader, new Class[] { Interface.class }, handler);
複製代碼

Java動態代理注意點

  • 包:
    • 代理接口是public, 則代理類被定義在頂層包 ,package爲空,不然default, 代理類被定義在該接口所在的包
/* * 記錄非公共代理接口的包,以便在同一個包中定義代理類 * 驗證全部非公共代理接口是否都在同一個包中 */
for (int i =0; i < interfaces.length; i++ ) {
	int flags = interfaces[i].getModifiers();
	if (!Modifier.isPublic(flags)) {
		String name = interfaces[i].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 packaes");
		}
	}
}
		if (proxyPkg == null) {
		// 沒有使用非公共代理接口代理類的包
		proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
		}
複製代碼
  • 生成的代理類爲public final,不能被繼承
  • 類名的格式爲 :"$ProxyN"
    • N是逐一遞增的數字,表明Proxy是被第N次動態代理生成的代理類
    • 對於同一組接口,接口的排列順序也相同,不會重複建立動態代理類,而是返回一個先前已經建立並緩存了的代理類對象,以此提升效率
synchronized (cache) {
	/* * 沒必要擔憂獲取到清除掉弱引用的緩存 * 由於若是一個代理類已經被垃圾回收,代理類的類加載器也會被垃圾回收 * 因此獲取到的緩存都是加載到緩存中的映射 */
	 do {
	 	Object value = cache.get(key);
	 	if (value instanceof Reference) {
	 		proxyClass = (Class) ((Reference) value).get();
	 		if (proxyClass != null) {
	 			/* * 代理類已經生成,返回代理類 */
	 			return proxyClass;
	 		} else if (value == pendingGenerationmarker) {
	 			/* * 代理類正在生成,等待代理類生成 */
	 			try {
	 				cache.wait();
	 			} catch (InterruptedException e) {
	 				/* * 等待生成的代理類有一個極小的限定的時間,所以能夠忽略線程在這裏的影響 */
	 			}
	 			continue;
	 		} else {
	 			/* * 若是沒有這個接口列表已經生成或者正在生成的代理類 * 須要去生成這些接口的代理類,將這些接口標記爲待生成 */
	 			 cache.put(key, pendingGenerationMarker);
	 			 break;
	 		}
	 	}while (true);
	 }
複製代碼
  • 類繼承關係:

在這裏插入圖片描述 Proxy類是父類,這個規則適用於全部由Proxy建立的動態代理類(這也致使Java動態代理的缺陷,因爲Java不支持多繼承,因此沒法實現對class的動態代理,只能對於Interface進行代理),該類實現了全部代理的一組接口,因此Proxy類可以被安全地類型轉換到其所代理的某接口數組

  • 代理類的根類java.lang.Object中的hashCode(),equals()和().toString方法一樣會被分派到調用處理器invoke方法執行

Java動態代理測試

建立一個動態代理類
public class serviceProxy implements InvocationHandler {
	private Object target;
	/** * 綁定委託對象並返回一個代理對象 * @param target 真實對象 * @return 代理對象 */
	public Object bind(Object target, Class[] interfaces) {
		this.target = target;
		// 取得代理對象
		return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
	}

	/** * 經過代理對象調用方法首先進入這個方法 * @param proxy 代理對象 * @param Method 方法,被調用方法 * @param args 方法的參數 */
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		/* * JDK動態代理 */
		 Object result = null;
		 // 反射方法前調用
		 System.err.println("--反射方法前執行的方法--");
		 // 反射執行方法,至關於調用target.xXX()
		 result = method.invoke(target, args);
		 // 反射方法後調用
		 System.err.println("--反射方法後執行的方法--");
		 return result;
	}
}
複製代碼
  • bind方法:
    • bind方法中的newProxyInstance方法,就是生成一個代理對象
      • 第一個參數: 類加載器
      • 第二個參數: 真實委託對象所實現的接口. 代理對象掛在那個接口下
      • 第三個參數: this表明當前HelloServiceProxy類, 也就是使用HelloServiceProxy做爲對象的代理
  • invoke方法:
    • invoke方法有三個參數:
      • 第一個proxy是代理對象
      • 第二個是當前調用那個方法
      • 第三個是方法的參數
ProxyTest
public class ProxyTest {
	public static void main(String[] args) {
		HelloServiceProxy proxy = new HelloServiceProxy();
		HelloService service = new HelloServiceImpl();
		// 綁定代理對象
		service = (HelloService) proxy.bind(service, new Class[] {HelloService.class});
		service.sayHello("user");
	}
}
複製代碼

class文件分析

  • Java編譯器編譯好Java文件後,產生 .class文件在磁盤中:
    • class文件是二進制文件,內容是隻有JVM虛擬機可以識別的機器碼
    • JVM虛擬機讀取字節碼文件,取出二進制數據
    • 加載到內存中,解析 .class文件內的信息,生成對應的Class對象

在這裏插入圖片描述

  • 加載class文件字節碼到系統內,轉換成class對象,而後再實例化:
    • 定義一個類
    • 自定義一個類加載器,用於將字節碼轉換成class對象
    • 編譯 .class文件,在程序中讀取字節碼,而後轉換成相應的class對象,再實例化

在運行期生成二進制字節碼

  • 在代碼中,動態建立一個類:
    • 因爲JVM經過字節碼的二進制信息加載類,所以在運行期的系統中,遵循Java編譯系統組織 .class文件的格式和結構,生成相應的二進制數據,而後再把這個二進制數據加載轉換成對應的類

在這裏插入圖片描述

  • 可使用開源框架在運行時期按照Java虛擬機規範對class文件的組織規則生成對應的二進制字節碼. 好比ASM,Javassist

ASM

  • ASM是一個Java字節碼操控框架:
    • 可以以二進制形式修改已有類或者動態生成類
    • ASM在建立class字節碼的過程當中,操縱的級別是底層JVM彙編指令級別
    • ASM能夠直接產生二進制class文件,也能夠在類被加載入Java虛擬機以前動態改變類行爲
    • ASM從類文件中讀入信息後,可以改變類行爲,分析類信息,甚至可以根據用戶的要求生成新類
  • 經過ASM生成類的字節碼:
    • 使用ASM框架提供的ClassWriter接口,經過訪問者模式進行動態建立class字節碼
    • 而後使用Java反編譯工具 (JD_GUI) 打開硬盤上生成的類.class文件查看類信息
    • 再使用定義的類加載器將class文件加載到內存中,而後建立class對象,而且實例化一個對象,調用code方法,查看code方法中的結果
  • 至此代表: 在代碼裏生成字節碼,並動態地加載成class對象,建立實例是徹底能夠實現的

Javassist

  • Javassist是一個開源的分析,編輯和建立Java字節碼的類庫,已經加入JBoss應用服務器項目,經過使用Javassist對字節碼操做爲JBoss實現動態AOP框架:
    • Javassist是JBoss一個子項目,主要優勢在於簡單快速
    • 直接使用Java編碼的形式,不須要虛擬機指令,就能改變類的結構或者動態生成類

源碼分析

Proxy類
// 映射表: 用於維護類裝載器對象到其對應的代理類緩存
private static Map loaderToCache = new WeakHashMap();

// 標記: 用於標記一個動態代理類正在被建立中
private static Object pendingGenerationMarker = new Object();

// 同步表: 記錄已經被建立的動態代理類類型,主要經過方法isProxyClass進行相關判斷
private static Map proxyClasses = Collections.synchronizedMap(new WeakHashMap());

// 關聯的調用處理器引用
protected InvocationHandler h;
複製代碼
newProxyInstance
  • Proxy靜態方法newProxyInstance:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException {
	/* * 檢查關聯調用處理器是否爲空,若是是空則拋出異常 */
	 if (h == null) {
	 	throw new NullPointerException();
	 }
	 /* * 得到與指定類型裝載器和一組接口相關的代理類類型對象 */
	  Class<?> cl = getProxyClass0(loader, interfaces);
	  /* * 經過反射獲取構造函數對象並生成代理類實例 */
	   try {
	       final Constructor<?> cons = cl.getConstructor(constructorParams);
	   	   final  InvocationHandler ih = h;
	   	   SecurityManager sm = System.getSecurityManager();
	   	   if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {
	   	       /* * 使用doPrivilege建立動態代理類實例 * 由於代理類實現可能須要特殊權限的非公共接口 */
	   	        return AccessController.doPrivileged(new PrivilegedAction<Object>() {
	   	        	public Object run() {
	   	        		return newInstance(cons, ih);
	   	        	}	
	   	        });
	   	   } else {
	   	   		return newInstance(cons, ih);
	   	   }
	   } catch (NoSuchMethodException e) {
	   		throw new InternalError(e.toString());
	   }
}

private static Object newInstance(Constructor<?> cons, InvocationHandler h) {
	try {
		return cons.newInstance(new Object[] {h});
	} catch (IllegalAccessException e) {
		throw new InternalError(e.toString());
	} catch (InstantationException e) {
		throw new InternalException(e.toString());
	} catch (InvocationTargetException e) {
		Throwable t = e.getCause();
		if (t instanceof RuntimeException) {
			throw (RuntimeException) t;
		} else {
			throw new InternalException(e.toString());
		}
	}
}
複製代碼
  • 動態代理的真正的關鍵是在getProxyClass0() 方法

getProxyClass0方法分析

  • 經過getProxyClass0方法中生成具體的class文件的過程:
    • 定義path
    • class文件寫到指定的硬盤中
    • 反編譯生成的class文件

getProxyClass0() 方法分爲四個步驟:緩存

  1. 對這組接口進行必定程度的安全檢查:

1.1 接口類對象是否對類裝載器可見 1.2 接口類對象與類裝載器所識別的接口類對象徹底相同 1.3 確保是interface類型而不是class類型.安全

for (int i = 0; i < interfaces.length; i++ ) {
	/* * 驗證類加載器是否將此接口的名稱解析爲相同的類對象 */
	 String interfaceName = interface[i].getName();
	 Class interfaceClass = null;
	 try {
	 	/* * forName(String name, boolean initialize, ClassLoader loader) * Returns the Class object associated with the class or interface with the given string name, * using the given class loader */ 
	 	interfaceClass = Class.forName(interfaceName, false, loader);
	 } catch (ClassNotFoundException e) {
	 }
	 if (interfaceClass != interface[i]) {
	 	throw new IllegalArgumentException(interface[i] + "is not visible from class loader.");
	 }

	/* * 驗證類加載器獲得的類對象是不是interface類型 */
	 if (! interfaceClass.isInterface()) {
	 	throw new IllegalArgumentException(interfaceClass.getName() + "is not an interface.");
	 }

	/* * 驗證類加載器獲得的類對象接口不是一個重複的接口 */
	 if (interfaceSet.contains(interfaceClass)) {
	 	throw new IllegalArgumentException("repeated interface:" + interface.getName());
	 }
	 interfaceSet.add(interfaceClass);
	 interfaceName[i] = interfaceName;
}
複製代碼
  1. loaderToCache映射表中獲取以類裝載器對象爲關鍵字所對應的緩存表,若是不存在,就會建立一個新的緩存表並更新到loaderToCahe中:

2.1 loaderToCache存放鍵值對 : 接口名字列表:動態生成的代理類的類對象的引用 2.2 當代理類正在被建立時,會臨時進行保存 : 接口名字列表:pendingGenerationMarker 2.3 標記pendingGenerationMarker的做用是通知後續的同類請求(接口數組相同且組內接口排列順序也相同)代理類正在被建立,請保持等待直至建立完成服務器

/* * 尋找類加載器的緩存表,若是沒有就爲類加載器建立代理類緩存 */
Map cache;
synchronized (loaderToCache) {
	cache = (Map) loaderToCache.get(loader);
 	if (cache == null) {
 		cache = new HashMap();
 		loaderToCache = put(loader, cache);
 	}
}
do {
	/* * 以接口名字做爲關鍵字得到對應的cache值 */
	 Object value = cache.get(key);
	 if (value instanceof Reference) {
	 	proxyClass = (Class)((Reference)value).get();
	 }
	 if (proxyClass != null) {
	 	// 若是已經建立,直接返回
	 	return proxyClass;
	 } else if (value == pendingGenerationMarker) {
	 	// 代理類正在建立,保持等待
	 	try {
	 		cache.wait()
	 	} catch (InterruptException e) {
	 	}
	 	// 等待被喚醒,繼續循環並經過二次檢查以確保建立完成,不然從新等待
	 	continue;
	 } else {
	 	// 標記代理類正在被建立
	 	cache.put(key, pendingGenerationMarker);
	 	// 跳出循環已進入建立過程
	 	break;
	 }
} while(true)
複製代碼
  1. 動態建立代理類的class對象
/* * 選擇一個名字代理類來生成 */
long num;
synchronized (nextUniqueNumberLock) {
	num = nextUniqueNumber ++;
}
String proxyName = proxyPkg + proxyClassNamePrefix + num;
/* * 驗證類加載器中沒有使用這個名字定義的類 */
 ...
 
// 動態生成代理類的字節碼數組
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces);
try {
	// 動態地定義新生成的代理類
	proxyClass = defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
	/* * 這裏的類格式錯誤指的是生代理類代碼中的錯誤 * 還有一些應用到代理類生成的參數的錯誤,好比一些虛擬機限制的超量 */
	 throw new IllegalArgumentException(e.toString());
}
// 將生成的代理類對象記錄到proxyClasses中
proxyClasses.put(proxyClass, null);
複製代碼

首先根據接口public與否的規則生成代理類的名稱 - $ProxyN格式,而後動態生成代理類. 全部的代碼生成工做由ProxyGenerator完成,該類在rt.jar中,須要進行反編譯markdown

public static byte[] generateProxyClass(final String name, Class[] interfaces) {
	ProxyGenerator gen = new ProxyGenerator(name, interfaces);
	// 動態生成代理類的字節碼
	final byte[] classFile = gen.generateClassFile();
	// 若是saveGeneratedFiles的值爲true,則會把所生成的代理類的字節碼保存到硬盤上
	if (saveGeneratedFiles) {
		java.security.AccessController.doPrivileged(
			new java.security.PrivilegedAction<Void>() {
				public Void run() {
					try{
						FileOutputStream file = new FileOutputStream(doToSlash(name) + ".class");
						file.write(classFile);
						file.close();
						return null;
					} catch (IOException e) {
						throw new InternalError("I/O exception saving generated file :" + e);
					}
				}
			}
		);
	} 
	// 返回代理類的字節碼
	return classFile;
}
複製代碼
  1. 代碼生成過程進入結尾部分,根據結果更新緩存表. 若是代理類成功生成則將代理類的類對象引用更新進緩存表,不然清除緩存表中對應的關鍵值,最後喚醒全部可能的正在等待的線程
finally {
	synchronized (cache) {
		if (proxyClass != null) {
			cache.put(key, new WeakReference(proxyClass));
		} else {
			cache.remove(key);
		}
		cache.notifyAll();
	}
}
return proxyClass;
複製代碼

InvocationHandler解析

  • Proxy角色在執行代理業務的時候,就是在調用真正業務以前或者以後完成一些額外的功能

在這裏插入圖片描述

  • 代理類就是在調用真實角色的方法以前或者以後作一些額外的業務
  • 爲了構造具備通用性和簡單性的代理類,能夠將全部的觸發真實角色動做交給一個觸發管理器,讓這個管理器統一管理觸發,這個觸發管理器就是InvocationHandler
  • 動態代理工做的基本工做模式:
    • 將方法功能的實現交給InvocationHandler角色
    • 外接對Proxy角色中每個的調用 ,Proxy角色都會交給InvocationHandler來處理
    • InvocationHandler則調用具體對象角色的方法

在這裏插入圖片描述

  • 在這種模式中,代理ProxyRealSubject應該實現相同的類的public方法,有兩種方式:
    • 一個比較直觀的方式: 就是定義一個功能接口,而後讓ProxyRealSubject來實現這個接口 (JDK中的動態代理機制 - Java動態代理機制)
    • 比較隱晦的方式: 經過繼承實現Proxy繼承RealSubject. 由於Proxy繼承自RealSubject, 這樣Proxy則擁有了RealSubject的功能 ,Proxy還能夠經過重寫RealSubject中的方法來實現多態(cglib)

JDK動態代理機制

  • JDK動態代理機制經過接口爲RealSubject建立一個動態代理對象:
    • 獲取RealSubject上的全部接口列表
    • 肯定要生成的代理類類名
    • 根據須要實現的接口信息,在代碼中動態建立該Proxy類的字節碼
    • 將對應的字節碼轉換成對應的class對象
    • 建立InvocationHandler, 用來處理Proxy全部方法調用
    • Proxyclass對象,以建立的handler爲參數,實例化一個proxy
  • JDK動態代理實例:
    • 定義兩個接口Vehicle和Rechargeable
    • Vehicle接口表示交通工具類,有drive()方法
    • Rechargeable接口表示可充電,有recharge()方法
    • 定義一個實現兩個接口的類ElectricCar,類圖以下:

在這裏插入圖片描述

  • 建立ElectricCar的動態代理類:
/** * 交通工具接口 */
 public interface Vehicle {
 	public void drive();
 }
複製代碼
/** * 充電接口 */
 public interface Rechargable {
 	public void recharge();
 }
複製代碼
/** * 電動車類 * 實現Rechargable, Vehicle接口 */
 public class ElectricCar implements Rechargable, Vehicle {
 	@Override
 	public void drive() {
 		System.out.println("ElectricCar can drive.");
 	}
 
 	@Override
 	public void recharge() {
 		System.out.println("ElectricCar can recharge.");
 	}
 }
複製代碼
/** * 動態代理類的觸發器 */
 public class InvocationHandlerImpl implements InvocationHandler {
 	private ElectricCar car;
 	
 	public InvocationHandlerImpl(Electric car) {
 		this.car = car;
 	} 
 
 	@Override
 	public Object invoke(Object paramObject, Method paramMethod, Object[] paramArrayOfObject) throws Throwable {
 		System.out.println("正在調用方法:" + paramMethod.getName() + "...");
 		paramMethod.invoke(car, null);
 		System.out.println("方法" + paramMethod.getName() + "調用結束.");
 		return null;
 	}
 }
複製代碼
public class ProxyTest {
	public static void main(String[] args) {
		ElectricCar car = new ElectricCar();
		// 獲取對應的ClassLoader
		ClassLoader classLoader = car.getClass().getClassLoader();
		// 獲取ElectricCar所實現的全部接口
		Class[] interfaces = car.getClass().getInterfaces();
		// 設置一個來自代理傳過來的方法調用請求處理器,處理全部的代理對象上的方法調用
		InvocationHandler handler = new InvocationHandlerImpl(car);
		/* * 建立代理對象在這個過程當中: * a. JDK根據傳入的參數信息動態地在內存中建立和 .class 等同的字節碼 * b. 根據相應的字節碼轉換成對應的class * c. 而後調用newInstance()建立實例 */
 		Object o = Proxy.newProxyInstance(classLoader, interfaces, handler);
 		Vehicle vehicle = (Vehicle) o;
 		vehicle.drive();
 		Rechargable rechargable = (Rechargable) o;
 		rechargable.recharge();
	}
}
複製代碼
  • 生成動態代理類的字節碼而且保存到硬盤中:
  • JDK提供了sun.misc.ProxyGenerator.generateProxyClass(String proxyName, calss[] interfaces) 底層方法來產生動態代理類的字節碼
  • 定義一個工具類,用來將生成的動態代理類保存到硬盤中:
public class proxyUtils {
	/* * 根據類信息,動態生成二進制字節碼保存到硬盤中 * 默認是clazz目錄下 * * @params clazz 須要生成動態代理類的類 * @proxyName 動態生成代理類的名稱 */
     public static void generateClassFile(Class clazz, String proxyName) {
     	// 根據提供的代理類名稱和類信息,生成字節碼
     	byte[] classFile = ProxyGenerator.generateProxyClass(ProxyName, clazz.getInterfaces());
     	String paths = clazz.getResource(".").getPath();
     	System.out.println(paths);
     	FileOutputStream out = null;
     	try {
     		// 保留到硬盤中
     		out = new FileOutputStream(paths + proxyName + ".class");
     		out.write(classFile);
     		out.flush();
     	} catch (Exception e) {
     		e.printStackTrace();
     	} finally {
     		try {
     			out.close();
     		} catch (IOException e) {
     			e.printStackTrace();
     		}
     	}
     }
}
複製代碼
  • 修改代理類名稱爲 "ElectricCarProxy", 並保存到硬盤,使用如下語句:
ProxyUtils.generateClassFile(car.getClass(), "ElectricCarProxy");
複製代碼

這樣將在ElectricCar.class同級目錄下產生ElectricCarProxy.class文件框架

  • 使用反編譯工具jd-gui.exe打開,將會看到如下信息:
/** * 生成的動態代理類的組織模式是繼承Proxy類,而後實現須要實現代理的類上的全部接口 * 在實現過程當中,是經過將全部的方法都交給InvocationHandler來處理 */
 public final class ElectricCarProxy extends Proxy implements Rechargable,Vehicle {
 	private static Method m1;
 	private static Method m3;
 	private static Method m4;
 	private static Method m0;
 	private static Method m2;

	public ElectricCarProxy(InvocationHandler paramInvocationHandler) throws {
		super(paramInvocationHandler);
	}  
 
 	public final boolean equals(Object paramObject) throws {
 		try {
 			/* * 方法功能的實現交給InvocationHandler處理 */
             return ((Boolean) this.h.invoke(this, m1, new Object[] {paramObject})).booleanValue();
 		} catch (Error | RuntimeException localError) {
 			throw localError;
 		} catch (Throwable localThrowable) {
 			throw new Undeclared ThrowableException(localThrowable);
 		}
 	} 
 	
 	public final void recharge() throws {
 		try {
 			/* * 方法功能的實現交給InvocationHandler處理 */
             this.h.invoke(this, m3, null);
             return;           
 		} catch (Error | RuntimeException localError) {
 			throw localError;
 		} catch (Throwable localThrowable) {
 			throw new Undeclared ThrowableException(localThrowable);
 		}
 	}
  
  	public final drive() throws {
  		try {
  			/* * 方法實現交給InvocationHandler處理 */
   			this.h.invoke(this, m4, null);
   			return;
  		} catch (Error | RuntimeException localError) {
  			throw localError;
  		} catch (Throwable localThrowable) {
  			throw new Undeclared ThrowableException(localThrowable);
  		}
  	}
   
   	public final int hasCode() throws {
   		try {
   			/* * 方法功能交給InvocationHandler處理 */
   			return ((Integer) this.h.invoke(this, m0, null)).intValue();
   		} catch (Error | RuntimeException localError) {
   			throw localError;
   		} catch (Throwable localThrowable) {
   			throw new Undeclared ThrowableException(localThrowable);
   		}
   	} 
   
   	public final String toString() throws {
   		try {
   			/* * 方法功能實現交給InvocationHandler處理 */
   			return (String)this.h.invoke(this, m2, null);
   		} catch (Error | RuntimeException localError) {
   			throw localError;
   		} catch (Throwable localThrowable) {
   			throw new Undeclared ThrowableException(localThrowable);
   		}
   	}
   
    static {
   		try {
   			/* * 爲每個須要方法對象 * 當調用相應的方法時,分別將方法對象做爲參數傳遞給InvocationHandler處理 */
   			m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
   			m3 = Class.forName("com.oxford.proxy.Rechargable").getMethod("recharge", new Class[0]);
   			m4 = Class.forName("com.oxford.proxy.Vehicle").getMethod("drive", new Class[0]);
   			m0 = Class.forName("java.lang.Object").getMethod("hasCode", new Class[0]);
   			m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
   			return;
   			} catch (NoSuchMethodException localNoSuchMethodException) {
   				throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
   			} catch (ClassNotFoundException localClassNotFoundException) {
   				throw new NoClassDefFoundError(localClassNotFoundException.getMessge());
   			}
   	}
 }
複製代碼
  • 生成的動態代理類的特色:
    • 繼承自java.lang.reflect.Proxy, 實現了Rechargable,Vehicle這兩個ElectricCar接口
    • 類中的全部方法都是final
    • 全部的方法功能的實現都統一調用了InvocationHandlerinvoke() 方法
    在這裏插入圖片描述

CGLIB動態代理機制

  • CGLIB經過類繼承生成動態代理類
  • JDK動態代理類的特色:
    • 某個類必須有實現的接口,而生成的代理類只能代理某個類接口定以的方法. 這樣會致使子類實現繼承兩個接口的方法外,另外實現的方法,在產生的動態代理類中不會有這個方法
    • 若是某個類沒有實現接口,那麼這個類就不能使用JDK動態代理了
  • CGLIB: Code Generation Library, CGLIB是一個強大的,高性能,高質量的Code生成類庫,能夠在運行時期擴展Java類與實現Java接口
  • CGLIB建立類的動態代理類的模式:
    • 查找類中全部非finalpublic類型的方法定義
    • 將這些方法的定義轉換成字節碼
    • 將組成的字節碼轉換成相應的代理的class對象
    • 實現MethodInterceptor接口,用來處理對代理類上的全部方法的請求 (相似JDK動態代理中的InvocationHandler的做用)
  • 定義一個Programmer類,一個Hacker
/** * 開發人員類 */
 public class Programmer {
 	public void code() {
 		System.out.println("開發人員開發程序代碼.");
 	}
 }
複製代碼
/** * 黑客類 * 實現了方法攔截器接口 */
 public class Hacker implements MethodInterceptor {
 	@Override
 	public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
 		System.out.println("Hacker what to do.");
 		proxy.invokeSuper(obj, args);
 		System.out.println("Hacker done this.");
 		return null;
 	}
 }
複製代碼
  • 測試類:
public class Test {
		public static void main(String[] args) {
			Programmer programmer = new Programmer();
			Hacker hacker = new Hacker();
			// CGLIB中的增強器,用來建立動態代理
			Enhancer enhancer = new Enhancer();
			// 設置要建立動態代理的類
			enhancer.setSuperclass(programmer.getClass());
			/* * 設置回調 * 這裏至關於對於代理類上全部方法的調用,都會調用CallBack * 而CallBack則須要實行intercept()方法進行攔截 */
			enhancer.setCallBack(hacker);
			Programmer proxy = (Programmer) enhancer.create();
			proxy.code();
		}
}
複製代碼
  • 經過CGLIB生成的class文件的內容:
public class Programmer EnhancerByCGLIB fa7aa2cd extends Programmer implements Factory {
	/* * .... */
	 
	 // Enhancer傳入的methodInterceptor
	 private MethodInterceptor CGLIB$CALLBACK_0;
	 
	 /* * ... */

	  public final void code() {
	  	MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;
	  	if (tmp4_1 == null) {
	  		tmp4_1;
	  		// 若callback不爲空,則調用methodInterceptor的intercept()方法
	  		CGLIB$BIND_CALLBACKS(this);
	  	}
	  	if (this.CGLIB$CALLBACK_0 != null)
	  		return;
	  		// 若是沒有設置callback回調函數,則默認執行父類的方法
	  		super.code();
	  }
	  /* * ... */
}
複製代碼
相關文章
相關標籤/搜索