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

攻城獅Chova發表於2021-07-14

代理模式

  • Java動態代理運用了設計模式中常用的代理模式
  • 代理模式:
    • 目的就是為其他物件提供一個代理用來控制對某個真實物件的訪問
  • 代理類的作用:
    • 為委託類預處理訊息
    • 過濾訊息並轉發訊息
    • 進行訊息被委託類執行後的後續處理
      在這裡插入圖片描述
      通過代理層這一中間層,有效的控制對於真實委託類物件的直接訪問,同時又可以實現自定義的控制策略,比如Spring中的AOP機制,這樣使得在設計上獲得更大的靈活性
  • 代理的基本構成:
    在這裡插入圖片描述
  • 代理模式中有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中,需要進行反編譯

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();
	  }
	  /*
	   * ...
	   */
}

相關文章