欢迎大家来到IT世界,在知识的湖畔探索吧!
简单来说,Javaagent可以让我们在不修改程序代码的前提下通过Instrumentation API改变运行中的java程序。
当Java虚拟机启动时,在执行 main 函数之前,JVM会先运行-javaagent所指定jar包内Premain-Class这个类的premain方法。
这个premain方法应该怎么写呢?这就是我们要讲的。
前言
基础描述什么的我就懒得复制粘贴了,找了俩博文放在这里,先粗略了解一下。
- 什么是JavaAgent?
- Javaagent使用指南
基础案例:重写Test对象的main方法
假设有 demo.Test 如下,我们要将main方法里的 sayHello(“java”); 改为 sayHello(“修改过后的java”);
<pre class="prettyprint hljs cs" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">package demo;
public class Test{
String aa = "value-aa";
String bb = "value-bb";
public static void main(String[] args) {
System.out.println(new Test());
sayHello("java");
}
public static void sayHello(String name) {
System.out.println("Hello, " + name);
}
}</pre>
欢迎大家来到IT世界,在知识的湖畔探索吧!
我们采取最傻瓜的办法,自己重新再写一个Test类,然后编译成字节码,当需要的时候直接将编译好的内容给上。
生成字节码
编译写好的 demo.Test ,生成 demo.Test.class ,为了方便区别,重命名为 Test.class.modified
以下为具体实现:
欢迎大家来到IT世界,在知识的湖畔探索吧!<pre class="prettyprint hljs cs" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">package demo;
public class Test{
String aa = "value-aa";
String bb = "value-bb";
public static void main(String[] args) {
System.out.println(new Test());
sayHello("修改过后的java");
}
public static void sayHello(String name) {
System.out.println("Hello, " + name);
}
}</pre>
创建agent
如果待处理的类是 demo.Test ,那么提供修改后的字节码;否则原封不动。
<pre class="prettyprint hljs cs" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">public class MyAgent {
public static void premain(String args, Instrumentation inst) {
// args 是命令行的入参
inst.addTransformer(new Transformer(args));
}
private static class Transformer implements ClassFileTransformer {
public Transformer(String args) {
}
static byte[] readAll(InputStream in) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
for (int len = 0; (len = in.read(buffer)) != -1;) {
out.write(buffer, 0, len);
}
in.close();
return out.toByteArray();
}
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
//System.out.println(className);
// 转换指定包名开头的类
if (className.equals("demo/Test")) {
try {
System.out.println("demo/test被修改");
return readAll(MyAgent.class.getResourceAsStream("/resources/Test.class.modified"));
} catch (Throwable t) {
t.printStackTrace();
return classfileBuffer;
}
} else {
return classfileBuffer;
}
}
}
}</pre>
创建MANIFEST.MF
创建MANIFEST.MF,指定上面premain方法所在的类
为了方便,我把agent和待测试的都打包成一个 test.jar ,所以下面还多了一行 Main-Class: demo.Test
欢迎大家来到IT世界,在知识的湖畔探索吧!<pre class="prettyprint hljs yaml" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">Manifest-Version: 1.0
Class-Path: .
Main-Class: demo.Test
Premain-Class: demo.javaagent.MyAgent
Can-Redefine-Classes: true</pre>
测试效果
<pre class="prettyprint hljs xml" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">> java -jar test.jar
<a href="/cdn-cgi/l/email-protection" data-cfemail="a0c4c5cdcf8ef4c5d3d4e09795c29894c39992">[email protected]</a>
Hello, java
> java -javaagent:test.jar=args -jar test.jar
demo/test被修改
<a href="/cdn-cgi/l/email-protection" data-cfemail="640001090b4a30011710245351065c50075d56">[email protected]</a>
Hello, 修改过后的java</pre>
基础案例:添加对象的toString方法
Test对象并没有重载Object的toString方法,所以我们打印的时候会直接出现内存地址。
我们期望的Test应该如下:
<pre class="prettyprint hljs gradle" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">package demo;
public class Test{
String aa = "value-aa";
String bb = "value-bb";
public String toString() {
StringBuilder sb =new StringBuilder("Test(");
sb.append("aa").append("=").append(aa).append(",");
sb.append("bb").append("=").append(bb).append(",");
sb.append(")");
return sb.toString();
}
public static void main(String[] args) {
System.out.println(new Test());
sayHello("java");
}
public static void sayHello(String name) {
System.out.println("Hello, " + name);
}
}</pre>
现在我们换个方法,不再死死的自己去重写代码然后编译,而是使用javaassisit去实现agent。
<pre class="prettyprint hljs gradle" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">public class MyAgentJavaAssist {
public static void premain(String args, Instrumentation inst) {
// args 是命令行的入参
inst.addTransformer(new Transformer(args));
}
private static class Transformer implements ClassFileTransformer {
public Transformer(String args) {
}
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
try {
ClassPool pool = ClassPool.getDefault();
ByteArrayInputStream in = new ByteArrayInputStream(classfileBuffer);
CtClass cc = pool.makeClass(in);
// 判断有无声明 toString() 方法,没有的话就生成一个
try {
cc.getDeclaredMethod("toString", new CtClass[] {});
} catch (NotFoundException e) {
CtMethod cm = new CtMethod(pool.getCtClass("java.lang.String"), "toString", new CtClass[] {}, cc);
StringBuilder sBody = new StringBuilder();
sBody.append("{StringBuilder sb =new StringBuilder(\"").append(cc.getSimpleName()).append("(\");");
CtField[] cfs = cc.getDeclaredFields();
for (CtField cf : cfs) {
sBody.append("sb.append(\"").append(cf.getName()).append("\").append(\"=\").append(")
.append(cf.getName()).append(").append(\",\");");
}
sBody.append("sb.append(\")\");").append("return sb.toString();}");
cm.setBody(sBody.toString());
cc.addMethod(cm);
}
// 判断是否是 demo/Test, 是的话修改 main方法
if (className.equals("demo/Test")) {
CtMethod cm = cc.getDeclaredMethod("main",
new CtClass[] { pool.getCtClass("[Ljava.lang.String;") });
cm.setBody("{System.out.println(new demo.Test());sayHello(\"修改过后的java\");}");
}
return cc.toBytecode();
} catch (Throwable t) {
t.printStackTrace();
return classfileBuffer;
}
}
}
}</pre>
测试效果
来源: https://nicelee.top/blog/2022/08/05/java-agent/
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://itzsg.com/36504.html