11 月 19 - 20 日 Apache Pulsar 社区年度盛会来啦,立即报名! 了解详情
写点什么

字节码操纵技术探秘

  • 2016-12-01
  • 本文字数:10691 字

    阅读完需:约 35 分钟

大家可能已经非常熟悉下面的处理流程:将一个“.java”文件输入到 Java 编译器中(可能会使用 javac,也可能像 ANT、Maven 或 Gradle 这样的构建工具),编译器对其进行分析,最终生成一个或多个“.class”文件。

图 1:什么是 Java 字节码?

如果从命令行中运行构建,并启用 verbose 的话,我们能够看到解析文件直到生成“.class”文件这一过程的输出。

复制代码
javac -verbose src/com/example/spring2gx/BankTransactions.java

所生成的“.class”文件包含了字节码,本质上来讲它就是 Java 虚拟机(Java virtual machine,JVM)所使用的指令,当程序运行时,它会由 Java 运行时类加载器进行加载。

在本文中,我们将会研究 Java 字节码以及如何对其进行操纵,并探讨人们为何想要这样做。

字节码操纵框架

最为流行的字节码操纵框架包括:

本文将会主要关注 Javassist 和 ASM。

我们为什么应该关注字节码操纵呢?

很多常用的 Java 库,如 Spring 和 Hibernate,以及大多数的 JVM 语言甚至我们的 IDE,都用到了字节码操纵框架。另外,它也确实非常有趣,所以这是一项很有价值的技术,掌握它之后,我们就能完成一些靠其他技术很难实现或无法完成的任务。一旦学会之后,我们的发挥空间将是无限的!

一个很重要的使用场景就是程序分析。例如,流行的 bug 定位工具 FindBugs 在底层就使用了 ASM 来分析字节码并定位 bug。有一些软件商店会有一定的代码复杂性规则,比如方法中 if/else 语句的最大数量以及方法的最大长度。静态分析工具会分析我们的字节码来确定代码的复杂性。

另外一个常见的使用场景就是类生成功能。例如,ORM 框架一般都会基于我们的类定义使用代理的机制。或者,在考虑实现应用的安全性时,可能会提供一种语法来添加授权的注解。在这样的场景下,都能很好地运用字节码操纵技术。

像 Scala、Groovy 和 Grails 这样的 JVM 语言都使用了字节码操纵框架。

考虑这样一种场景,我们需要转换库中的类,这些类我们并没有源码,这样的任务通常会由 Java profiler 来执行。例如,在 New Relic,采用了字节码 instrumentation 技术实现了对方法执行的计时。

借助字节码操纵,我们可以优化或混淆代码,甚至可以引入一些功能,比如为应用添加重要的日志。本文将会关注一个日志样例,这个样例提供使用这些字节码操纵框架的基本工具。

我们的样例

Sue 负责一家银行的 ATM 编程,她有了一项新的需求:针对一些指定的重要操作,在日志中添加关键的数据。

如下是一个简化的银行交易类,它允许用户通过用户名和密码进行登录、进行一些处理、提取一些钱,然后打印“交易完成”。这里的重要操作就是登录和提款。

复制代码
public void login(String password, String accountId, String userName) {
// 登录逻辑
}
public void withdraw(String accountId, Double moneyToRemove) {
// 交易逻辑
}

为了简化编码,Sue 会为这些方法调用创建一个 @ImportantLog 注解,这个注解所包含的输入参数代表了希望记录的方法参数索引。借助这一点,她就可以为 login 和 withdraw 方法添加注解了。

复制代码
/**
* 方法注解,用于识别
* 重要的方法,这些方法的调用需要进行日志记录。
*/
public @interface ImportantLog {
/**
* 需要进行日志记录的方法参数索引。
* 例如,如果有名为
* hello(int paramA, int paramB, int paramC) 的方法,我们
* 希望以日志的形式记录 paramA 和 paramC 的值,那么 fields
* 应该是 ["0","2"]。如果我们只想记录
* paramB 的值,那么 fields 将会是 ["1"]。
*/
String[] fields();
}
{1}

对于 login 方法,Sue 希望记录账户 Id 和用户名,那么她的 fields 应该设置为“1”和“2”(她不希望将密码展现出来!)。对于 withdraw 方法,她的 fields 应该设置为“0”和“1”,因为她希望输出前两个域:账户 ID 以及要提取的金额,其审计日志理想情况下应该包含如下的内容:

