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羣


無編程不創客,無案例不學習。 一定記得去跑一跑案例哦


類加載器系列全目錄

1.導入

2. JAVA類加載器分類

3. 揭祕ClassLoader抽象基類

4. 神祕的雙親委託機制

5. 入門案例:自定義一個文件系統的自定義classLoader

6. 基礎案例:自定義一個網絡類加載器

7. 中級案例:設計一個加密的自定義網絡加載器

8. 高級案例1:使用ASM技術,結合類加載器,解密AOP原理

9. 高級案例2:上下文加載器原理和案例

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章