Javassist實現JDK動態代理

提到JDK動態代理,相信很多人并不陌生。然而,對于動態代理的實現原理,以及如何編碼實現動態代理功能,可能知道的人就比較少了。接下一來,我們就一起來看看JDK動態代理的基本原理,以及如何通過Javassist進行模擬實現。

JDK動態代理

示例

以下是一個基于JDK動態代理的hello world示例,在很多地方都可以看到類似的版本。

public class DynamicProxyTest {interface IHello {void sayHello();}static class Hello implements IHello {@Overridepublic void sayHello() {System.out.println("hello world");}}static class DynamicProxy implements InvocationHandler {Object originalObj;Object bind(Object originalObj) {this.originalObj = originalObj;return Proxy.newProxyInstance(originalObj.getClass().getClassLoader(), originalObj.getClass().getInterfaces(), this);}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("pre method");Object result = method.invoke(originalObj, args);System.out.println("post method");return result;}}public static void main(String[] args) {System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");IHello hello = (IHello) new DynamicProxy().bind(new Hello());hello.sayHello();}
}
復制代碼

生成代理類源碼

通過設置參數 sun.misc.ProxyGenerator.saveGeneratedFiles為true,在執行main函數之后,我們將得到一份$Proxy0.class文件,它就是Hello的代理類。

經過反編譯,得到$Proxy0的源碼(省略了無關內容)如下:

final class $Proxy0 extends Proxy implements DynamicProxyTest.IHello {private static Method m1;private static Method m3;private static Method m2;private static Method m0;public $Proxy0(InvocationHandler paramInvocationHandler) {super(paramInvocationHandler);}public final void sayHello() {try {this.h.invoke(this, m3, null);return;} catch (Error | RuntimeException localError) {throw localError;} catch (Throwable localThrowable) {throw new UndeclaredThrowableException(localThrowable);}}static {try {m1 = Class.forName("java.lang.Object").getMethod("equals",new Class[] { Class.forName("java.lang.Object") });m3 = Class.forName("DynamicProxyTest$IHello").getMethod("sayHello", new Class[0]);m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);} catch (NoSuchMethodException localNoSuchMethodException) {throw new NoSuchMethodError(localNoSuchMethodException.getMessage());} catch (ClassNotFoundException localClassNotFoundException) {throw new NoClassDefFoundError(localClassNotFoundException.getMessage());}}
}
復制代碼

主要實現原理

  • 動態生成一個代理類,實現IHello接口;
  • 代理類繼承Proxy類,提供一個實例變量InvocationHandler h用于保存代理邏輯(此外,Proxy還提供了相關代理類處理邏輯);
  • 代理類聲明一系列Method類變量,用于保存接口相關的反射方法;
  • 代理類實現相關接口方法,核心邏輯是調用InvocationHandler的invoke方法,并傳入3個參數:當前代理類對象、接口反射方法對象、實際方法參數。

Javassist實現JDK動態代理

前面簡單分析了JDK動態代理的基本原理,其中,最核心的邏輯在于如何生成動態代理類,也就是 java.lang.reflect.Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)方法的實現。

接下來我們將通過Javassist一步步實現newProxyInstance方法。

1. 定義接口

接口基本與Proxy.newProxyInstance相同。為簡單說明,我們這里只定義了一個接口類型參數Class<?>而不是數組。

public static Object newProxyInstance(ClassLoader loader, Class<?> interfaceClass, InvocationHandler h) {...
}
復制代碼

2. 創建動態代理類

Javassist可以通過簡單的Java API來操作源代碼,這樣就可以在不了解Java字節碼相關知識的情況下,動態生成類或修改類的行為。

創建名稱為NewProxyClass的代理類。

ClassPool pool = ClassPool.getDefault();
CtClass proxyCc = pool.makeClass("NewProxyClass");
復制代碼

3. 添加實例變量InvocationHandler

添加類型為InvocationHandler的實例變量h。

CtClass handlerCc = pool.get(InvocationHandler.class.getName());
/* 生成代碼:private InvocationHandler h; */
CtField handlerField = new CtField(handlerCc, "h", proxyCc);
handlerField.setModifiers(AccessFlag.PRIVATE);
proxyCc.addField(handlerField);
復制代碼

4. 添加構造函數

創建構造函數,參數類型為InvocationHandler。

// 生成構造函數:public NewProxyClass(InvocationHandler h) { this.h = h; }
CtConstructor ctConstructor = new CtConstructor(new CtClass[] { handlerCc }, proxyCc);
ctConstructor.setBody("$0.h = $1;");
proxyCc.addConstructor(ctConstructor);
復制代碼