要实现该功能,Sue 将会使用 Java agent 技术。Java agent 是在 JDK 1.5 中引入的,它允许我们在处于运行状态的 JVM 中,修改组成类的字节,在这个过程中,并不需要这些类的源码。

在没有 agent 的时候,Sue 的程序的正常执行流程是这样的:

  1. 在某个主类上运行 Java,这个类会由一个类加载器进行加载;
  2. 调用该类的 main 方法,它会调用预先定义好的处理过程;
  3. 打印“交易完成”。

在引入 Java agent 之后,会发生几件额外的事情——但是,在此之前,我们先看一下创建 agent 都需要些什么。agent 必须要包含一个类,这个类要具有一个名为 premain 的方法。这个类必须要打包为 JAR 文件,这个包中还需要包含一个正确的 manifest 文件,在 manifest 文件中要有一个名为 Premain-Class 的条目。在启动的时候,必须要设置一个启动项,指向该 JAR 文件的路径,这样的话,JVM 才能知道这个 agent:

复制代码
java -javaagent:/to/agent.jar com/example/spring2gx/BankTransactions

在 premain 方法中,我们可以注册一个 Transformer,它会在每个类加载的时候,捕获它的字节,进行所需的修改,然后返回修改后的字节。在 Sue 的样例中,Transformer 会捕获 BankTransaction,在这里她会作出修改并返回修改后的字节,这也就是类加载器所加载的字节,main 方法将会执行原有的功能,除此之外还会增加 Sue 所需的日志增强。

当 agent 类加载后,它的 premain 方法会在应用程序的 main 方法之前被调用。

图 2:使用 Java agent 的过程。

我们最好来看一个样例。

Agent 类不需要实现任何接口,但是它必须要包含一个 premain 方法,如下所示:

Transformer 类包含了一个 transform 方法,它的签名会接受 ClassLoader、类名、要重定义的类所对应的 Class 对象、定义权限的 ProtectionDomain 以及这个类的原始字节。如果从 transform 方法中返回 null 的话,将会告诉运行时环境我们并没有对这个类进行变更。

如果要修改类的字节的话,我们需要在 transform 中提供字节码操纵的逻辑并返回修改后的字节。

Javassist

Javassist(“Java Programming Assistant”的缩写形式)是 JBoss 的子项目,包含了高层级的基于对象的 API,同时也包含了低层级更接近字节码的 API。基于对象的 API 社区更为活跃,这也是本文所关注的焦点。读者可以参考 Javassist 站点以获取完整的使用指南。

在 Javassist 中,进行类表述的基本单元是 CtClass(即“编译时的类”,compile time class)。组成程序的这些类会存储在一个 ClassPool 中,它本质上就是 CtClass 实例的一个容器。

ClassPool 的实现使用了一个 HashMap,其中 key 是类的名称,而 value 是对应的 CtClass 对象。

正常的 Java 类都会包含域、构造器以及方法。在 CtClass 中,分别与之对应的是 CtField、CtConstructor 和 CtMethod。要定位某个 CtClass,我们可以根据名称从 ClassPool 中获取,然后通过 CtClass 得到任意的方法,并做出我们的修改。

图 3

CtMethod 中包含了相关方法的代码行。我们可以借助 insertBefore 命令在方法开始的地方插入代码。Javassist 非常棒的一点在于我们所编写的是纯 Java,只不过需要提醒一下:Java 代码必须要以引用字符串的形式来实现。但是,大多数的人都会同意这种方式要比处理字节码好得多!(尽管如此,如果你碰巧喜欢直接处理字节码的话,那么可以关注本文 ASM 相关的内容。)JVM 包含了一个字节码验证器(verifier),以防止出现不合法的字节码。如果在你的 Javassist 代码中,所使用的 Java 是非法的,那么在运行时字节码验证器会拒绝它。

与 insertBefore 类似,还有一个名为 insertAfter 的方法,借助它我们可以在相关方法的结尾处插入代码。我们还可以使用 insertAt 方法,从而在相关方法的中间插入代码,或者使用 addCatch 添加 catch 语句。

现在,让我们打开 IDE,然后编码实现这个日志特性,首先从 Agent(包含 premain)和 ClassTransformer 开始。

