Java Agent-dynamic code modification
After an afternoon, I finally ran the java agent. This article records the specific operation steps to avoid forgetting...
You can use java agent to dynamically modify code (replace and modify class definitions) for AOP.
Objectives:
Implement the default ToString method for all classes added with the @ toString Annotation
Two programs are required, one for testing and the other for modifying the code. 1. Test Procedure
The tested programs include:
-ToString. java
-Foo. java
-Main. java
The Code is as follows:
ToString. java: Define the ToString Annotation
package com.chosen0ne.agent.test;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;@Retention(RetentionPolicy.RUNTIME)public @interface ToString {}
Foo. java: It is very simple for testing and uses the ToString annotation.
package com.chosen0ne.agent.test;@ToStringpublic class Foo {}
Main. java:
package com.chosen0ne.agent.test;public class Main { public static void main(String[] args) { Foo foo = new Foo(); System.out.println(foo.toString()); }}
Run Main. java and the result is as follows:
[email protected]
ToString returns the default Implementation of the Object. 2. Agent Program
Java agent programs are similar to hooks in two ways:
-Before the main function starts
-Running
Here we mainly test the situation before the main function starts. Similar to the main function
public static void premain(String agentArgs, Instrumentation inst);
This function will be called before the main function. You can perform Bytecode operations in premain to replace or re-implement some classes. Byte Buddy library is used here, which provides more advanced abstraction on ASM for ease of use. The Code is as follows:
package com.chosen0ne.ByteCode.agent;import java.lang.instrument.Instrumentation;import com.chosen0ne.agent.test.ToString;import net.bytebuddy.agent.builder.AgentBuilder;import net.bytebuddy.description.type.TypeDescription;import net.bytebuddy.dynamic.DynamicType.Builder;import net.bytebuddy.implementation.FixedValue;import net.bytebuddy.matcher.ElementMatchers;public class ToStringAgent { public static void premain(String args, Instrumentation instrumentation) { System.out.println("print pre main"); new AgentBuilder.Default() .type(ElementMatchers.isAnnotatedWith(ToString.class)) .transform(new AgentBuilder.Transformer() { @Override public Builder
transform(Builder
builder, TypeDescription typeDescription, ClassLoader classLoader) { return builder.method(ElementMatchers.named("toString")) .intercept(FixedValue.value("test")); } }).installOn(instrumentation); }}The agent needs to be packaged as a jar, and premain-Class must be specified in MANIFEST. MF for the Premain method to specify the Class containing the premain function. There are two methods for packaging:
1) run the jar command directly.
After editing and generating MANIFEST. MF, run:
jar cvfm agent.jar MANIFEST.MF -C . com lib
The jar package in the preceding command contains:
-Com: compiled class files
-Lib: the dependent Library
2) generate directly using maven:
Use the maven-jar-plugin plug-in to generate a jar package. The configuration is as follows:
<build> <plugins> <plugin> <groupid>org.apache.maven.plugins</groupid> <artifactid>maven-jar-plugin</artifactid> <version>2.1</version> <configuration> <archive> <manifest> <addclasspath>true</addclasspath> <classpathprefix>lib/</classpathprefix> <mainclass>com.chosen0ne.ByteCode.ByteBuddyTest</mainclass> </manifest> <manifestentries> <premain-class>com.chosen0ne.ByteCode.agent.ToStringAgent</premain-class> </manifestentries> </archive> </configuration> </plugin> </plugins></build>
Auto properties are generated mainly through the manifestEntries tag, where the Premain-Class3 is specified. Run
Put the generated agent. jar, ByteBuddy jar package, and the class file compiled by the test program in a path. The directory layout is as follows:
.├── agent.jar├── classes│ └── com│ └── chosen0ne│ └── agent│ └── test│ ├── Foo.class│ ├── Main.class│ └── ToString.class└── lib └── byte-buddy-1.2.3.jar
Run the following command in the current directory:
java -cp classes:lib/byte-buddy-1.2.3.jar -javaagent:agent.jar com.chosen0ne.agent.test.Main
The running result is as follows:
print pre maintest
Note that if you package the test program into a jar package, you will fail to specify the ByteBuddy library through-cp. The corresponding class cannot be found. The error is as follows:
> java -cp classes:lib/byte-buddy-1.2.3.jar -javaagent:agent.jar -jar agent-test-case-0.0.1-SNAPSHOT.jarException in thread "main" java.lang.NoClassDefFoundError: net/bytebuddy/matcher/ElementMatcherat java.lang.Class.getDeclaredMethods0(Native Method)at java.lang.Class.privateGetDeclaredMethods(Class.java:2688)at java.lang.Class.getDeclaredMethod(Class.java:2115)at sun.instrument.InstrumentationImpl.loadClassAndStartAgent(InstrumentationImpl.java:327)at sun.instrument.InstrumentationImpl.loadClassAndCallPremain(InstrumentationImpl.java:401)Caused by: java.lang.ClassNotFoundException: net.bytebuddy.matcher.ElementMatcherat java.net.URLClassLoader$1.run(URLClassLoader.java:372)at java.net.URLClassLoader$1.run(URLClassLoader.java:361)at java.security.AccessController.doPrivileged(Native Method)at java.net.URLClassLoader.findClass(URLClassLoader.java:360)at java.lang.ClassLoader.loadClass(ClassLoader.java:424)at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)at java.lang.ClassLoader.loadClass(ClassLoader.java:357)... 5 moreFATAL ERROR in native method: processing of -javaagent failed
I do not know the specific reason... So you can run it directly in class.