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

  • 相关阅读:
    师生关系
    2019-2020 20191316《信息安全专业导论》第二周学习总结
    计算机科学概论问题2
    android studio device file explorer 无法向/storage/emulated/0/上传文件
    【转载】android打包APK
    android app no modules 错误,不能运行
    Error:Unable to locate adb within SDK in Android Studio
    Ubuntu set up 8
    Ubuntu 18.04.3 安装 CUDA 10.2
    Intel Realsense SDK for Android 编译
  • 原文地址:https://www.cnblogs.com/crazymakercircle/p/9824124.html
Copyright © 2011-2022 走看看