复制代码
package com.example.spring2gx.agent;
public class Agent {
public static void premain(String args, Instrumentation inst) {
System.out.println("Starting the agent");
inst.addTransformer(new ImportantLogClassTransformer());
}
}
package com.example.spring2gx.agent;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
public class ImportantLogClassTransformer
implements ClassFileTransformer {
public byte[] transform(ClassLoader loader, String className,
Class classBeingRedefined, ProtectionDomain protectionDomain,
byte[] classfileBuffer) throws IllegalClassFormatException {
// 在这里操纵字节
return modified_bytes;
}

为了添加审计日志,首先要实现 transform,将类的字节转换为 CtClass 对象。然后,我们可以迭代它的方法,并捕获其中带有 @ImportantLogin 注解的方法,获取要进行日志记录的输入参数索引,并将相关的代码插入到方法开头的位置上。

复制代码
public byte[] transform(ClassLoader loader, String className,
Class classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] cfbuffer) throws IllegalClassFormatException {
// 将字节数组转换为 CtClass 对象
pool.insertClassPath(new ByteArrayClassPath(className,classfileBuffer));
// 将路径斜线转换为点号
CtClass cclass = pool.get(className.replaceAll("/", "."));
if (!cclass.isFrozen()) {
// 检查 CtClass 的每个方法是否有 @ImportantLog 注解
for (CtMethod currentMethod : cclass.getDeclaredMethods()) {
// 查找 @ImportantLog 注解
Annotation annotation = getAnnotation(currentMethod);
if (annotation != null) {
// 如果方法上有 @ImportantLog 注解的话,那么
// 获得重要方法的参数索引
List parameterIndexes = getParamIndexes(annotation);
// 在方法的开头位置添加日志语句
currentMethod.insertBefore(
createJavaString(currentMethod, className, parameterIndexes));
}
}
return cclass.toBytecode();
}
return null;
}

Javassist 注解可以声明为“不可见的(invisible)”和“可见的(visible)”。不可见的注解只会在类加载和编译期可见,它们在声明时需要将 RententionPolicy.CLASS 参数传递到注解中。可见注解(RententionPolicy.RUNTIME)在运行期会加载,并且是可见的。对于本例来说,我们只在编译期需要这些属性,因此将其设为不可见的。

getAnnotation 方法会扫描 @ImportantLog 注解,如果找不到注解的话,将会返回 null。

复制代码
private Annotation getAnnotation(CtMethod method) {
MethodInfo methodInfo = method.getMethodInfo();
AnnotationsAttribute attInfo = (AnnotationsAttribute) methodInfo
.getAttribute(AnnotationsAttribute.invisibleTag);
if (attInfo != null) {
return attInfo.getAnnotation("com.example.spring.mains.ImportantLog");
}
return null;
}

在得到注解之后,我们就可以检索参数索引了。通过使用 Javassist 的 ArrayMemberValue,会以字符串数组的形式返回成员 field 的值,然后我们就可以对其进行遍历,从而获取在注解中所嵌入的 field 索引。

复制代码
private List getParamIndexes(Annotation annotation) {
ArrayMemberValue fields =
(ArrayMemberValue) annotation.getMemberValue(“fields”);
if (fields != null) {
MemberValue[] values = (MemberValue[]) fields.getValue();
List parameterIndexes = new ArrayList();
for (MemberValue val : values) {
parameterIndexes.add(((StringMemberValue) val).getValue());
}
return parameterIndexes;
}
return Collections.emptyList();
}

最后,我们可以借助 createJavaString 在某个位置插入日志语句。

复制代码
1 private String createJavaString(CtMethod currentMethod,
2 String className, List indexParameters) {
3 StringBuilder sb = new StringBuilder();
4 sb.append("{StringBuilder sb = new StringBuilder");
5 sb.append("(\"A call was made to method '\");");
6 sb.append("sb.append(\"");
7 sb.append(currentMethod.getName());
8 sb.append("\");sb.append(\"' on class '\");");
9 sb.append("sb.append(\"");
10 sb.append(className);
11 sb.append("\");sb.append(\"'.\");");
12 sb.append("sb.append(\"\\n Important params:\");");
13
14 for (String index : indexParameters) {
15 try {
16 int localVar = Integer.parseInt(index) + 1;
17 sb.append("sb.append(\"\\n Index: \");");
18 sb.append("sb.append(\"");
19 sb.append(index);
20 sb.append("\");sb.append(\" value: \");");
21 sb.append("sb.append($" + localVar + ");");
22 } catch (NumberFormatException e) {
23 e.printStackTrace();
24 }
25 }
26 sb.append("System.out.println(sb.toString());}");
27 return sb.toString();
28 }
29 }

