【正文】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中的每个元素。当用ClassReader来读取Class的字节码时,每read一个元素,ASM会调用指定的ClassVisitor来访问这个元素。这就是访问者模式。利用这个特点,当ClassVisitor访问Class的Annotation元素时,我们会把annotation的信息记录下来。这样就可以在将来使用这个Annotation。

简单的说,ASM能干什么呢?

分析一个类、从字节码角度创建一个类、修改一个已经被编译过的类文件

1.1.2. ASM和访问者模式

关于访问者模式,后面会详细为大家介绍。

使用ASM框架,需要理解访问者模式。大家可以自行百度,理解访问者模式有助于我们理解ASM的CoreAPI;

如果仅仅简单的使用ASM框架,只需要掌握框架中的基本的ClassVisitor、ClassAdapter、MethodVisitor、FieldVisitor、ClassReader和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 字节码

         */

        ClassReader classReader = 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);

            classReader = new ClassReader(inputStream);

        } catch (IOException e)

        {

            e.printStackTrace();

        }

        ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);

        classReader.accept(

                new ModifyClassVisitor(classWriter, tranMehodSet),

                ClassReader.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

    {

        ClassReader classReader = new ClassReader(inputStream);

        FilterClassVisitor filterClassVisitor = new FilterClassVisitor();

        classReader.accept(filterClassVisitor, ClassReader.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:上下文加载器原理和案例

Java类加载器( 死磕8)的更多相关文章

  1. Java类加载器(死磕 1-2)

      Java类加载器(  CLassLoader ) 死磕 1.2:  导入 & 类加载器分类 本小节目录 1.导入 1.1. 从class文件的载入开始 1.2. 什么是类加载器 2. JA ...

  2. Java类加载器(死磕5)

    Java类加载器(  CLassLoader )  死磕5:  自定义一个文件系统classLoader 本小节目录 5.1. 自定义类加载器的基本流程 5.2. 入门案例:自定义文件系统类加载器 5 ...

  3. Java类加载器( 死磕9)

    [正文]Java类加载器(  CLassLoader ) 死磕9:  上下文加载器原理和案例 本小节目录 9.1. 父加载器不能访问子加载器的类 9.2. 一个宠物工厂接口 9.3. 一个宠物工厂管理 ...

  4. Java类加载器( 死磕7)

    [正文]Java类加载器(  CLassLoader )死磕7:  基于加密的自定义网络加载器 本小节目录 7.1. 加密传输Server端的源码 7.2. 加密传输Client端的源码 7.3. 使 ...

  5. Java类加载器( 死磕 6)

    [正文]Java类加载器(  CLassLoader )死磕 6:  自定义网络类加载器 本小节目录 6.1. 自定义网络类加载器的类设计 6.2. 文件传输Server端的源码 6.3. 文件传输C ...

  6. Java类加载器( 死磕 4)

    [正文]Java类加载器(  CLassLoader ) 死磕 之4:  神秘的双亲委托机制 本小节目录 4.1. 每个类加载器都有一个parent父加载器 4.2. 类加载器之间的层次关系 4.3. ...

  7. Java类加载器(死磕3)

    [正文]Java类加载器(  CLassLoader ) 死磕3:  揭秘 ClassLoader抽象基类 本小节目录 3.1. 类的加载分类:隐式加载和显示加载 3.2. 加载一个类的五步工作 3. ...

  8. java笔记--理解java类加载器以及ClassLoader类

    类加载器概述: java类的加载是由虚拟机来完成的,虚拟机把描述类的Class文件加载到内存,并对数据进行校验,解析和初始化,最终形成能被java虚拟机直接使用的java类型,这就是虚拟机的类加载机制 ...

  9. java类加载器深入研究

    看了下面几篇关于类的加载器的文章,豁然开朗.猛击下面的地址开始看吧. Java类加载原理解析      深入探讨 Java 类加载器 分析BootstrapClassLoader/ExtClassLo ...

随机推荐

  1. 下拉菜单的option的value属性值问题

    下拉菜单的所有option都必须有value值,如果没有value会将标签中间的值传上去: 比如: <span class="el_spans">试卷级别:</s ...

  2. 25深入理解C指针之---传递数组

    一.传递数组:将数组作为参数传入函数,或将数组作为数据当成是函数的返回值 1.定义:可以传入和传出数组 2.特征: 1).将数组作为参数传递给函数的本质是传递数组的地址,这种传递无需复制数组元素,所以 ...

  3. Perl语言入门--2--perl的运算符

    一.算数运算符 **:是幂 结果不能超过数的范围 当指数为小数时 底数不能为负数 %:取余数  两边的操作数为整数,如果不是则要截取,把所有的小数部分去掉 注意:当一个字符串参加运算,需要转化为整数时 ...

  4. [深入学习C#]C#实现多线程的方式:Task——任务

    简介 .NET 4包含新名称空间System.Threading.Tasks,它 包含的类抽象出了线程功能. 在后台使用ThreadPool. 任务表示应完成的某个单元的工作. 这个单元的工作可以在单 ...

  5. ListView 在设备切换横竖屏时保存状态

    比如listview在设备切换横竖屏时,仍然需要保证position, activity - > onSaveInstanceState  - > restoreInstanceState ...

  6. Javao中使用Jackson反序列时,将LinkedHashMap转成对象的方法(将任何Object类型转成实体)

    可能存在这样一种情况,Jackson已经满足了大部分的序列化和反序列化工作,但是对于复杂的泛型实体估计未必能如愿的正常反序列,而此时对于一些泛型里面的实体对象就会反序列化成LinkedHashMap类 ...

  7. 自签名证书安全性问题研究https(ssl)

    先看下https(ssl)的好处,以及为什么要用: http://imweb.io/topic/565c71673ad940357eb99879 https://zh.wikipedia.org/wi ...

  8. Interface Builder中的技巧

    在我工作中经常会遇到有人吐槽Xcode中的interface builder(以下简称IB)不好用的开发者.在我看来,IB是一个非常棒的可视化开发工具,可以非常快捷的设置UI控件的大部分常用属性.下面 ...

  9. Android设置TextView行间距(非行高)

    Android设置TextView行间距(非行高) Android系统中TextView默认显示中文时会比较紧凑,不是很美观. 为了让每行保持一定的行间距,可以设置属性android:lineSpac ...

  10. utuntu16.04安装caffe+Matlab2017a+opencv3.1+CUDA8.0+cudnn6.0

    上午把tensorflow安装好了,下午和晚上装caffe的确很费劲. 默认CUDA,cuDNN可以用了 caffe官方安装教程 有些安装顺序自己也不清楚,简直就是碰运气 1. 安装之前依赖项 Gen ...