zoukankan      html  css  js  c++  java
  • 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中的每个元素。当用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:上下文加载器原理和案例

  • 相关阅读:
    Proj THUDBFuzz Paper Reading: PMFuzz: Test Case Generation for Persistent Memory Programs
    入围 WF 后训练记
    算法竞赛历程
    2021 多校 杭电 第十场
    2021 多校 杭电 第九场
    2021 多校 牛客 第十场
    2021 多校 牛客 第九场
    2021 多校 杭电 第八场
    2021 多校 杭电 第六场
    2021 多校 杭电 第七场
  • 原文地址:https://www.cnblogs.com/crazymakercircle/p/9824124.html
Copyright © 2011-2022 走看看