本文將介紹有哪些常見的字節碼增強技術、字節碼增強的實現方式、AOP實現的原理。
1. 字節碼增強技術的應用場景:
????寫日志、事務管理
????常見的字節碼增強技術:
????1. Java 動態代理
? ? ????Java Proxy API 通過invoke方法攔截出來相應的代碼邏輯。Proxy 是面向接口的,被代理的Class的所有方法調用都會通過反射調用invoke方法。
????????缺點:性能開銷大
????2.? Java 5 提供的Instrumentation API
? ? ? ? 適應場景:適用于監控
? ? ? ? 缺點:不適合處理靈活的代碼邏輯
? ? ? ? Instrumentation API 不僅可以做字節碼增強,還可以通過調用getObjectSize(Object o) 方法來計算一個對象的精確大小。
? ? 3. ASM?
? ? ? ? ASM 是一個提供字節碼解析和操作的框架。CGlib 框架是基于ASM 實現,而常用的框架Hibernate、Spring 是基于CGlib 實現 AOP的。
? ? ? ? ASM 對使用者屏蔽了整個類的字節碼的長度、偏移量,能夠靈活、方便地解析和操作字節碼。主要提供 Core API 和Tree API。
? ? ? ? Core API 主要的類(接口):
? ? ? ? ? ? ClassVisitor、ClassAdapter、ClassReader、ClassWriter
? ? ? ? Tree API 主要的類(接口):
? ? ? ? 工具類:
? ? ? ? ? ? TraceClassVisitor、CheckClassAdapter、ASMifier、Type
? ? ? ? ? ? TraceClassVisitor 能打印ClassWriter 提供的byte[] 字節數組。
? ? ? ? ? ? TraceClassVisitor 通過初始化一個ClassWriter 和一個Printer 對象來打印我們需要的字節流信息。方便比較類文件及分析字節碼文件的結構。
2. 兩種實現機制:
? ? (1) 通過創建原始類的一個子類(動態創建的類繼承原來的類)。子類名以原始類名為前綴,以避免重名。Spring AOP 使用的就是這種
? ? (2) 直接修改原始類的字節碼。類的跟蹤過程中使用
3. 實現字節碼增強要執行兩個步驟:
? ? (1) 在內存中獲取到原始的字節碼, 然后通過一些開源的API 來修改它的byte[] 數組,得到一個新的byte[] 數組。
? ? (2) 將新的byte[] 數組加載到PermGen 區(即加載新的byte[] 數組或替換原始類的字節碼)。
4. 使用較多的開源的字節碼增強API:
? ? ASM、javassist、BCEL、SERP、CGLib(基于ASM )
5. 加載字節數組的方式:
????1. 基于Java 的instrument API (接口ClassFileTransformer 的transform方法)
byte[] transform( ClassLoader loader, String className, Class?classBeingRedefined, ProtectionDomain ????protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException
????2. 通過指定的ClassLoader? 來完成
????FAQ: 這兩種加載字節數組的方式的區別?
附錄:
????ASM? 是一種修改字節碼本身的工具庫,它實現的抽象層次是很低的,幾乎接近于指令級別。例子中的操作都是基于指令和操作數的。
/**
* Visits a local variable instruction. A local variable instruction is an
* instruction that loads or stores the value of a local variable.
*
* @param opcode the opcode of the local variable instruction to be visited.
*? ? ? ? This opcode is either ILOAD, LLOAD, FLOAD, DLOAD, ALOAD, ISTORE,
*? ? ? ? LSTORE, FSTORE, DSTORE, ASTORE or RET.
* @param var the operand of the instruction to be visited. This operand is
*? ? ? ? the index of a local variable.
*/
void visitVarInsn(int opcode,int var);
LDC 指令將一個常量加載到操作數棧
/**
* Visits a LDC instruction.
*
* @param cst the constant to be loaded on the stack. This parameter must be
*? ? ? ? a non null {@link Integer}, a {@link Float}, a {@link Long}, a
*? ? ? ? {@link Double} a {@link String} (or a {@link Type} for
*? ? ? ? <tt>.class</tt>constants, for classes whose version is 49.0 or
*? ? ? ? more).
*/
void visitLdcInsn(Object cst);
/**
* Visits a field instruction. A field instruction is an instruction that
* loads or stores the value of a field of an object.
*
* @param opcode the opcode of the type instruction to be visited. This
*? ? ? ? opcode is either GETSTATIC, PUTSTATIC, GETFIELD or PUTFIELD.
* @param owner the internal name of the field's owner class (see {@link
*? ? ? ? Type#getInternalName() getInternalName}).
* @param name the field's name.
* @param desc the field's descriptor (see {@link Type Type}).
*/
void visitFieldInsn(int opcode, String owner, String name, String desc);
/**
* Visits a method instruction. A method instruction is an instruction that
* invokes a method.
*
* @param opcode the opcode of the type instruction to be visited. This
*? ? ? ? opcode is either INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC,
*? ? ? ? INVOKEINTERFACE or INVOKEDYNAMIC.
* @param owner the internal name of the method's owner class (see {@link
*? ? ? ? Type#getInternalName() getInternalName})
*? ? ? ? or {@link org.objectweb.asm.Opcodes#INVOKEDYNAMIC_OWNER}.
* @param name the method's name.
* @param desc the method's descriptor (see {@link Type Type}).
*/
void visitMethodInsn(int opcode, String owner, String name, String desc);