其中,$0代表this, $1代表構造函數的第1個參數。

5. 實現IHello接口聲明

// 生成接口實現聲明:public class NewProxyClass implements IHello
CtClass interfaceCc = pool.get(interfaceClass.getName());
proxyCc.addInterface(interfaceCc);
復制代碼

6. 實現IHello相關接口方法

6.1 遍歷接口方法

CtMethod[] ctMethods = interfaceCc.getDeclaredMethods();
for (int i = 0; i < ctMethods.length; i++) {// 核心邏輯在下方
}
復制代碼

6.2 代理方法實現

由于代理類調用invoke方法需要傳入接口的反射方法對象(Method),因此,我們需要為每個方法添加一個可復用的Method類變量。

6.2.1 反射方法對象聲明及初始化

/* 構造方法參數,如:new Class[] { String.class, Boolean.TYPE, Object.class } */
String classParamsStr = "new Class[0]";
if (ctMethods[i].getParameterTypes().length > 0) {for (CtClass clazz : ctMethods[i].getParameterTypes()) {classParamsStr = ((classParamsStr == "new Class[0]") ? clazz.getName() : classParamsStr + "," + clazz.getName()) + ".class";}classParamsStr = "new Class[] {" + classParamsStr + "}";
}
// 字段生成模板
String methodFieldTpl = "private static java.lang.reflect.Method %s=Class.forName(\"%s\").getDeclaredMethod(\"%s\", %s);";
// 根據模板生成方法及參數構造方法字段生成語句
String methodFieldBody = String.format(methodFieldTpl, "m" + i, interfaceClass.getName(), ctMethods[i].getName(), classParamsStr);
// 為代理類添加反射方法字段
CtField methodField = CtField.make(methodFieldBody, proxyCc);
proxyCc.addField(methodField);
復制代碼

通過以上邏輯,將生成類似代碼如下:

private static Method m0 = Class.forName("chapter9.javassistproxy3.IHello").getDeclaredMethod("sayHello", new Class[0]);
private static Method m1 = Class.forName("chapter9.javassistproxy3.IHello").getDeclaredMethod("sayHello2", new Class[] { Integer.TYPE });
private static Method m2 = Class.forName("chapter9.javassistproxy3.IHello").getDeclaredMethod("sayHello3", new Class[] { String.class, Boolean.TYPE, Object.class });
復制代碼

6.2.2 接口方法體實現

// invoke調用邏輯. 其中$args是實際方法傳入的參數數組
String methodBody = "$0.h.invoke($0, " + methodFieldName + ", $args)";// 如果方法有返回類型,則需要轉換為相應類型后返回
if (CtPrimitiveType.voidType != ctMethods[i].getReturnType()) {// 對8個基本類型進行轉型// 例如:((Integer)this.h.invoke(this, this.m2, new Object[] { paramString, new Boolean(paramBoolean), paramObject })).intValue();if (ctMethods[i].getReturnType() instanceof CtPrimitiveType) {CtPrimitiveType ctPrimitiveType = (CtPrimitiveType) ctMethods[i].getReturnType();methodBody = "return ((" + ctPrimitiveType.getWrapperName() + ") " + methodBody + ")." + ctPrimitiveType.getGetMethodName() + "()";}// 對于非基本類型直接轉型即可else {methodBody = "return (" + ctMethods[i].getReturnType().getName() + ") " + methodBody;}
}
methodBody += ";";/* 為代理類添加方法 */
CtMethod newMethod = new CtMethod(ctMethods[i].getReturnType(), ctMethods[i].getName(),ctMethods[i].getParameterTypes(), proxyCc);
newMethod.setBody(methodBody);
proxyCc.addMethod(newMethod);
復制代碼

通過以上邏輯,將生成類似代碼如下:

public void sayHello() {this.h.invoke(this, m0, new Object[0]);
}public String sayHello2(int paramInt) {return (String)this.h.invoke(this, m1, new Object[] { new Integer(paramInt) });
}public int sayHello3(String paramString, boolean paramBoolean, Object paramObject) {return ((Integer)this.h.invoke(this, m2, new Object[] { paramString, new Boolean(paramBoolean), paramObject })).intValue();
}
復制代碼

7. 生成代理類字節碼

以下語句,將生成代理類字節碼:D:/tmp/NewProxyClass.class

proxyCc.writeFile("D:/tmp"); // 該步驟可選
復制代碼