在我们的实现中,创建了一个 StringBuilder,它首先拼接了一些报头信息,紧接着是方法名和类名。需要注意的一件事情是,如果我们插入多行 Java 语句的话,需要将其用大括号括起来(参见第 4 行和第 26 行)。

(如果只有一条语句的话,那没有必要使用括号。)

前文基本上已经全部涵盖了使用 Javassist 添加审计日志的代码。回顾一下,它的优势在于:

  • 因为它使用我们所熟悉的 Java 语法,所以不需要学习字节码;
  • 没有太多的编码工作要做;
  • Javassist 已经有了很棒的文档。

它的不足之处在于:

  • 不使用字节码会限制它的功能;
  • Javassist 会比其他的字节码操纵框架更慢一些。

ASM

ASM 最初是一个博士研究项目,在 2002 年开源。它的更新非常活跃,从 5.x 版本开始支持 Java 8。ASM 包含了一个基于事件的库和一个基于对象的库,分别类似于 SAX 和 DOM XML 解析器。本文将会关注事件驱动的库,我们可以在这里找到完整的文档。

一个 Java 类是由很多组件组成的,包括超类、接口、属性、域和方法。在使用 ASM 时,我们可以将其均视为事件。我们会提供一个 ClassVisitor 实现,通过它来解析类,当解析器遇到每个组件时,ClassVisitor 上对应的“visitor”事件处理器方法会被调用(始终按照上述的顺序)。

复制代码
package com.sun.xml.internal.ws.org.objectweb.asm;
public interface ClassVisitor {
void visit(int version, int access, String name, String signature,
String superName, String[] interfaces);
void visitSource(String source, String debug);
void visitOuterClass(String owner, String name, String desc);
AnnotationVisitor visitAnnotation(String desc, boolean visible);
void visitAttribute(Attribute attr);
void visitInnerClass(String name, String outerName,
String innerName, int access);
FieldVisitor visitField(int access, String name, String desc,
String signature, Object value);
MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions);
void visitEnd();
}

接下来,我们将 Sue 的 BankTransaction(在本文的开头中进行了定义)传递到一个 ClassReader 中进行解析,以便对这种处理方式有个直观的感觉。

同样,我们需要从 Agent premain 开始:

复制代码
import java.lang.instrument.Instrumentation;
public class Agent {
public static void premain(String args, Instrumentation inst) {
System.out.println("Starting the agent");
inst.addTransformer(new ImportantLogClassTransformer());
}
}

然后,将输出的字节传递给不进行任何操作(no-op)的 ClassWriter,将解析得到的字节全部写回到字节数组中,得到一个重新生成的 BankTransaction,它的行为与原始类的行为是完全一致的。

图 4

复制代码
import jdk.internal.org.objectweb.asm.ClassReader;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.ClassVisitor;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
public class ImportantLogClassTransformer implements ClassFileTransformer {
public byte[] transform(ClassLoader loader, String className,
Class classBeingRedefined, ProtectionDomain protectionDomain,
byte[] classfileBuffer) throws IllegalClassFormatException {
ClassReader cr = new ClassReader(classfileBuffer);
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES);
cr.accept(cw, 0);
return cw.toByteArray();
}
}

现在,我们修改一下 ClassWriter,让它做一些更有用的事情,这需要添加一个 ClassVisitor(名为 LogMethodClassVisitor)来调用我们的事件处理方法,如 visitField 或 visitMethod,在解析时遇到相关组件时就会调用这些方法。

图 5

复制代码
public byte[] transform(ClassLoader loader, String className,
Class classBeingRedefined, ProtectionDomain protectionDomain,
byte[] classfileBuffer) throws IllegalClassFormatException {
ClassReader cr = new ClassReader(classfileBuffer);
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES);
ClassVisitor cv = new LogMethodClassVisitor(cw, className);
cr.accept(cv, 0);
return cw.toByteArray();
}

