1. 程式人生 > >Java類載入器( 深磕8)

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:上下文載入器原理和案例