日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

Javassist实现JDK动态代理

發布時間:2023/11/29 java 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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(args)).intValue();

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


參考

Javassist官網:www.javassist.org/

個人公眾號

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

總結

以上是生活随笔為你收集整理的Javassist实现JDK动态代理的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。