기본 오픈 소스 프레임 워크의 ASM 깊이

ASM은 무엇인가?

ASM은 자바 바이트 코드 조작 프레임 워크입니다. 동적으로 클래스 또는 기존의 향상의 클래스를 생성하는 데 사용할 수 있습니다. ASM은 Java 가상 머신이 동적으로 클래스에서 클래스의 동작을 변경하기 전에 직접 바이너리 클래스 파일도로드 할 수 있습니다 할 수 있습니다. 클래스 이름, 메서드, 속성 및 자바 바이트 코드 (명령어) : 자바 클래스의 .class 파일에 정의 된 형식으로 엄격하게, 클래스 파일은 모든 요소 클래스를 해결하기에 충분 메타 데이터 저장소가 있습니다. ASM은 파일에서 클래스 정보를 읽은 후에는 클래스 정보의 행동 분석을 변경할 수 있으며, 새로운 클래스는 사용자의 요구에 따라도 생성 할 수 있습니다.

왜 동적으로 자바 클래스를 생성?

당신이 오픈 소스 프레임 워크의 다양한 같은 로그, 캐시, 거래 등등, 나는이 오픈 소스 프레임 워크는 당신이 그것을 사용하지 않을 생각 등의 기능을 구현하는 자바 클래스를 필요로 추가 할 경우, 상상 해보세요. 동적으로 생성 된 클래스는, 코드에 침입을 줄이고 사용자의 효율성을 향상시킬 수 있습니다.

왜 ASM?

가장 직접적인 방법은 직접 덮어 쓰기 클래스 파일보다 자바 클래스를 변환합니다. 자바 스펙은 클래스 파일의 형식, 직접 정말 자바 클래스의 동작을 변경할 수 있습니다 바이트 코드를 편집을 자세히 설명합니다. 오늘날에도, 같은 클래스 파일에 대한 UltraEdit는 편집기 수술로 가장 원시적 인 도구를 사용하는 Java 전문가가 있습니다. 예, 이것은 가장 직접적인 방법이지만 마음에 요리 자바 클래스 파일 형식의 사용자가 필요합니다 신중하게 당신이 변환하고자하는 상대 파일의 헤더 기능의 오프셋 (offset),하지만 다시 계산 체크섬 클래스 파일을 계산 Java 가상 머신의 보안 메커니즘을 통해.

클래스 파일을 조작 직접 확인할 수 있습니다 것은 우리가 같은 프레임을 사용하기로 선택한 바로 왜, 너무 많은 문제이며, 프레임은 기본의 복잡성을 보호한다. ASM은 무기의 작동 클래스입니다.

사용 ASM 프로그래밍

ASM은 두 개의 API를 제공한다 :

  1. CoreAPI (ClassVisitor, MethodVisitor 等)
  2. TreeAPI (ClassNode, MethodNode 等)

차이는 메모리에 전체 클래스를로드 할 필요가 없습니다 CoreAPI 이벤트 기반 모델, 방문자 클래스의 정의의 각 요소입니다. 전체 구조 클래스 트리 구조에 TreeAPI 메모리로 읽어 보시기 바랍니다. 관점에서 쉽게 사용할 수 TreeAPI.

다음 예 CoreAPI 방식을 사용한다.

메이븐 추가 :

<dependency>
    <groupId>org.ow2.asm</groupId>
    <artifactId>asm</artifactId>
    <version>5.0.4</version>
</dependency>
复制代码

사용은 더 사용 버전 5.0.4 상대적으로 안정적이다.

향상된 효과를 달성하기 위해 첫째,이 현재의 클래스를 직접 예를 들어, 클래스를 수정 수정하는 방법에는 여러 가지가 있습니다, 또는 하위 클래스의 클래스, 설명한다.

다음의 예는이 클래스의 이득을 향상시키는 효과를 달성하기 위해 클래스를 하위 클래스로 지정하는 비 침습적이며, 다형성의 효과를 얻을 수있다.

첫째, 우리는 클래스가 강화 될 정의 :

package com.zjz;

import java.util.Random;

/**
 * @author zhaojz created at 2019-08-22 10:49
 */
public class Student {
    public String name;

    public void studying() throws InterruptedException {
        System.out.println(this.name+"正在学习...");
        Thread.sleep(new Random().nextInt(5000));
    }
}

复制代码

다음 첫 번째는 ClassReader을 정의합니다 :

ClassReader classReader = new ClassReader("com.zjz.Student");
复制代码

그리고 다음 ClassWriter을 정의합니다 :

 ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS);
复制代码

ClassWriter.COMPUTE_MAXS 자동 오퍼랜드 스택 및 로컬 변수 크기를 계산 나타낸다. 더는 다른 옵션을 참조 할 수 있습니다 : asm.ow2.io를

그런 다음 클래스에 대한 공식 방문을 시작했다 :

//通过ClassVisitor访问Class(匿名类的方式,可以自行定义为一个独立的类)
//ASM5为JVM字节码指令操作码
ClassVisitor classVisitor = new ClassVisitor(Opcodes.ASM5, classWriter) {
	//声明一个全局变量,表示增强后生成的子类的父类
   String enhancedSuperName;
   @Override
   public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
   //拼接需要生成的子类的类名:Student$EnhancedByASM
   String enhancedName = name+"$EnhancedByASM";
   //将Student设置为父类
   enhancedSuperName = name;
   super.visit(version, access, enhancedName, signature, enhancedSuperName, interfaces);
   }

    @Override
    public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
    //这里是演示字段访问
    System.out.println("Field:" + name);
    return super.visitField(access, name, desc, signature, value);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
    System.out.println("Method:" + name);
    MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
    MethodVisitor wrappedMv = mv;
    //判断当前读取的方法
    if (name.equals("studying")) {
    //如果是studying方法,则包装一个方法的Visitor
    wrappedMv = new StudentStudyingMethodVisitor(Opcodes.ASM5, mv);
    }else if(name.equals("<init>")){
    //如果是构造方法,处理子类中父类的构造函数调用
    wrappedMv = new StudentEnhancedConstructorMethodVisitor(Opcodes.ASM5, mv,enhancedSuperName);
    }
    return wrappedMv;
    }
};
复制代码

