Java Agent入门学习之动态修改代码
前言
最近用了一下午总算把Javaagent给跑通了,本篇文章记录一下具体的操作步骤,以免遗忘。下面话不多说,来一起看看详细的介绍:
通过javaagent可以动态修改代码(替换、修改类的定义),进行AOP。
目标:
为所有添加@ToString注解的类实现默认的toString方法
需要两个程序,一个是用来测试的程序,一个agent用于修改代码。
1.测试程序
被测试的程序包括:
-ToString.Java
-Foo.java
-Main.java
具体代码如下:
ToString.java:定义ToString注解
packagecom.chosen0ne.agent.test; importjava.lang.annotation.Retention; importjava.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.RUNTIME) public@interfaceToString{ }
Foo.java:很简单用于测试,使用了ToString注解
packagecom.chosen0ne.agent.test; @ToString publicclassFoo{ }
Main.java:
packagecom.chosen0ne.agent.test; publicclassMain{ publicstaticvoidmain(String[]args){ Foofoo=newFoo(); System.out.println(foo.toString()); } }
执行Main.java,结果如下:
com.chosen0ne.agent.test.Foo@7852e922
可以看到toString返回的是Object的默认实现。
2.Agent程序
javaagent程序实际上类似于钩子,有两种方式:
-main函数开始前
-程序运行中
这里主要测试main函数开始前的情况。类似于main函数,需要实现
publicstaticvoidpremain(StringagentArgs,Instrumentationinst);
这个函数会在main函数之前被调用。可以在premain中,进行字节码操作,替换或重新实现一些类。这里使用ByteBuddy库,在ASM之上提供了更高级的抽象,便于使用。
具体代码如下:
packagecom.chosen0ne.ByteCode.agent; importjava.lang.instrument.Instrumentation; importcom.chosen0ne.agent.test.ToString; importnet.bytebuddy.agent.builder.AgentBuilder; importnet.bytebuddy.description.type.TypeDescription; importnet.bytebuddy.dynamic.DynamicType.Builder; importnet.bytebuddy.implementation.FixedValue; importnet.bytebuddy.matcher.ElementMatchers; publicclassToStringAgent{ publicstaticvoidpremain(Stringargs,Instrumentationinstrumentation){ System.out.println("printpremain"); newAgentBuilder.Default() .type(ElementMatchers.isAnnotatedWith(ToString.class)) .transform(newAgentBuilder.Transformer(){ @Override publicBuilder>transform(Builder>builder, TypeDescriptiontypeDescription,ClassLoaderclassLoader){ returnbuilder.method(ElementMatchers.named("toString")) .intercept(FixedValue.value("test")); } }).installOn(instrumentation); } }
agent需要打包成jar,并且对于premain的方式需要在MANIFEST.MF中指定Premain-Class,用于指明包含premain函数的类。具体有两种方式打包:
1)直接通过jar命令
编辑生成MANIFEST.MF后,执行:
jarcvfmagent.jarMANIFEST.MF-C.comlib
上述命令打包成的jar包含:
-com:编译生成的class文件
-lib:其依赖的库
2)通过maven直接生成:
通过maven-jar-plugin插件生成jar包,具体配置如下:
org.apache.maven.plugins maven-jar-plugin 2.1 true lib/ com.chosen0ne.ByteCode.ByteBuddyTest com.chosen0ne.ByteCode.agent.ToStringAgent
主要通过manifestEntries标签生成自动的属性,这里指定了Premain-Class
3.运行
将生成的agent.jar、依赖的ByteBuddy的jar包和测试程序编译生成的class文件放到一个路径下,目录布局如下:
. ├──agent.jar ├──classes │└──com │└──chosen0ne │└──agent │└──test │├──Foo.class │├──Main.class │└──ToString.class └──lib └──byte-buddy-1.2.3.jar
在当前目录执行命令:
java-cpclasses:lib/byte-buddy-1.2.3.jar-javaagent:agent.jarcom.chosen0ne.agent.test.Main
运行结果如下:
printpremain test
这里需要注意一点,如果将测试程序也打包成jar包的话,那么在通过-cp指定ByteBuddy库时会失败,找不到对应的class,错误如下:
>java-cpclasses:lib/byte-buddy-1.2.3.jar-javaagent:agent.jar-jaragent-test-case-0.0.1-SNAPSHOT.jar Exceptioninthread"main"java.lang.NoClassDefFoundError:net/bytebuddy/matcher/ElementMatcher atjava.lang.Class.getDeclaredMethods0(NativeMethod) atjava.lang.Class.privateGetDeclaredMethods(Class.java:2688) atjava.lang.Class.getDeclaredMethod(Class.java:2115) atsun.instrument.InstrumentationImpl.loadClassAndStartAgent(InstrumentationImpl.java:327) atsun.instrument.InstrumentationImpl.loadClassAndCallPremain(InstrumentationImpl.java:401) Causedby:java.lang.ClassNotFoundException:net.bytebuddy.matcher.ElementMatcher atjava.net.URLClassLoader$1.run(URLClassLoader.java:372) atjava.net.URLClassLoader$1.run(URLClassLoader.java:361) atjava.security.AccessController.doPrivileged(NativeMethod) atjava.net.URLClassLoader.findClass(URLClassLoader.java:360) atjava.lang.ClassLoader.loadClass(ClassLoader.java:424) atsun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308) atjava.lang.ClassLoader.loadClass(ClassLoader.java:357) ...5more FATALERRORinnativemethod:processingof-javaagentfailed
暂时不知道具体原因。。。所以直接以class运行即可
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对毛票票的支持。