对于日志记录的需求,我们想要检查每个方法是否具有标示注解并添加特定的日志。我们只需要重写 ClassVisitor 的 visitMethod 方法,让它返回一个 MethodVisitor,从而提供我们自己的实现。就像类是由多个组件组成的一样,方法也是由多个组件组成的,对应着方法属性、注解以及编译后的代码。ASM 的 MethodVisitor 提供了一种钩子(hook)机制,以便访问方法中的每个操作码(opcode),这样我们就能以很细的粒度来进行修改。

复制代码
public class LogMethodClassVisitor extends ClassVisitor {
private String className;
public LogMethodIfAnnotationVisitor(ClassVisitor cv, String className) {
super(Opcodes.ASM5, cv);
this.className = className;
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
// 将我们的逻辑放在这里
}
}

同样的,事件处理器会始终按照预先定义的相同顺序来调用,所以在实际 _ 访问(visit)_ 代码的时候,我们就已经得知了方法的所有属性和注解。(顺便说一下,我们还可以将多个 MethodVisitor 链接在一起,就像我们可以链接多个 ClassVisitor 实例一样。)所以,在 visitMethod 方法中,我们将会通过 PrintMessageMethodVisitor 添加钩子,重载 visitAnnotation 方法来获取注解并插入任意所需的日志代码。

我们的 PrintMessageMethodVisitor 重载了两个方法。首先是 visitAnnotation,所以可以检查方法的 @ImportantLog 注解。如果存在这个注解的话,我们需要提取 field 属性上的索引。当 visitCode 执行的时候,是否存在注解已经确定了,所以我们就可以添加特定的日志了。visitAnnotation 的代码以钩子的形式添加到了 AnnotationVisitor 中,它能够暴露 @ImportantLog 注解上的 field 参数。

复制代码
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
if ("Lcom/example/spring2gx/mains/ImportantLog;".equals(desc)) {
isAnnotationPresent = true;
return new AnnotationVisitor(Opcodes.ASM5,
super.visitAnnotation(desc, visible)) {
public AnnotationVisitor visitArray(String name) {
if (“fields”.equals(name)){
return new AnnotationVisitor(Opcodes.ASM5,
super.visitArray(name)) {
public void visit(String name, Object value) {
parameterIndexes.add((String) value);
super.visit(name, value);
}
};
}else{
return super.visitArray(name);
}
}
};
}
return super.visitAnnotation(desc, visible);
}

现在,我们来看一下 visitCode 方法。首先,它必须要检查 AnnotationVisitor 是否有注解存在的标记。如果有的话,那么添加我们自己的字节码。

复制代码
public void visitCode() {
if (isAnnotationPresent) {
// 创建 string builder
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out",
"Ljava/io/PrintStream;");
mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder");
mv.visitInsn(Opcodes.DUP);
// 将所需的所有内容添加到 string builder 上
mv.visitLdcInsn("A call was made to method \"");
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder",
"", "(Ljava/lang/String;)V", false);
mv.visitLdcInsn(methodName);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder",
"append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
}

这是 ASM 非常恐怖的一点——我们必须要编写字节码,所以要学习新的东西。这是一门相当简单的语言,但是如果你只是想随便了解一下的话,那么可以非常容易地借助 javap 得到现有的字节码:

复制代码
javap -c com/example/spring2gx/mains/PrintMessage

我推荐你在一个 Java 测试类中编写你所需的代码、对其进行编译,然后通过 javap -c 来运行它,以便查看精确的字节码。在上面的样例代码中,以蓝字显示的所有内容都是字节码。其中的每一行都是一个单字节的操作码,后面跟着零个或更多的参数。我们需要为目标代码确定这些参数,它们通常可以通过对原始类执行 javap-c -v 进行抽取(这里 -v 代表的意思是 verbose,将会展示常量池)。

我鼓励读者查阅一下 JVM 规范,该规范定义了所有的字节码。有一些操作,比如 load 和 store(会将数据在操作栈和本地变量之间进行转移),针对每种参数形式都进行了重载。例如,ILOAD 会将一个整型值的本地变量推送到栈中,而 LLOAD 会对长整型执行相同的操作。

