前言
最近用了一下午總算把Java agent給跑通了,本篇文章記錄一下具體的操作步驟,以免遺忘。下面話(huà)不多說(shuō),來(lái)一起看看詳細(xì)的介紹:
通過(guò)java agent可以動(dòng)態(tài)修改代碼(替換、修改類(lèi)的定義),進(jìn)行AOP。
目標(biāo):
為所有添加@ToString注解的類(lèi)實(shí)現(xiàn)默認(rèn)的toString方法
需要兩個(gè)程序,一個(gè)是用來(lái)測(cè)試的程序,一個(gè)agent用于修改代碼。
1. 測(cè)試程序
被測(cè)試的程序包括:
- ToString.Java
- Foo.java
- Main.java
具體代碼如下:
ToString.java:定義ToString注解
1
2
3
4
5
6
7
8
|
package com.chosen0ne.agent.test; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Retention (RetentionPolicy.RUNTIME) public @interface ToString { } |
Foo.java:很簡(jiǎn)單用于測(cè)試,使用了ToString注解
1
2
3
4
5
6
|
package com.chosen0ne.agent.test; @ToString public class Foo { } |
Main.java:
1
2
3
4
5
6
7
8
|
package com.chosen0ne.agent.test; public class Main { public static void main(String[] args) { Foo foo = new Foo(); System.out.println(foo.toString()); } } |
執(zhí)行Main.java,結(jié)果如下:
1
|
com.chosen0ne.agent.test.Foo @7852e922 |
可以看到toString返回的是Object的默認(rèn)實(shí)現(xiàn)。
2. Agent程序
java agent程序?qū)嶋H上類(lèi)似于鉤子,有兩種方式:
- main函數(shù)開(kāi)始前
- 程序運(yùn)行中
這里主要測(cè)試main函數(shù)開(kāi)始前的情況。類(lèi)似于main函數(shù),需要實(shí)現(xiàn)
1
|
public static void premain(String agentArgs, Instrumentation inst); |
這個(gè)函數(shù)會(huì)在main函數(shù)之前被調(diào)用。可以在premain中,進(jìn)行字節(jié)碼操作,替換或重新實(shí)現(xiàn)一些類(lèi)。這里使用Byte Buddy庫(kù),在ASM之上提供了更高級(jí)的抽象,便于使用。
具體代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
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); } } |
agent需要打包成jar,并且對(duì)于premain的方式需要在MANIFEST.MF中指定Premain-Class,用于指明包含premain函數(shù)的類(lèi)。具體有兩種方式打包:
1)直接通過(guò)jar命令
編輯生成MANIFEST.MF后,執(zhí)行:
1
|
jar cvfm agent.jar MANIFEST.MF -C . com lib |
上述命令打包成的jar包含:
- com:編譯生成的class文件
- lib:其依賴(lài)的庫(kù)
2)通過(guò)maven直接生成:
通過(guò)maven-jar-plugin插件生成jar包,具體配置如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
<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> |
主要通過(guò)manifestEntries標(biāo)簽生成自動(dòng)的屬性,這里指定了Premain-Class
3. 運(yùn)行
將生成的agent.jar、依賴(lài)的ByteBuddy的jar包和測(cè)試程序編譯生成的class文件放到一個(gè)路徑下,目錄布局如下:
1
2
3
4
5
6
7
8
9
10
11
12
|
. ├── agent.jar ├── classes │ └── com │ └── chosen0ne │ └── agent │ └── test │ ├── Foo.class │ ├── Main.class │ └── ToString.class └── lib └── byte-buddy-1.2.3.jar |
在當(dāng)前目錄執(zhí)行命令:
1
|
java -cp classes:lib/byte-buddy-1.2.3.jar -javaagent:agent.jar com.chosen0ne.agent.test.Main |
運(yùn)行結(jié)果如下:
1
2
|
print pre main test |
這里需要注意一點(diǎn),如果將測(cè)試程序也打包成jar包的話(huà),那么在通過(guò)-cp指定ByteBuddy庫(kù)時(shí)會(huì)失敗,找不到對(duì)應(yīng)的class,錯(cuò)誤如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
> java -cp classes:lib/ byte -buddy- 1.2 . 3 .jar -javaagent:agent.jar -jar agent-test- case - 0.0 . 1 -SNAPSHOT.jar Exception in thread "main" java.lang.NoClassDefFoundError: net/bytebuddy/matcher/ElementMatcher at 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.ElementMatcher at 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 more FATAL ERROR in native method: processing of -javaagent failed |
暫時(shí)不知道具體原因。。。所以直接以class運(yùn)行即可
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來(lái)一定的幫助,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)服務(wù)器之家的支持。
原文鏈接:http://blog.csdn.net/chosen0ne/article/details/50790372