8. 生成代理對象

最后,通過調用第3步創建的構造函數,傳入InvocationHandler對象,生成并返回代理類。

Object proxy = proxyCc.toClass().getConstructor(InvocationHandler.class).newInstance(h);
return proxy;
復制代碼

完整代碼

public class ProxyFactory {public static Object newProxyInstance(ClassLoader loader, Class<?> interfaceClass, InvocationHandler h) throws Throwable {ClassPool pool = ClassPool.getDefault();// 1.創建代理類:public class NewProxyClassCtClass proxyCc = pool.makeClass("NewProxyClass");/* 2.給代理類添加字段:private InvocationHandler h; */CtClass handlerCc = pool.get(InvocationHandler.class.getName());CtField handlerField = new CtField(handlerCc, "h", proxyCc); // CtField(CtClass fieldType, String fieldName, CtClass addToThisClass)handlerField.setModifiers(AccessFlag.PRIVATE);proxyCc.addField(handlerField);/* 3.添加構造函數:public NewProxyClass(InvocationHandler h) { this.h = h; } */CtConstructor ctConstructor = new CtConstructor(new CtClass[] { handlerCc }, proxyCc);ctConstructor.setBody("$0.h = $1;"); // $0代表this, $1代表構造函數的第1個參數proxyCc.addConstructor(ctConstructor);/* 4.為代理類添加相應接口方法及實現 */CtClass interfaceCc = pool.get(interfaceClass.getName());// 4.1 為代理類添加接口:public class NewProxyClass implements IHelloproxyCc.addInterface(interfaceCc);// 4.2 為代理類添加相應方法及實現CtMethod[] ctMethods = interfaceCc.getDeclaredMethods();for (int i = 0; i < ctMethods.length; i++) {String methodFieldName = "m" + i; // 新的方法名// 4.2.1 為代理類添加反射方法字段// 如:private static Method m1 = Class.forName("chapter9.javassistproxy3.IHello").getDeclaredMethod("sayHello2", new Class[] { Integer.TYPE });/* 構造反射字段聲明及賦值語句 */String classParamsStr = "new Class[0]"; // 方法的多個參數類型以英文逗號分隔if (ctMethods[i].getParameterTypes().length > 0) { // getParameterTypes獲取方法參數類型列表for (CtClass clazz : ctMethods[i].getParameterTypes()) {classParamsStr = (("new Class[0]".equals(classParamsStr)) ? clazz.getName() : classParamsStr + "," + clazz.getName()) + ".class";}classParamsStr = "new Class[] {" + classParamsStr + "}";}String methodFieldTpl = "private static java.lang.reflect.Method %s=Class.forName(\"%s\").getDeclaredMethod(\"%s\", %s);";String methodFieldBody = String.format(methodFieldTpl, "m" + i, interfaceClass.getName(), ctMethods[i].getName(), classParamsStr);// 為代理類添加反射方法字段. CtField.make(String sourceCodeText, CtClass addToThisClass)CtField methodField = CtField.make(methodFieldBody, proxyCc);proxyCc.addField(methodField);System.out.println("methodFieldBody: " + methodFieldBody);/* 4.2.2 為方法添加方法體 *//* 構造方法體. this.h.invoke(this, 反射字段名, 方法參數列表); */String methodBody = "$0.h.invoke($0, " + methodFieldName + ", $args)";// 如果方法有返回類型,則需要轉換為相應類型后返回,因為invoke方法的返回類型為Objectif (CtPrimitiveType.voidType != ctMethods[i].getReturnType()) {// 對8個基本類型進行轉型// 例如:((Integer)this.h.invoke(this, this.m2, new Object[] { paramString, new Boolean(paramBoolean), paramObject })).intValue();if (ctMethods[i].getReturnType() instanceof CtPrimitiveType) {CtPrimitiveType ctPrimitiveType = (CtPrimitiveType) ctMethods[i].getReturnType();methodBody = "return ((" + ctPrimitiveType.getWrapperName() + ") " + methodBody + ")." + ctPrimitiveType.getGetMethodName() + "()";} else { // 對于非基本類型直接轉型即可methodBody = "return (" + ctMethods[i].getReturnType().getName() + ") " + methodBody;}}methodBody += ";";/* 為代理類添加方法. CtMethod(CtClass returnType, String methodName, CtClass[] parameterTypes, CtClass addToThisClass) */CtMethod newMethod = new CtMethod(ctMethods[i].getReturnType(), ctMethods[i].getName(),ctMethods[i].getParameterTypes(), proxyCc);newMethod.setBody(methodBody);proxyCc.addMethod(newMethod);System.out.println("Invoke method: " + methodBody);}proxyCc.writeFile("D:/tmp");// 5.生成代理實例. 將入參InvocationHandler h設置到代理類的InvocationHandler h變量@SuppressWarnings("unchecked")Object proxy = proxyCc.toClass().getConstructor(InvocationHandler.class).newInstance(h);return proxy;}}public interface IHello {int sayHello3(String a, boolean b, Object c);
}public class Hello implements IHello {@Overridepublic int sayHello3(String a, boolean b, Object c) {String abc = a + b + c;System.out.println("a + b + c=" + abc);return abc.hashCode();}
}public class CustomHandler implements InvocationHandler {private Object obj;public CustomHandler(Object obj) {this.obj = obj;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("pre method");Object result = method.invoke(obj, args);System.out.println("post method");return result;}}public class ProxyTest {public static void main(String[] args) throws Throwable {IHello hello = new Hello();CustomHandler customHandler = new CustomHandler(hello);IHello helloProxy = (IHello) ProxyFactory.newProxyInstance(hello.getClass().getClassLoader(), IHello.class, customHandler);System.out.println();System.out.println("a+false+Object=" + helloProxy.sayHello3("a", false, new Object()));}}
復制代碼

執行結果

methodFieldBody: private static java.lang.reflect.Method m0=Class.forName("chapter9.javassistproxy3.IHello").getDeclaredMethod("sayHello3", new Class[] {java.lang.String.class,boolean.class,java.lang.Object.class});
Invoke method: return ((java.lang.Integer) $0.h.invoke(0, m0,args)).intValue();

pre method
a + b + c=afalsejava.lang.Object@504bae78
post method
a+false+Object=-903110407


參考

Javassist官網:www.javassist.org/

個人公眾號

更多文章,請關注公眾號:二進制之路

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/389452.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/389452.shtml
英文地址,請注明出處:http://en.pswp.cn/news/389452.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

數據圖表可視化_數據可視化如何選擇正確的圖表第1部分

數據圖表可視化According to the World Economic Forum, the world produces 2.5 quintillion bytes of data every day. With so much data, it’s become increasingly difficult to manage and make sense of it all. It would be impossible for any person to wade throug…

Keras框架:實例分割Mask R-CNN算法實現及實現

實例分割 實例分割&#xff08;instance segmentation&#xff09;的難點在于&#xff1a; 需要同時檢測出目標的位置并且對目標進行分割&#xff0c;所以這就需要融合目標檢測&#xff08;框出目標的位置&#xff09;以及語義分割&#xff08;對像素進行分類&#xff0c;分割…

機器學習 缺陷檢測_球檢測-體育中的機器學習。

機器學習 缺陷檢測&#x1f6a9; 目標 (&#x1f6a9;Objective) We want to evaluate the quickest way to detect the ball in a sport event in order to develop an Sports AI without spending a million dollars on tech or developers. Quickly we find out that detec…

莫煩Pytorch神經網絡第二章代碼修改

import torch import numpy as np""" Numpy Torch對比課程 """ # #tensor與numpy格式數據相互轉換 # np_data np.arange(6).reshape((2,3)) # print(np_data) # # torch_data torch.from_numpy(np_data) # print(\n,torch_data) # # tensor2ar…

自定義字符類

當 VC不使用MFC&#xff0c;無法使用屬于MFC的CString&#xff0c;為此自定義一個&#xff0c;先暫時使用&#xff0c;后續完善。 頭文件&#xff1a; #pragma once#define MAX_LOADSTRING 100 // 最大字符數class CString {public:char *c_str, cSAr[MAX_LOADSTRING];WCHAR *w…

使用python和javascript進行數據可視化

Any data science or data analytics project can be generally described with the following steps:通常可以通過以下步驟來描述任何數據科學或數據分析項目&#xff1a; Acquiring a business understanding & defining the goal of a project 獲得業務理解并定義項目目…

Android 事件處理

事件就是用戶對圖形的操作&#xff0c;在android手機和平板電腦上&#xff0c;主要包含物理按鍵事件和觸摸屏事件兩類。物理按鍵事件包含&#xff1a;按下、抬起、長按等&#xff1b;觸摸屏事件主要包含按下、抬起、滾動、雙擊等。 在View中提供了onTouchEvent()方法&#xff0…

莫煩Pytorch神經網絡第三章代碼修改

3.1Regression回歸 import torch import torch.nn.functional as F from torch.autograd import Variable import matplotlib.pyplot as plt""" 創建數據 """x torch.unsqueeze(torch.linspace(-1,1,100),dim1) y x.pow(2) 0.2*torch.rand(x…

為什么餅圖有問題

介紹 (Introduction) It seems as if people are split on pie charts: either you passionately hate them, or you are indifferent. In this article, I am going to explain why pie charts are problematic and, if you fall into the latter category, what you can do w…

New Distinct Substrings(后綴數組)

New Distinct Substrings&#xff08;后綴數組&#xff09; 給定一個字符串&#xff0c;求不相同的子串的個數。\(n<50005\)。 顯然&#xff0c;任何一個子串一定是后綴上的前綴。先&#xff08;按套路&#xff09;把后綴排好序&#xff0c;對于當前的后綴\(S_i\)&#xff0…

Android dependency 'com.android.support:support-v4' has different version for the compile (26.1.0...

在項目中加入react-native-camera的時候 出現的錯誤. 解決方案: 修改 implementation project(:react-native-camera)為 implementation (project(:react-native-camera)) {exclude group: "com.android.support"}查看原文 Could not find play-services-basement.aa…

先知模型 facebook_使用Facebook先知進行犯罪率預測

先知模型 facebookTime series prediction is one of the must-know techniques for any data scientist. Questions like predicting the weather, product sales, customer visit in the shopping center, or amount of inventory to maintain, etc - all about time series …

莫煩Pytorch神經網絡第四章代碼修改

4.1CNN卷積神經網絡 import torch import torch.nn as nn from torch.autograd import Variable import torch.utils.data as Data import torchvision import matplotlib.pyplot as pltEPOCH 1 BATCH_SIZE 50 LR 0.001 DOWNLOAD_MNIST False #如果數據集已經下載到…

github gists 101使代碼共享漂亮

If you’ve been going through Medium, looking at technical articles, you’ve undoubtedly seen little windows that look like the below:如果您一直在閱讀Medium&#xff0c;并查看技術文章&#xff0c;那么您無疑會看到類似于以下內容的小窗口&#xff1a; def hello_…

loj #6278. 數列分塊入門 2

題目 題解 區間修改&#xff0c;詢問區間小于c的個數。分塊排序&#xff0c;用vector。至于那個塊的大小&#xff0c;好像要用到均值不等式 我不太會。。。就開始一個個試&#xff0c;發現sizsqrt(n)/4時最快&#xff01;&#xff01;&#xff01;明天去學一下算分塊復雜度的方…

基于Netty的百萬級推送服務設計要點

1. 背景1.1. 話題來源最近很多從事移動互聯網和物聯網開發的同學給我發郵件或者微博私信我&#xff0c;咨詢推送服務相關的問題。問題五花八門&#xff0c;在幫助大家答疑解惑的過程中&#xff0c;我也對問題進行了總結&#xff0c;大概可以歸納為如下幾類&#xff1a;1&#x…

莫煩Pytorch神經網絡第五章代碼修改

5.1動態Dynamic import torch from torch import nn import numpy as np import matplotlib.pyplot as plt# torch.manual_seed(1) # reproducible# Hyper Parameters INPUT_SIZE 1 # rnn input size / image width LR 0.02 # learning rateclass…

鮮為人知的6個黑科技網站_6種鮮為人知的熊貓繪圖工具

鮮為人知的6個黑科技網站Pandas is the go-to Python library for data analysis and manipulation. It provides numerous functions and methods that expedice the data analysis process.Pandas是用于數據分析和處理的Python庫。 它提供了加速數據分析過程的眾多功能和方法…

VRRP網關冗余

實驗要求?1、R1創建環回口&#xff0c;模擬外網?2、R2&#xff0c;R3使用VRRP技術?3、路由器之間使用EIGRP路由協議? 實驗拓撲? 實驗配置??R1(config)#interface loopback 0R1(config-if)#ip address 1.1.1.1 255.255.255.0R1(config-if)#int e0/0R1(config-if)#ip addr…

網頁JS獲取當前地理位置(省市區)

網頁JS獲取當前地理位置&#xff08;省市區&#xff09; 一、總結 一句話總結&#xff1a;ip查詢接口 二、網頁JS獲取當前地理位置&#xff08;省市區&#xff09; 眼看2014又要過去了&#xff0c;翻翻今年的文章好像沒有寫幾篇&#xff0c;忙真的或許已經不能成為借口了&#…