1. Байт-код
1.1 Что такое байт-код
Причина, по которой Java может «компилироваться один раз и работать где угодно», заключается в том, что JVM настраивается для различных платформ и операционных систем, ограждая разработчиков от основных деталей. Во-вторых, независимо от платформы файл байт-кода фиксированного формата (.class) будет скомпилирован и сгенерирован для использования JVM, а виртуальные машины JVM на разных платформах могут загружать и выполнять один и тот же независимый от платформы байт-код. Это показывает важность байт-кода для экосистемы Java. Он называется байт-кодом, потому что файл байт-кода состоит из шестнадцатеричных значений, которые JVM считывает в байтах. В Java команда javac обычно используется для компиляции исходного кода в файл байт-кода Пример файла .java от компиляции до запуска показан на следующем рисунке.
1.2 структура кода слова
После компиляции файла .java с помощью javac будет получен файл .class, например, запись простого класса ByteCodeDemo, как показано в левой части следующего рисунка. После компиляции генерируется файл ByteCodeDemo.class, который после открытия представляет собой набор шестнадцатеричных чисел, разделенных на байты и отображаемых в правой части следующего рисунка.
Спецификация JVM требует, чтобы каждый файл байт-кода состоял из 10 частей в фиксированном порядке, и общий результат показан на следующем рисунке.
2. ASM
ASM — это среда обработки байт-кода Java. Его можно использовать для динамического создания классов или для улучшения функциональности существующих классов. ASM может напрямую генерировать бинарные файлы классов или изменять поведение классов до того, как классы будут загружены в виртуальную машину Java. Сценарии применения ASM включают AOP (cglib основан на ASM), горячее развертывание и модификацию классов в других пакетах jar. Процесс модификации байт-кода ASM показан на следующем рисунке.
2.1 Основной API ASM
2.1.1 ClassReader
Используется для чтения скомпилированных файлов .class;
2.1.2 ClassWriter
Используется для перестроения скомпилированного класса, например, для изменения имени класса, свойств и методов и т. д.;
2.1.3 ClassVistor
Внутреннее внедрение ASMшаблон посетителяВ соответствии с обработкой байт-кода сверху вниз для разных областей в файле байт-кода существуют разные посетители.
- При доступе к файлу класса будет вызван ClassVistor.visitметод;
- При доступе к аннотации класса будет вызван ClassVistor.visitAnnotationметод;
- При доступе к члену класса будет вызван ClassVistor.visitFieldметод;
- При доступе к методу класса будет вызван ClassVistor.visitMethodметод;
- .......
При доступе к различным областям будет вызван соответствующий метод, и метод вернет соответствующий объект операции байт-кода. Изменяя этот объект, можно изменить содержимое, соответствующее соответствующей структуре файла класса. Наконец, содержимое байт-кода этого объекта может быть перезаписано исходным файлом .class для реализации записи байт-кода.
2.2 ASM реализует пример АОП
Пример кода выглядит следующим образом: Исходный метод процесса Base выводит только одну строку «процесс».
Цель: Мы ожидаем вывести «начало» перед выполнением метода и «конец» после выполнения метода после улучшения байт-кода.
public class Base {
public void process() {
System.out.println("process");
}
}
Чтобы добиться вставки выходного кода до и после метода процесса, необходимо выполнить следующие шаги:
- (1) читатьBase.classфайл, доступ к которому можно получить черезClassReaderПрочитайте файл класса;
- (2) СтруктураSystem.out.println(String)байт-код, черезClassVisitorВставьте дополнительный байт-код, где это необходимо;
- (3) вClassVisitorПосле того, как обработка завершена,ClassWriterЗамените старый байт-код новым байт-кодом;
Базовая ссылка на код реализации выглядит следующим образом: Класс SimpleGenerator определяет объекты ClassReader и ClassWriter. ClassReader отвечает за чтение байт-кода, а затем передает его классу ClassVisitor для обработки.После завершения обработки ClassWriter завершает замену файла байт-кода.
public class SimpleGenerator {
public static void main(String[] args) throws Exception {
//读取类文件
ClassReader classReader = new ClassReader("com.example.aop.asm.Base");
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
//处理,通过classVisitor修改类
ClassVisitor classVisitor = new SimpleClassVisitor(classWriter);
classReader.accept(classVisitor, ClassReader.SKIP_DEBUG);
byte[] data = classWriter.toByteArray();
//保存新的字节码文件
File file = new File("target/classes/com/example/aop/asm/Base.class");
FileOutputStream outputStream = new FileOutputStream(file);
outputStream.write(data);
outputStream.close();
System.out.println("generator new class file success.");
}
}
Класс SimpleClassVisitor наследует класс ClassVisitor. Методы в ClassVisitor вызываются в следующем порядке, который также описан в java-документе ClassVisitor.
[
visitSource
] [visitOuterClass
] (visitAnnotation
|visitTypeAnnotation
|visitAttribute
)* (visitInnerClass
|visitField
|visitMethod
)*visitEnd
.
Которые Visitmethod вызывали при методах доступа, мы можем достичь усиления, расширяя метод метода логики методика. Аналогичным образом, методвизитор также должен вызывать его методы в порядке.
(
visitParameter
)* [visitAnnotationDefault
] (visitAnnotation
|visitAnnotableParameterCount
|visitParameterAnnotation
visitTypeAnnotation
|visitAttribute
)* [visitCode
(visitFrame
|visitXInsn
|visitLabel
|visitInsnAnnotation
|visitTryCatchBlock
|visitTryCatchAnnotation
|visitLocalVariable
|visitLocalVariableAnnotation
|visitLineNumber
)*visitMaxs
]visitEnd
.
public class SimpleClassVisitor extends ClassVisitor {
public SimpleClassVisitor(ClassVisitor classVisitor) {
super(Opcodes.ASM5, classVisitor);
}
// visitMethod在访问类方法时回调,可以获知当前访问到方法信息
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, desc, signature,
exceptions);
// 不需要增强Base类的构造方法
if (mv != null && !name.equals("<init>")) {
// 通过定制MethodVisitor,替换原来的MethodVisitor对象来实现方法改写
mv = new SimpleMethodVisitor(mv);
}
return mv;
}
/**
* 定制方法visitor
*/
class SimpleMethodVisitor extends MethodVisitor {
public SimpleMethodVisitor(MethodVisitor methodVisitor) {
super(Opcodes.ASM5, methodVisitor);
}
// visitCode方法在ASM开始访问方法的Code区时回调
@Override
public void visitCode() {
super.visitCode();
// System.out.println("start")对应的字节码,在visitCode访问方法之后添加
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("start");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
}
// 每当ASM访问到无参数指令时,都会调用visitInsn方法
@Override
public void visitInsn(int opcode) {
if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN)) {
// System.out.println("end")对应的字节码,在visitInsn方法返回前添加
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("end");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
}
super.visitInsn(opcode);
}
}
}
Запустите метод main класса SimpleGenerator, чтобы завершить расширение байт-кода класса Base. Результат декомпиляции расширенного файла класса показан на следующем рисунке. Вы можете видеть, что соответствующий код изменился, добавив вывод «start» и «end» до и после него.
в состоянии пройтиASM ByteCode OutlineПлагины легко реализуют сопоставление исходного кода с байт-кодом.
3. Ссылки
Изучение улучшений байт-кода Java
Добро пожаловать, чтобы обратить внимание на мой публичный номер ~