除此之外,还有像 invokeVirtual、invokeSpecial、invokeStatic 这样的操作,以及最近新添加的 invokeDynamic,它们分别用来调用标准的实例方法、构造器、静态方法以及动态类型的 JVM 语言中的动态方法。另外,还有创建新对象的 new 操作符,以及复制栈顶操作数的指令。

总结起来,ASM 的优势在于:

  • 它的内存占用很小;
  • 它的运行通常会非常快;
  • 在网上,它的文档很丰富;
  • 所有的操作码都是可用的,所以可以通过它做很多的事情;
  • 有很多的社区支持。

它只有一个不足之处,但这是很大的不足:我们编写的是字节码,所以需要理解在幕后是如何运行的,这样的话,开发人员所需要的时间就会增加。

我们学到了什么

  • 当我们处理字节码操纵时,很重要的一点就是步子不要太大。不要编写大量的字节码并希望它们能够立即通过验证并且可以执行。每次只编写一行,考虑一下在你的栈中会有什么,考虑一下局部变量,然后再写下一行。如果它不能通过验证器的校验,那么每次只对一个地方进行修改,否则的话,你永远无法让其正常运行起来。另外,还需要记住,除了 JVM 的验证器以外,ASM 还维护了一个单独的字节码验证器,所以我们最好运行这两个验证器,检查你的字节码是否能够通过它们两者的校验。
  • 在修改类的时候,很重要的一点就是要考虑类加载机制。当我们使用 Java agent 的时候,它的 transformer 会在类加载进 JVM 时接触到每个类,并不关心是哪个类加载器加载的它。所以,我们需要确保类加载器也能看到对应的对象,否则的话,就会遇到麻烦。
  • 如果你将 Javassist 与应用服务器组合使用时,应用服务器会有多个类加载器,注意类池(class pool)需要能够访问到你的类对象。我们可能需要为类池注册一个新的类路径,以确保它能访问类对象。你可以将类池链接起来,就像 Java 将类加载器链接起来一样,这样,如果在类池中无法找到 CTClass 对象的话,它能够访问它的父类池进行查找。
  • 最后,还有很重要的一点需要注意,那就是 JDK 本身也有转换类的功能,JDK 已经转换过的类会有一些特定的限制。我们可以修改方法的实现,但是与原始的转换不同,此时,重新转换就不允许改变类的结构了,例如添加新的域或方法,或者修改签名。

字节码操纵能够让我们的生活更加轻松。我们可以查找缺陷、添加日志(就像之前所讨论的)、混淆源码、执行像 Spring 或 Hibernate 这样的预处理,甚至编写自己的语言编译器。我们还可以限制 API 调用、通过分析代码来查看是否有多个线程访问同一个集合、从数据库中懒加载数据,并且还可以通过探测 JAR 包,寻找这些包的差异。

所以,我鼓励你选择某个字节码操纵框架为友,也许有一天,它就能拯救你的工作。

本文根据来自 New Relic 的 Ashley Puls 在 Spring One 上的演讲整理而来,完整的演讲可以在该地址观看和下载。

关于作者

Victor Grazi是 InfoQ 的 Java 板块主编。在 2012 年,他曾成为 Oracle Java Champion,Victor 就职于 Nomura Securities,从事核心平台工具相关的工作,同时还担任技术咨询和 Java 布道师。在技术会议上,他经常做一些演讲。Victor 还负责着名为“Java Concurrent Animated”和“Bytecode Explorer”开源项目。

查看英文原文: Living in the Matrix with Bytecode Manipulation

2016-12-01 16:0010963

评论