다음으로, 초점 MethodVisitor 보면 :

//Studying方法的Visitor
static class StudentStudyingMethodVisitor extends MethodVisitor{

    public StudentStudyingMethodVisitor(int i, MethodVisitor methodVisitor) {
    	super(i, methodVisitor);
    }

	//MethodVisitor 中定义了不同的visitXXX()方法,代表的不同的访问阶段。
	//visitCode表示刚刚进入方法。
    @Override
    public void visitCode() {
    	//添加一行System.currentTimeMillis()调用
        visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
        //并且将其存储在局部变量表内位置为1的地方
        visitVarInsn(Opcodes.LSTORE, 1);
        //上面两个的作用就是在Studying方法的第一行添加 long start = System.currentTimeMillis()
    }

	//visitInsn 表示访问进入了方法内部
    @Override
    public void visitInsn(int opcode) {
    	//通过opcode可以得知当前访问到了哪一步,如果是>=Opcodes.IRETURN && opcode <= Opcodes.RETURN 表明方法即将退出
        if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN)){
        	//加载局部变量表中位置为1的数据,也就是start的数据,并传入给下面的方法
            visitVarInsn(Opcodes.LLOAD, 1);
            //然后调用自定义的一个工具方法,用来输出耗时
            visitMethodInsn(Opcodes.INVOKESTATIC, "com/zjz/Before", "end", "(J)V", false);
        }
        super.visitInsn(opcode);
    }


}

static class StudentEnhancedConstructorMethodVisitor extends MethodVisitor{
	//定义一个全局变量记录父类名称
    private String superClassName;
    public StudentEnhancedConstructorMethodVisitor(int i, MethodVisitor methodVisitor,String superClassName) {
        super(i, methodVisitor);
        this.superClassName = superClassName;
    }

    @Override
    public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean b) {
    	//当开始初始化构造函数时,先访问父类构造函数,类似源码中的super()
        if (opcode==Opcodes.INVOKESPECIAL && name.equals("<init>")){
        	owner = superClassName;
        }
        super.visitMethodInsn(opcode, owner, name, desc, b);
    }
}
复制代码

이 경우, 입력 데이터는 필요하지있다 ClassVisitor 만 (Opcodes.ASM5, classWriter) 새로운 출력 데이터 ClassVisitor 정의 : 그래서

classReader.accept(classVisitor, ClassReader.SKIP_DEBUG);
复制代码

이 클래스를 방문 수정 프로세스, 출력 읽기를 완료합니다.

세심한 시청자들은 찾을 어디에 출력 할 것인가? 어떻게 새로운 생성 된 클래스가 할에 액세스 할 수? 그래서 우리는 우리 세대를로드하는 클래스의 클래스 로더를 정의해야합니다 :

 static class StudentClassLoader extends ClassLoader{
        public Class defineClassFromClassFile(String className,byte[] classFile) throws ClassFormatError{
            return defineClass(className, classFile, 0, classFile.length);
        }
    }
复制代码

이어서 JVM에 새롭게 생성 된 클래스 바이트 ClassWriter하여 배열 한로드가 취득 :

 byte[] data = classWriter.toByteArray();
 Class subStudent = classLoader.defineClassFromClassFile("com.zjz.Student$EnhancedByASM", data);
复制代码

이 클래스의 생성을 완료, 위의 코드 완성은 매우 간단한 일이다 : 기록 학습 시간.

요약하면 :

세 가지의 ASM CoreAPI 코어는 함께 연결됩니다 책임 패턴의 체인을 통해, ClassReader, 방문자, ClassWriter입니다.

방문자 액세스 메소드, 필드 등 방문자 패턴의 속성을 통해, 필요 메소드와 필드를 수정하고 방문자 랩 클릭에 원래해야하는 경우.

어떻게 코드 관련 JVM 바이트 코드 명령어를 이해하는 필요성과 관련된 연산 코드 ASM에 후크.

ASM 바이트 코드 개요 2017

그러나 너무 많은 명령, 동작 코드는 기호를 사는 방법을 기억 하는가? 이러한 위의 코드로 :

visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
visitVarInsn(Opcodes.LSTORE, 1);
复制代码

Opcodes.INVOKESTATIC, Opcodes.LSTORE, () J, 현기증보고되지 않는 이유는 무엇입니까? 사실, 연습뿐만 아니라 완벽하게, 당신은 또한 도구를 사용할 수 있습니다.

당신이 IDEA를 사용하는 경우, ASM 바이트 코드 개요 2017에 플러그인을 설치할 수 있습니다. 마우스 오른쪽 단추로 누른 다음 소스 파일을 표시 바이트 코드 개요를 선택, 다음 뷰를 볼 수 있습니다 :

의 image.png

ASMified보기 전환, 당신은 직접 사용을 통해 복사 할 수 있습니다, 우리가 위에서 작성한 코드와 같은 볼 수 있습니다.

완전한 소스 코드 예제를보기 : asm_demo를

참고 :

asm.ow2.io/

www.ibm.com/developerwo...

juejin.im/post/5b549b...

추천

출처juejin.im/post/5d6f423ae51d4561df7805f7