Java类加载器( 死磕8)

文章很长,而且持续更新,建议收藏起来,慢慢读!疯狂创客圈总目录 博客园版 为您奉上珍贵的学习资源 :

免费赠送 :《尼恩Java面试宝典》 持续更新+ 史上最全 + 面试必备 2000页+ 面试必备 + 大厂必备 +涨薪必备
免费赠送 经典图书:《Java高并发核心编程(卷1)加强版》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
免费赠送 经典图书:《Java高并发核心编程(卷2)加强版》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
免费赠送 经典图书:《Java高并发核心编程(卷3)加强版》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
免费赠送 经典图书:尼恩Java面试宝典 最新版 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
免费赠送 资源宝库: Java 必备 百度网盘资源大合集 价值>10000元 加尼恩领取


【正文】Java类加载器( CLassLoader ) 死磕 8:

使用ASM,和类加载器实现AOP

本小节目录

8.1. ASM字节码操作框架简介
8.2. ASM和访问者模式
8.3. 用于增强字节码的事务类
8.4 通过ASM访问注解
8.5. 通过ASM注入AOP事务代码
8.6. 实现AOP的类加载器

说明:本文会以pdf格式持续更新,更多最新尼恩3高pdf笔记,请从下面的链接获取:语雀 或者 码云

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这几个类即可。

说明:本文会以pdf格式持续更新,更多最新尼恩3高pdf笔记,请从下面的链接获取:语雀 或者 码云

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集合中,等待后面的进一步处理。

说明:本文会以pdf格式持续更新,更多最新尼恩3高pdf笔记,请从下面的链接获取:语雀 或者 码云

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<span style="color: rgb(0, 0, 255);">&lt;?</span>&gt; target = null;
    if (classData == null)
    {
        throw new ClassNotFoundException();
    }

    Set&lt;String&gt; 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&lt;String&gt; getTransSet(InputStream inputStream) throws IOException{
    ClassReader classReader = new ClassReader(inputStream);
    FilterClassVisitor filterClassVisitor = new FilterClassVisitor();
    classReader.accept(filterClassVisitor, ClassReader.SKIP_DEBUG);
    Set&lt;String&gt; tranMehodSet = filterClassVisitor.getTranMehods();
    return tranMehodSet;
  }
}

源码:

代码工程: classLoaderDemo.zip

下载地址:下面链接获取

说明:本文会以pdf格式持续更新,更多最新尼恩3高pdf笔记,请从下面的链接获取:语雀 或者 码云

posted @ 2018-10-21 09:33  疯狂创客圈  阅读(940)  评论(0编辑  收藏  举报