发布
暂无评论
  • 解析 JDK 7 的动态类型语言支持

    Java虚拟机的字节码指令集的数量自从Sun公司的第一款Java虚拟机问世至JDK 7来临之前的十余年时间里,一直没有发生任何变化 。随着JDK 7的发布,字节码指令集终于迎来了第一位新成员——invokedynamic指令。这条新增加的指令是JDK 7实现“动态类型语言(Dynamically Typed Language)”支持而进行的改进之一,也是为JDK 8可以顺利实现Lambda表达式做技术准备。在这篇文章中,我们将去了解JDK 7这项新特性的出现前因后果和它的意义。

  • Groovy 2.0 新特性

    新发布的Groovy2.0为这门语言带来了关键的静态特性:静态类型检查和静态编译;采用了JDK 7相关的改进:Project Coin语法增强和新支持的“invoke dynamic” JVM指令;同时,提高了模块化。我们将在这篇文章中了解这些新特性的详情。

  • Xtend 2.4 发布,新版添加了 Active 注解,对 Android 的支持等新特性

    Xtend是一门静态编译型的JVM编程语言,它可以通过类型推导来减少样板代码。新发布的Xtend2.4增加了active注解,通过active注解可方便地生成标准模式并减少代码错误。除此之外该版本还添加了对Android的支持以及集合字面量(collection literals)的功能。InfoQ采访了Sven Efftinge并对Xtend2.4发布的新特性进行更深入的了解。

  • 解读:Java 11 中的模块感知服务加载器

    Java模块是一个自包含、自描述组件,隐藏了内部细节,为客户端使用提供接口、类和服务。Java的ServiceLoader可以用来加载实现给定服务接口程序。Java的服务加载机制可以通过库进行扩展,以减少样板代码,并提供一些有用的特性。

  • 错误处理(下):如何设计错误包?

    在Go项目开发中,错误是我们必须要处理的一个事项。今天,我来带你设计一个优秀的错误包。

    2021-07-08

  • OpenJDK 更新了 InvokeDynamic

    就在圣诞节之前,OpenJDK构建开始包含更新版本的JSR 292 API了,虽然尚非最终版,但却是一个好的迹象,向我们展现了JSR的形成过程。

  • dojo1.7 功能介绍:面向方面编程(AOP)功能与原理

    日前发布的dojo 1.7版本对其源码进行了很大的变更。在迈向2.0版本之际,dojo提供了许多新的功能,也对许多已有的功能进行了修改,具体来说新版本更好地支持AMD规范、提供了新的事件处理系统(计划在2.0版本中替换dojo.connect API)和DOM查询模块、更新对象存储相关接口(ObjectStore)等。在本文中我们将会介绍在dojo 1.7版本中新增的面向方面编程(AOP)功能以及其实现原理。

  • 02|Spring Bean 依赖注入常见错误(上)

    控制反转,依赖注入。

    2021-04-23

  • 第 23 讲 | 请介绍类加载过程,什么是双亲委派模型?

    Java通过引入字节码和JVM机制,提供了强大的跨平台能力,理解Java的类加载机制是深入Java开发的必要条件,也是个面试考察热点。

    2018-06-28

  • 13|类型系统:如何使用 trait 来定义接口?

    trait是啥?能干什么?什么时候用?

    2021-09-22

  • 程序员练级攻略:Java 底层知识

    Java最黑科技的玩法是字节码编程,从而使得Java这门静态语言在运行时可以进行各种动态的代码修改。

    2018-07-03

  • 当反射、注解和泛型遇到 OOP 时,会有哪些坑?

    业务项目中大都是增删改查,用到反射、注解和泛型的机会非常少,但只有学好、用好这些高级特性,才能开发出更简洁易读的代码。

    2020-04-23

  • 互操作现在进行时

    JVM和CLR这两个应用最广泛的托管环境只不过是一套共享类库,每个都提供一些执行代码的服务,比如内存管理、线程管理、代码编译(JIT)等。在同一个操作系统进程里同时使用JVM和CLR其实是很容易的,因为任何进程只能装载可共享的类库。

  • Serializable 详解(1):代码验证 Java 序列化与反序列化

    说明:本文为Serializable详解(1),最后两段内容在翻译上出现歧义(暂时未翻译),将在后续的Serializable(2)文中补充。

  • JVM 源码分析之 javaagent 原理完全解读

    本文重点讲述javaagent的具体实现,因为它面向的是我们Java程序员,而且agent都是用Java编写的,不需要太多的C/C++编程基础,不过这篇文章里也会讲到JVMTIAgent(C实现的),因为javaagent的运行还是依赖于一个特殊的JVMTIAgent。

  • JavaAgent 原理与实践

    启动时加载的JavaAgent是JDK1.5之后引入的新特性,此特性为用户提供了在JVM将字节码文件读入内存之后,JVM使用对应的字节流在Java堆中生成一个Class对象之前,用户可以对其字节码进行修改的能力,从而JVM也将会使用用户修改过之后的字节码进行Class对象的创建。

  • Java 深度历险(一)——Java 字节代码的操纵

    Java类文件中包含的字节代码可以被不同平台上的JVM所使用。Java字节代码不仅可以以文件形式存在于磁盘上,也可以通过网络方式来下载,还可以只存在于内存中。JVM中的类加载器会负责从包含字节代码的字节数组(byte[])中定义出Java类。在某些情况下,可能会需要动态的生成 Java字节代码,或是对已有的Java字节代码进行修改。这个时候就需要用到本文中将要介绍的相关技术。

  • Java 反射库中的安全漏洞在 30 个月后终于修复了

    在2013年7月,Security Explorations发现了Java中的一个漏洞,通过这个漏洞攻击者可以提升他们的访问权限。Oracle曾经发布过一个补丁,但是只需简单的修改就可以让攻击继续有效。Oracle在8u77中包含了一个补丁对其进行了修复。在本文中,我们会深入介绍比较鲜为人知的类加载过程,它是这个问题的核心所在。

  • Oracle 弃用 sun.reflect.Reflection.getCallerClass

    从jdk 7u40开始,Oracle弃用了sun.reflect.package包里的Reflection.getCallerClass方法。在Java 8及以后的版本中,该方法将被彻底删除,不再支持对这一感知调用者的行为。

  • Java 8 全面解析

    TechEmpower是位于加利福尼亚州埃尔塞贡多的一家定制应用开发公司,该公司发表了一篇题为“Java 8全面解析”的文章。该博客文章全面概括了开发者在即将到来的Java 8中所要面对的变化。下面的内容快速概括了该博客文章中的信息。如果想查看所有的细节请访问TechEmpower的博客文章。

