1. Introduction
1.介绍
In this article, we’ll look at how to use the ASM library for manipulating an existing Java class by adding fields, adding methods, and changing the behavior of existing methods.
在本文中,我们将了解如何使用ASM库通过添加字段、添加方法和改变现有方法的行为来操作现有的Java类。
2. Dependencies
2.依赖性
We need to add the ASM dependencies to our pom.xml:
我们需要在pom.xml中添加ASM的依赖关系。
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>6.0</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-util</artifactId>
<version>6.0</version>
</dependency>
We can get the latest versions of asm and asm-util from Maven Central.
我们可以从Maven中心获得asm和asm-util的最新版本。
3. ASM API Basics
3.ASM API基础知识
The ASM API provides two styles of interacting with Java classes for transformation and generation: event-based and tree-based.
ASM API提供了两种与Java类互动的方式,用于转换和生成:基于事件和基于树。
3.1. Event-based API
3.1.基于事件的API
This API is heavily based on the Visitor pattern and is similar in feel to the SAX parsing model of processing XML documents. It is comprised, at its core, of the following components:
该API在很大程度上基于Visitor模式,并且在感觉上类似于处理XML文档的SAX解析模型。它的核心是由以下组件组成。
- ClassReader – helps to read class files and is the beginning of transforming a class
- ClassVisitor – provides the methods used to transform the class after reading the raw class files
- ClassWriter – is used to output the final product of the class transformation
It’s in the ClassVisitor that we have all the visitor methods that we’ll use to touch the different components (fields, methods, etc.) of a given Java class. We do this by providing a subclass of ClassVisitor to implement any changes in a given class.
正是在ClassVisitor中,我们拥有了所有的访问者方法,我们将用这些方法来接触特定Java类的不同组件(字段、方法等)。我们通过提供一个ClassVisitor的子类来实现特定类的任何变化。
Due to the need to preserve the integrity of the output class concerning Java conventions and the resulting bytecode, this class requires a strict order in which its methods should be called to generate correct output.
由于需要保持输出类关于Java惯例和所产生的字节码的完整性,这个类需要一个严格的顺序,其方法应被调用以产生正确的输出。
The ClassVisitor methods in the event-based API are called in the following order:
基于事件的API中的ClassVisitor方法是按照以下顺序调用的。
visit
visitSource?
visitOuterClass?
( visitAnnotation | visitAttribute )*
( visitInnerClass | visitField | visitMethod )*
visitEnd
3.2. Tree-based API
3.2.基于树的API
This API is a more object-oriented API and is analogous to the JAXB model of processing XML documents.
这个API是一个更加面向对象的API,并且类似于处理XML文档的JAXB模型。
It’s still based on the event-based API, but it introduces the ClassNode root class. This class serves as the entry point into the class structure.
它仍然基于基于事件的API,但它引入了ClassNode根类。这个类作为进入类结构的入口。
4. Working With the Event-based ASM API
4.使用基于事件的ASM API工作
We’ll modify the java.lang.Integer class with ASM. And we need to grasp a fundamental concept at this point: the ClassVisitor class contains all the necessary visitor methods to create or modify all the parts of a class.
我们将用ASM修改java.lang.Integer类。而在这一点上,我们需要掌握一个基本概念。ClassVisitor类包含了所有必要的访问者方法,以创建或修改一个类的所有部分。
We only need to override the necessary visitor method to implement our changes. Let’s start by setting up the prerequisite components:
我们只需要重写必要的访问者方法来实现我们的改变。让我们从设置先决条件的组件开始。
public class CustomClassWriter {
static String className = "java.lang.Integer";
static String cloneableInterface = "java/lang/Cloneable";
ClassReader reader;
ClassWriter writer;
public CustomClassWriter() {
reader = new ClassReader(className);
writer = new ClassWriter(reader, 0);
}
}
We use this as a basis to add the Cloneable interface to the stock Integer class, and we also add a field and a method.
我们以此为基础,将Cloneable接口添加到存量Integer类中,并且我们还添加了一个字段和一个方法。
4.1. Working With Fields
4.1.使用字段工作
Let’s create our ClassVisitor that we’ll use to add a field to the Integer class:
让我们创建我们的ClassVisitor,我们将用它来为Integer类添加一个字段。
public class AddFieldAdapter extends ClassVisitor {
private String fieldName;
private String fieldDefault;
private int access = org.objectweb.asm.Opcodes.ACC_PUBLIC;
private boolean isFieldPresent;
public AddFieldAdapter(
String fieldName, int fieldAccess, ClassVisitor cv) {
super(ASM4, cv);
this.cv = cv;
this.fieldName = fieldName;
this.access = fieldAccess;
}
}
Next, let’s override the visitField method, where we first check if the field we plan to add already exists and set a flag to indicate the status.
接下来,让我们覆盖visitField方法,我们首先检查我们打算添加的字段是否已经存在,并设置一个标志来指示状态。
We still have to forward the method call to the parent class — this needs to happen as the visitField method is called for every field in the class. Failing to forward the call means no fields will be written to the class.
我们仍然需要将方法调用转发到父类–这需要发生,因为visitField方法会对类中的每个字段进行调用。未能转发调用意味着没有字段会被写入类中。。
This method also allows us to modify the visibility or type of existing fields:
这种方法也允许我们修改现有字段的可见性或类型。
@Override
public FieldVisitor visitField(
int access, String name, String desc, String signature, Object value) {
if (name.equals(fieldName)) {
isFieldPresent = true;
}
return cv.visitField(access, name, desc, signature, value);
}
We first check the flag set in the earlier visitField method and call the visitField method again, this time providing the name, access modifier, and description. This method returns an instance of FieldVisitor.
我们首先检查先前visitField方法中设置的标志,然后再次调用visitField方法,这次提供名称、访问修改器和描述。这个方法返回一个FieldVisitor.的实例。
The visitEnd method is the last method called in order of the visitor methods. This is the recommended position to carry out the field insertion logic.
visitEnd方法是按访问者方法的顺序最后调用的方法。这是执行字段插入逻辑的推荐位置。
Then, we need to call the visitEnd method on this object to signal that we’re done visiting this field:
然后,我们需要在这个对象上调用visitEnd方法,以示我们已经完成了对这个领域的访问:。
@Override
public void visitEnd() {
if (!isFieldPresent) {
FieldVisitor fv = cv.visitField(
access, fieldName, fieldType, null, null);
if (fv != null) {
fv.visitEnd();
}
}
cv.visitEnd();
}
It’s important to be sure that all the ASM components used come from the org.objectweb.asm package — a lot of libraries use the ASM library internally and IDEs could auto-insert the bundled ASM libraries.
确保所有使用的ASM组件来自org.objectweb.asm 包很重要–很多库都在内部使用ASM库,IDE可以自动插入捆绑的ASM库。
We now use our adapter in the addField method, obtaining a transformed version of java.lang.Integer with our added field:
我们现在在addField方法中使用我们的适配器,获得一个转换后的java.lang.Integer的版本,并添加了字段。
public class CustomClassWriter {
AddFieldAdapter addFieldAdapter;
//...
public byte[] addField() {
addFieldAdapter = new AddFieldAdapter(
"aNewBooleanField",
org.objectweb.asm.Opcodes.ACC_PUBLIC,
writer);
reader.accept(addFieldAdapter, 0);
return writer.toByteArray();
}
}
We’ve overridden the visitField and visitEnd methods.
我们已经重写了visitField和visitEnd方法。
Everything to be done concerning fields happens with the visitField method. This means we can also modify existing fields (say, transforming a private field to the public) by changing the desired values passed to the visitField method.
所有关于字段的工作都是通过visitField方法进行的。这意味着我们也可以通过改变传递给visitField方法的期望值来修改现有的字段(例如,将一个私有字段转变为公共字段)。
4.2. Working With Methods
4.2.用方法工作
Generating whole methods in the ASM API is more involved than other operations in the class. This involves a significant amount of low-level byte-code manipulation and, as a result, is beyond the scope of this article.
在ASM API中生成整个方法要比类中的其他操作更复杂。这涉及到大量的低级别的字节码操作,因此,超出了本文的范围。
For most practical uses, however, we can either modify an existing method to make it more accessible (perhaps make it public so that it can be overridden or overloaded) or modify a class to make it extensible.
然而,对于大多数实际用途来说,我们可以修改一个现有的方法,使其更容易访问(也许使其成为公共的,以便它可以被重载或覆盖),或者修改一个类,使其可扩展。
Let’s make the toUnsignedString method public:
让我们把toUnsignedString方法公开。
public class PublicizeMethodAdapter extends ClassVisitor {
public PublicizeMethodAdapter(int api, ClassVisitor cv) {
super(ASM4, cv);
this.cv = cv;
}
public MethodVisitor visitMethod(
int access,
String name,
String desc,
String signature,
String[] exceptions) {
if (name.equals("toUnsignedString0")) {
return cv.visitMethod(
ACC_PUBLIC + ACC_STATIC,
name,
desc,
signature,
exceptions);
}
return cv.visitMethod(
access, name, desc, signature, exceptions);
}
}
Like we did for the field modification, we merely intercept the visit method and change the parameters we desire.
就像我们对字段的修改一样,我们只是拦截访问方法并改变我们想要的参数。
In this case, we use the access modifiers in the org.objectweb.asm.Opcodes package to change the visibility of the method. We then plug in our ClassVisitor:
在这种情况下,我们使用org.objectweb.asm.Opcodes包中的访问修改器来改变该方法的可见性。然后我们插入我们的ClassVisitor。
public byte[] publicizeMethod() {
pubMethAdapter = new PublicizeMethodAdapter(writer);
reader.accept(pubMethAdapter, 0);
return writer.toByteArray();
}
4.3. Working With Classes
4.3.使用类的工作
Along the same lines as modifying methods, we modify classes by intercepting the appropriate visitor method. In this case, we intercept visit, which is the very first method in the visitor hierarchy:
沿着修改方法的思路,我们通过拦截适当的访问者方法来修改类。在本例中,我们拦截visit,它是访问者层次结构中的第一个方法。
public class AddInterfaceAdapter extends ClassVisitor {
public AddInterfaceAdapter(ClassVisitor cv) {
super(ASM4, cv);
}
@Override
public void visit(
int version,
int access,
String name,
String signature,
String superName, String[] interfaces) {
String[] holding = new String[interfaces.length + 1];
holding[holding.length - 1] = cloneableInterface;
System.arraycopy(interfaces, 0, holding, 0, interfaces.length);
cv.visit(V1_8, access, name, signature, superName, holding);
}
}
We override the visit method to add the Cloneable interface to the array of interfaces to be supported by the Integer class. We plug this in just like all the other uses of our adapters.
我们覆盖visit方法,将Cloneable接口添加到Integer类所支持的接口阵列中。我们把它插入,就像我们适配器的所有其他用途一样。
5. Using the Modified Class
5.使用修改后的类
So we’ve modified the Integer class. Now we need to be able to load and use the modified version of the class.
所以我们已经修改了Integer类。现在我们需要能够加载和使用该类的修改版本。
In addition to simply writing the output of writer.toByteArray to disk as a class file, there are some other ways to interact with our customized Integer class.
除了简单地将writer.toByteArray的输出作为类文件写入磁盘外,还有一些其他的方法可以与我们定制的Integer类互动。
5.1. Using the TraceClassVisitor
5.1.使用TraceClassVisitor
The ASM library provides the TraceClassVisitor utility class that we’ll use to introspect the modified class. Thus we can confirm that our changes have happened.
ASM库提供了TraceClassVisitor实用类,我们将用它来窥视修改后的类。因此,我们可以确认我们的修改已经发生。
Because the TraceClassVisitor is a ClassVisitor, we can use it as a drop-in replacement for a standard ClassVisitor:
因为TraceClassVisitor是一个ClassVisitor,我们可以用它来替代标准ClassVisitor。
PrintWriter pw = new PrintWriter(System.out);
public PublicizeMethodAdapter(ClassVisitor cv) {
super(ASM4, cv);
this.cv = cv;
tracer = new TraceClassVisitor(cv,pw);
}
public MethodVisitor visitMethod(
int access,
String name,
String desc,
String signature,
String[] exceptions) {
if (name.equals("toUnsignedString0")) {
System.out.println("Visiting unsigned method");
return tracer.visitMethod(
ACC_PUBLIC + ACC_STATIC, name, desc, signature, exceptions);
}
return tracer.visitMethod(
access, name, desc, signature, exceptions);
}
public void visitEnd(){
tracer.visitEnd();
System.out.println(tracer.p.getText());
}
What we have done here is to adapt the ClassVisitor that we passed to our earlier PublicizeMethodAdapter with the TraceClassVisitor.
我们在这里所做的是用TraceClassVisitor来调整我们先前传递给PublicizeMethodAdapter的TraceClassVisitor。
All the visiting will now be done with our tracer, which then can print out the content of the transformed class, showing any modifications we’ve made to it.
现在所有的访问都将由我们的跟踪器完成,然后可以打印出转换后的类的内容,显示我们对它所做的任何修改。
While the ASM documentation states that the TraceClassVisitor can print out to the PrintWriter that’s supplied to the constructor, this doesn’t appear to work properly in the latest version of ASM.
虽然ASM文档指出,TraceClassVisitor可以打印出提供给构造函数的PrintWriter,但在ASM的最新版本中,这似乎不能正常工作。
Fortunately, we have access to the underlying printer in the class and were able to manually print out the tracer’s text contents in our overridden visitEnd method.
幸运的是,我们可以访问类中的底层打印机,并且能够在我们重载的visitEnd方法中手动打印出追踪器的文本内容。
5.2. Using Java Instrumentation
5.2.使用Java Instrumentation
This is a more elegant solution that allows us to work with the JVM at a closer level via Instrumentation.
这是一个更优雅的解决方案,它允许我们通过Instrumentation与JVM在更近的层面上合作。
To instrument the java.lang.Integer class, we write an agent that will be configured as a command line parameter with the JVM. The agent requires two components:
为了检测java.lang.Integer类,我们写一个代理,它将被配置为JVM的一个命令行参数。该代理需要两个组件。
- A class that implements a method named premain
- An implementation of ClassFileTransformer in which we’ll conditionally supply the modified version of our class
public class Premain {
public static void premain(String agentArgs, Instrumentation inst) {
inst.addTransformer(new ClassFileTransformer() {
@Override
public byte[] transform(
ClassLoader l,
String name,
Class c,
ProtectionDomain d,
byte[] b)
throws IllegalClassFormatException {
if(name.equals("java/lang/Integer")) {
CustomClassWriter cr = new CustomClassWriter(b);
return cr.addField();
}
return b;
}
});
}
}
We now define our premain implementation class in a JAR manifest file using the Maven jar plugin:
现在我们使用Maven jar插件在JAR清单文件中定义我们的premain实现类。
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<configuration>
<archive>
<manifestEntries>
<Premain-Class>
com.baeldung.examples.asm.instrumentation.Premain
</Premain-Class>
<Can-Retransform-Classes>
true
</Can-Retransform-Classes>
</manifestEntries>
</archive>
</configuration>
</plugin>
Building and packaging our code so far produces the jar that we can load as an agent. To use our customized Integer class in a hypothetical “YourClass.class“:
到目前为止,构建和打包我们的代码产生了我们可以作为一个代理加载的jar。要在一个假设的”YourClass.class“中使用我们定制的Integer类。
java YourClass -javaagent:"/path/to/theAgentJar.jar"
6. Conclusion
6.结论
While we implemented our transformations here individually, ASM allows us to chain multiple adapters together to achieve complex transformations of classes.
虽然我们在这里单独实现了我们的转换,但ASM允许我们将多个适配器连在一起,以实现类的复杂转换。
In addition to the basic transformations we examined here, ASM also supports interactions with annotations, generics, and inner classes.
除了我们在这里考察的基本转换之外,ASM还支持与注解、泛型和内部类的交互。
We’ve seen some of the power of the ASM library — it removes a lot of limitations we might encounter with third-party libraries and even standard JDK classes.
我们已经看到了ASM库的一些力量–它消除了我们在第三方库甚至标准JDK类中可能遇到的很多限制。
ASM is widely used under the hood of some of the most popular libraries (Spring, AspectJ, JDK, etc.) to perform a lot of “magic” on the fly.
ASM在一些最流行的库(Spring、AspectJ、JDK等)的罩子下被广泛使用,以在飞行中执行大量的 “魔法”。
You can find the source code for this article in the GitHub project.
你可以在GitHub项目中找到这篇文章的源代码。