Java類載入器( 深磕8)
【正文】Java類載入器( CLassLoader ) 深磕 8:
使用ASM,和類載入器實現AOP
本小節目錄
8.1. ASM位元組碼操作框架簡介
8.2. ASM和訪問者模式
8.3. 用於增強位元組碼的事務類
8.4 通過ASM訪問註解
8.5. 通過ASM注入AOP事務程式碼
8.6. 實現AOP的類載入器
1.1. 使用類載入器實現AOP
前面講到,程式設計過程中,出現了很多需要動態加強位元組碼的場景:為了效能、統計、安全等等可能的加強,根據實際情況動態建立加強程式碼並執行。
這次使用asm來動態實現事務AOP功能。
更詳細的說,適用ASM技術,對原始類動態生成子類,呼叫子類的方法覆蓋父類,來實現AOP的功能。著名的 Hibernate 和 Spring 框架,就是使用這種技術實現了 AOP 的“無損注入”的。
1.1.1. ASM位元組碼操作框架簡介
ASM是一個JAVA位元組碼分析、建立和修改的開源應用框架。在ASM中提供了諸多的API用於對類的內容進行位元組碼操作的方法。與傳統的BCEL和SERL不同,在ASM中提供了更為優雅和靈活的操作位元組碼的方式。目前ASM已被廣泛的開源應用架構所使用,例如:Spring、Hibernate等。
Asm是很好的ByteCode generator 和 ByteCode reader。Asm提供了ClassVisitor來訪問Class中的每個元素。當用Cla***eader來讀取Class的位元組碼時,每read一個元素,ASM會呼叫指定的ClassVisitor來訪問這個元素。這就是訪問者模式。利用這個特點,當ClassVisitor訪問Class的Annotation元素時,我們會把annotation的資訊記錄下來。這樣就可以在將來使用這個Annotation。
簡單的說,ASM能幹什麼呢?
分析一個類、從位元組碼角度建立一個類、修改一個已經被編譯過的類檔案
1.1.2. ASM和訪問者模式
關於訪問者模式,後面會詳細為大家介紹。
使用ASM框架,需要理解訪問者模式。大家可以自行百度,理解訪問者模式有助於我們理解ASM的CoreAPI;
如果僅僅簡單的使用ASM框架,只需要掌握框架中的基本的ClassVisitor、ClassAdapter、MethodVisitor、FieldVisitor、Cla***eader和ClassWriter這幾個類即可。
1.1.3. 用於增強位元組碼的事務類
本案例,模擬Spring的配置事務功能。如果在方法前面加上@Transaction註解,則使用ASM進行方法的程式碼增強。在方法的前面加上開始事務的位元組碼,在方法的後面加上結束事務的位元組碼。
作為示意,Transaction 的程式碼很簡單,具體如下:
public class Transaction { public static void beginTransaction() { Logger.info("開始事務:"); } public static void commit() { Logger.info("提交事務 "); } }
現在的場景是:
修改目標位元組碼,現在需要對加上@Transaction註解方法做AOP增強,在方法執行之前執行如下類的beginTransaction()方法,方法執行結束後,執行commit()方法。
1.1.4. 通過ASM訪問註解
第一步,需要通過ASM,提取位元組碼中帶有@Transaction註解的方法名稱。
簡單粗暴,直接上程式碼:
public class FilterClassVisitor extends ClassVisitor { private Set<String> tranMehodSet; public FilterClassVisitor() { super(Opcodes.ASM5); tranMehodSet=new HashSet<>(); } public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { MethodVisitor methodVisitor= super.visitMethod(access, name, desc, signature, exceptions); return new FilterMethodVisitor( name, methodVisitor,this); } public void addTranMehod(String methName) { tranMehodSet.add(methName); } public Set<String> getTranMehods() { return tranMehodSet; } }
案例路徑:com.crazymakercircle.classLoaderDemo.AOP.ClassVisitor
這個類,呼叫了 FilterMethodVisitor ,來逐個訪問類的方法。其程式碼如下:
public class FilterMethodVisitor extends MethodVisitor { private final FilterClassVisitor classVisitor; String methName; public FilterMethodVisitor(String name, MethodVisitor methodVisitor, FilterClassVisitor transactionClassVisitor) { super(Opcodes.ASM5, methodVisitor); this.methName = name; this.classVisitor = transactionClassVisitor; } @Override public AnnotationVisitor visitAnnotation(String s, boolean b) { if (s.contains("Tanscation")) { this.classVisitor.addTranMehod(this.methName); } return super.visitAnnotation(s, b); } }
案例路徑:com.crazymakercircle.classLoaderDemo.AOP.FilterMethodVisitor
在這個類的 visitAnnotation,對每個訪問到的方法註解,進行判斷。如果一個方法的某個註解的名稱包含Tanscation,說明這個方法需要進行AOP的事務增強,將這個方法的名稱,加到classVisitor的AOP 事務方法Set集合中,等待後面的進一步處理。
1.1.5. 通過ASM注入AOP事務程式碼
通過ASM,實現在進入方法和退出方法時注入程式碼實現aop程式碼增強。涉及到MethodVisitor的兩個方法:
(1)visitCode方法。將會在ASM開始訪問某一個方法時呼叫,因此這個方法一般可以用來在進入分析JVM位元組碼之前來新增一些位元組碼。
(2)visitInsn方法。它是ASM訪問到無引數指令時呼叫的,這裡我們判斷了當前指令是否為無引數的return,來在方法結束前新增一些指令。
簡單粗暴,上程式碼。
package com.crazymakercircle.classLoaderDemo.AOP; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; public class ModifyMethodVisitor extends MethodVisitor { public ModifyMethodVisitor(MethodVisitor methodVisitor) { super(Opcodes.ASM5, methodVisitor); } public void visitCode() { super.visitMethodInsn( Opcodes.INVOKESTATIC, "com/crazymakercircle/classLoaderDemo/AOP/Transaction", "beginTransaction", "()V", false); super.visitCode(); } public void visitInsn(int opcode) { /** * 方法return之前,植入程式碼 */ if(opcode == Opcodes.RETURN || opcode == Opcodes.ARETURN ) { super.visitMethodInsn( Opcodes.INVOKESTATIC, "com/crazymakercircle/classLoaderDemo/AOP/Transaction", "commit", "()V", false); } super.visitInsn(opcode); } }
這個是方法級別的Visitor,需要類級別的訪問者來呼叫。
類級別的ClassVisitor,程式碼如下:
public class ModifyClassVisitor extends ClassVisitor { private Set<String> tranMehodSet; public ModifyClassVisitor(ClassVisitor classVisitor,Set<String> tranMehodSet) { super(Opcodes.ASM5, classVisitor); this.tranMehodSet=tranMehodSet; } public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { if(tranMehodSet.contains(name)) { MethodVisitor methodVisitor= super.visitMethod(access, name, desc, signature, exceptions); return new ModifyMethodVisitor( methodVisitor); } return super.visitMethod(access, name, desc, signature, exceptions); } }
在上面的visitMethod方法中,判斷方法名稱,是否在需要進行事務增強的方法集合中。
如果是,則使用前面定義ModifyMethodVisitor,進行事務的增強。
1.1.6. 實現AOP的類載入器
簡單粗暴,上程式碼
public class TransactionClassLoader extends FileClassLoader { public TransactionClassLoader(String rootDir) { super(rootDir); } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { byte[] classData = super.getClassData(name); Class<?> target = null; if (classData == null) { throw new ClassNotFoundException(); } Set<String> tranMehodSet = null; InputStream inputStream = null; /** * 讀取之前的class 位元組碼 */ Cla***eader cla***eader = null; try { inputStream = new ByteArrayInputStream(classData); tranMehodSet = getTransSet(inputStream); } catch (IOException e) { e.printStackTrace(); } if (null == tranMehodSet) { target = super.defineClass(name, classData, 0, classData.length); return target; } /** * 進行位元組碼的解析 */ try { inputStream = new ByteArrayInputStream(classData); cla***eader = new Cla***eader(inputStream); } catch (IOException e) { e.printStackTrace(); } ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS); cla***eader.accept( new ModifyClassVisitor(classWriter, tranMehodSet), Cla***eader.SKIP_DEBUG); /** * 取得解析後的位元組碼 */ byte[] transactionClassBits = classWriter.toByteArray(); target = defineClass( name, transactionClassBits, 0, transactionClassBits.length); Logger.info("src class=" + target.getName()); return target; } private static Set<String> getTransSet(InputStream inputStream) throws IOException { Cla***eader cla***eader = new Cla***eader(inputStream); FilterClassVisitor filterClassVisitor = new FilterClassVisitor(); cla***eader.accept(filterClassVisitor, Cla***eader.SKIP_DEBUG); Set<String> tranMehodSet = filterClassVisitor.getTranMehods(); return tranMehodSet; } }
原始碼:
程式碼工程: classLoaderDemo.zip
下載地址:在瘋狂創客圈QQ群檔案共享。
瘋狂創客圈:如果說Java是一個武林,這裡的聚集一群武痴, 交流程式設計體驗心得
QQ群連結:瘋狂創客圈QQ群
無程式設計不創客,無案例不學習。 一定記得去跑一跑案例哦
類載入器系列全目錄
5. 入門案例:自定義一個檔案系統的自定義classLoader