发现更多内容

Java并发包源码学习系列:阻塞队列实现之SynchronousQueue源码解析

Java 编程

分页方式,看这一篇就够了。

大伟

分页

「产品经理训练营」作业03:利益相关方识别

狷介

产品经理训练营

科技,亲吻这个特别的春节

脑极体

架构训练营大作业(二)

一期一会

京东支付SDK重构设计与实现

京东数科风险算法与技术

产品训练营第三次作业

Geek_79e983

区块链在数字版权领域的应用发展报告(2020)

CECBC

版权保护

静默安装Oracle也没那么恐怖

MySQL从删库到跑路

oracle

PanoVideoCall 的 Electron Demo 开源了

拍乐云Pano

html Mac windows Electron js

架构训练营大作业(一)

一期一会

用最少人力玩转万亿级数据,我用的就是MongoDB

dbaplus社群

夕四今晚加班到2点30,而王二还不打算走《打工人的那些事》

谙忆

如何基于Spring Aware和InitializingBean接口实现策略模式

技术进阶之路

Java spring 5 Java设计模式

欢迎来到,2021摄像机竞技场

脑极体

不会开发的你也能管理好企业漏洞,开源免费工具:洞察(insight II)

BigYoung

安全 安全漏洞 28天写作 2月春节不断更

管理笔记[4]:组织管理的目标就是实现1+1>2

俊毅

精美的淘客项目完全开源啦,确定不来围观吗

Silently9527

Vue mybatis springboot uniapp

《学会写作》学习笔记之如何拟标题

JiangX

28天写作

批判性思维自修课(六)

石君

28天写作 批判性思维

【LeetCode】子集问题debug模式查看数据变化

Albert

算法 LeetCode 2月春节不断更

翻译:《实用的Python编程》00_Setup

codists

Python

看完字节大佬的算法刷题宝典,我直接手撕了500道算法算法题

程序员 面试 算法

学习计算机视觉

IT蜗壳-Tango

OpenCV 计算机视觉 2月春节不断更

如何基于Spring 事件驱动模型实现业务解耦

技术进阶之路

Java spring 架构

中国为什么加快推进数字人民币

CECBC

数字货币

硬核!八张图搞懂 Flink 端到端精准一次处理语义 Exactly-once(深入原理,建议收藏)

五分钟学大数据

大数据 flink

翻译:《实用的Python编程》README

codists

Python

「抖音同款播放器」上市:有效解决卡顿、黑屏和模糊

字节跳动技术团队

区块链--另一场改变社会组织方式的工业革命

CECBC

区块链

字节码操纵技术探秘_Java_Victor Grazi_InfoQ精选文章