本文重点java Instrumentation
java Instrumentation指的是可以用独立于应用程序之外的代理(agent)程序来监测和协助运行在JVM上的应用程序。这种监测和协助包括但不限于获取JVM运行时状态,替换和修改类定义等。简单一句话概括下:Java Instrumentation可以在JVM启动后,动态修改已加载或者未加载的类,包括类的属性、方法。
成功图
这里演示是直接本地启动,实战中需要去打包成jar包
这里有个问题,打包成jar文件,运行一直显示确实tools.jar包,但是我的系统路径是有tools.jar的,和项目所用的tools路径一样,但还是不行
如下报错
接着我将tools.jar放到项目中,添加library,
还是不行
下面看一个简单的例子:首先新建3个Java工程Example、Agent和AgentStarter。
最开始的Bird.java
public class Bird {
public void say()
{
System.out.println("bird is gone.");
}
}
生成Bird.class文件
然后修改Bird.java
public class Bird {
public void say(){
System.out.println("bird say hello");
}
}
编写主类:
public class Main {
public static void main(String[] args) throws Exception {
while (true){
Bird bird = new Bird();
bird.say();
Thread.sleep(3000);
}
}
}
将这两个打包成Example.jar
AgentEntry.java
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
public class AgentEntry {
public static void agentmain(String agentArgs, Instrumentation inst)
throws ClassNotFoundException, UnmodifiableClassException,
InterruptedException {
inst.addTransformer(new Transformer (), true);
Class[] loadedClasses = inst.getAllLoadedClasses();
for (Class c : loadedClasses) {
if (c.getName().equals("Bird")) {
try {
inst.retransformClasses(c);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
System.out.println("Class changed!");
}
}
Transformer.java
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
import java.util.Arrays;
public class Transformer implements ClassFileTransformer {
static byte[] mergeByteArray(byte[]... byteArray) {
int totalLength = 0;
for(int i = 0; i < byteArray.length; i ++) {
if(byteArray[i] == null) {
continue;
}
totalLength += byteArray[i].length;
}
byte[] result = new byte[totalLength];
int cur = 0;
for(int i = 0; i < byteArray.length; i++) {
if(byteArray[i] == null) {
continue;
}
System.arraycopy(byteArray[i], 0, result, cur, byteArray[i].length);
cur += byteArray[i].length;
}
return result;
}
public static byte[] getBytesFromFile(String fileName) {
try {
byte[] result=new byte[] {};
InputStream is = new FileInputStream(new File(fileName));
byte[] bytes = new byte[1024];
int num = 0;
while ((num = is.read(bytes)) != -1) {
result=mergeByteArray(result, Arrays.copyOfRange(bytes, 0, num));
}
is.close();
return result;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public byte[] transform(ClassLoader classLoader, String className, Class<?> c,
ProtectionDomain pd, byte[] b) throws IllegalClassFormatException {
if (!className.equals("Bird")) {
return null;
}
return getBytesFromFile("d:/Bird.class");
}
}
MANIFEST.MF:
Manifest-Version: 1.0
Agent-Class: AgentEntry
Can-Retransform-Classes: true
将这三个打包成agent.jar
Attach.java:
import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.VirtualMachineDescriptor;
import java.util.List;
public class Attach {
public static void main(String[] args) throws Exception {
VirtualMachine vm = null;
List<VirtualMachineDescriptor> listAfter = null;
List<VirtualMachineDescriptor> listBefore = null;
listBefore = VirtualMachine.list();
while (true) {
try {
listAfter = VirtualMachine.list();
if (listAfter.size() <= 0)
continue;
for (VirtualMachineDescriptor vmd : listAfter) {
vm = VirtualMachine.attach(vmd);
listBefore.add(vmd);
System.out.println("i find a vm,agent.jar was injected.");
Thread.sleep(1000);
if (null != vm) {
vm.loadAgent("d:/agent.jar");
vm.detach();
}
}
break;
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
打包成AgentStarter.jar
效果图在文章开始
补充:
java11会显示Can not attach to current VM
是因为在Java9及以后的版本中,默认不允许SelfAttach:也就是说,系统提供了一个jdk.attach.allowAttachSelf的VM参数,这个参数默认为false,且必须在Java启动时指定才生效。
参考链接:
https://www.cnblogs.com/rebeyond/p/9686213.html