一、使用javaagent参数启动java程序

使用方法:

1、agent类需要实现特定方法,实现

public static void premain(String args, Instrumentation instrumentation) 或者 public static void premain(String args)

(优先两个参数的方法,找不到则执行一个参数的方法)

2、jar包里的MF文件需要指定Premain-Class为agent类(Can-Redefine-Classes和Can-Retransform-Classes通常也指定为true)

3、将agent类和MF文件打成jar包

4、启动java程序时使用-javaagent参数指定代理jar包(java程序可以用java直接启动主类class也可以-jar启动jar包,-javaagent位置放-jar前面,多个javaagent按序执行)

Demo:

Hello.java、hello.MF是用来测试的java程序,hello.bat脚本用来编译打包;

MyAgent.java、myagent.MF、myagent.bat用来编译打包代理类的;

agentcls文件夹用来放置agent类的class文件并用作打包目录,并且提前将javassist.jar解压进去(我的agent类使用了javassist修改类内容,并且我想打成一个jar包);

 

Hello.java代码:

public class Hello {

    public static void main(String[] args) {
        System.out.println("HELLO");
        Count c = new Count();
        System.out.println(c.count(1));
    }

}
class Count{
    public int count(int x){
        return x + 1;
    };
};

hello.MF文件:

Manifest-Version: 1.0
Created-By: FlyLikeButterfly
Main-Class: Hello

hello.bat脚本:

javac Hello.java
jar cvfm hello.jar hello.MF Hello.class Count.class
del Hello.class
del Count.class
pause

 

MyAgent.java代码:

package test;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;


public class MyAgent {

	public static void premain(String args, Instrumentation instrumentation) {
		ClassFileTransformer mytransformer = new ClassFileTransformer() {
			@Override
			public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
					ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
				System.out.println("className:" + className);
				if("Count".equals(className)) {
					System.out.println("==find class.");
					ClassPool pool = ClassPool.getDefault();
					InputStream is = new ByteArrayInputStream(classfileBuffer);
					try {
						CtClass cls = pool.makeClass(is);
						CtMethod m = cls.getDeclaredMethod("count");
						m.setBody("{return $1 + 10;}");
						return cls.toBytecode();
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
				return classfileBuffer;
			}
		};
		instrumentation.addTransformer(mytransformer);
	}
	
	public static void premain(String args) {
		System.out.println("premain.");
	}
	
}

myagent.MF文件:

Manifest-Version: 1.0
Created-By: FlyLikeButterfly
Premain-Class: test.MyAgent
Can-Redefine-Classes: true
Can-Retransform-Classes: true

myagent.bat脚本:

javac -cp agentcls -d agentcls MyAgent.java
jar cvfm myagent.jar myagent.MF -C agentcls .
pause

 

执行测试:

先生成测试需要的jar包:执行hello.bat生成hello.jar,执行myagent.bat生成myagent.jar;

先测试hello.jar包,再加入javaagent测试:

java -jar hello.jar

java -javaagent:myagent.jar -jar hello.jar

执行结果:

 

二、使用attach机制让运行中的java程序加载agent

使用方法:

1、agent需要实现特定方法,实现

public static void agentmain(String args, Instrumentation instrumentation) 或者 public static void agentmain(String args)

(跟premain一样优先两个参数的)

2、MF文件指定Agent-Class参数(Can-Redefine-Classes和Can-Retransform-Classes)

3、将agent类和MF打包

4、正常启动java测试程序

5、使用一个带main方法的java程序attach到java测试程序vm的id,将agent.jar加载到vm里

 

Demo:

hello.bat、Hello.java、hello.MF用来生成测试jar;

myagent.bat、MyAgent.java、myagent.MF和agentcls文件夹用来生成agent的jar;

startAttach.bat、StartAttach.jar用来将agent.jar加载到目标vm里;

 

Hello.java代码:

package test;

public class Hello {

    public static void main(String[] args) {
        System.out.println("HELLO");
        Count c = new Count();
		while(true){
			System.out.println(c.count(1));
			try {
				Thread.sleep(3000);
			}catch(Exception e) {
			}
		}
    }

}
class Count{
    public int count(int x){
        return x + 1;
    };
};

hello.MF文件:

Manifest-Version: 1.0
Created-By: FlyLikeButterfly
Main-Class: test.Hello

hello.bat脚本:

javac -d hellocls Hello.java
jar cvfm hello.jar hello.MF -C hellocls .
pause

 

MyAgent.java代码:

package test;
import java.lang.instrument.ClassDefinition;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Method;

import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;


public class MyAgent {

	public static void agentmain(String args, Instrumentation instrumentation) {
		Class<?>[] clses = instrumentation.getAllLoadedClasses();
		for(Class<?> cls : clses) {
			if(cls.getName().equals("test.Count")) {
				System.out.println("----" + cls.getName());
				for(Method m : cls.getMethods()) {
					System.out.println("-" + m);
				}
				System.out.println("----");
				
				try {
					ClassPool pool = ClassPool.getDefault();
					ClassClassPath classPath = new ClassClassPath(cls);
					pool.insertClassPath(classPath);
					CtClass ccls = pool.get("test.Count");
					System.out.println(ccls);
					CtMethod cm = ccls.getDeclaredMethod("count");
					System.out.println(cm);
					cm.setBody("{return $1 * 10;}");
					ClassDefinition definitions = new ClassDefinition(cls, ccls.toBytecode());
					instrumentation.redefineClasses(definitions);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
		
	}
	
	public static void agentmain(String args) {
		System.out.println("agentmain.");
	}
	
}

myagent.MF文件:

Manifest-Version: 1.0
Created-By: FlyLikeButterfly
Agent-Class: test.MyAgent
Can-Redefine-Classes: true
Can-Retransform-Classes: true

myagent.bat脚本:

javac -cp agentcls -d agentcls MyAgent.java
jar cvfm myagent.jar myagent.MF -C agentcls .
pause

 

StartAttach.java代码:

import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.VirtualMachineDescriptor;

public class StartAttach {
	
	public static void main(String[] args) {
		for(VirtualMachineDescriptor v : VirtualMachine.list()) {
			System.out.println(v.id() + ":" + v.displayName());
			if(v.displayName().contains("hello")) {
				try {
					System.out.println("find hello VM and attach. VM_id=" + v.id());
					VirtualMachine vm = VirtualMachine.attach(v.id());
					vm.loadAgent("myagent.jar");
					vm.startLocalManagementAgent();
					vm.detach();
					System.out.println("detached VM.");
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
	}
	
}

startAttach.bat脚本:

javac StartAttach.java
java StartAttach
pause

 

执行测试:

先执行hello.bat和myagent.bat生成测试jar包和代理jar包

先正常执行测试jar包:

java -jar hello.jar

然后执行startAttach.bat脚本将代理jar加载到测试vm里:

 

另外,执行attach的java程序可以跟agent合成到一个jar里:

将main方法挪到myagent.java里,并且修改myagent.MF文件添加Main-Class

MyAgent.java代码:

package test;
import java.lang.instrument.ClassDefinition;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Method;

import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;

import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.VirtualMachineDescriptor;

public class MyAgent {

	public static void main(String[] args) {
		for(VirtualMachineDescriptor v : VirtualMachine.list()) {
			System.out.println(v.id() + ":" + v.displayName());
			if(v.displayName().contains("hello")) {
				try {
					System.out.println("find hello VM and attach. VM_id=" + v.id());
					VirtualMachine vm = VirtualMachine.attach(v.id());
					vm.loadAgent("myagent.jar");
					vm.startLocalManagementAgent();
					vm.detach();
					System.out.println("detached VM.");
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
	}

	public static void agentmain(String args, Instrumentation instrumentation) {
		Class<?>[] clses = instrumentation.getAllLoadedClasses();
		for(Class<?> cls : clses) {
			if(cls.getName().equals("test.Count")) {
				System.out.println("----" + cls.getName());
				for(Method m : cls.getMethods()) {
					System.out.println("-" + m);
				}
				System.out.println("----");
				
				try {
					ClassPool pool = ClassPool.getDefault();
					ClassClassPath classPath = new ClassClassPath(cls);
					pool.insertClassPath(classPath);
					CtClass ccls = pool.get("test.Count");
					System.out.println(ccls);
					CtMethod cm = ccls.getDeclaredMethod("count");
					System.out.println(cm);
					cm.setBody("{return $1 * 10;}");
					ClassDefinition definitions = new ClassDefinition(cls, ccls.toBytecode());
					instrumentation.redefineClasses(definitions);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
		
	}
	
	public static void agentmain(String args) {
		System.out.println("agentmain.");
	}
	
}

myagnet.MF文件:

Manifest-Version: 1.0
Created-By: FlyLikeButterfly
Main-Class: test.MyAgent
Agent-Class: test.MyAgent
Can-Redefine-Classes: true
Can-Retransform-Classes: true

执行hello.bat和myagent.bat生成jar包

命令行分别执行测试jar和代理jar:

 

修改类最好使用原来加载的类修改,不然很容易出现异常(修饰词不一致,方法名不一致,参数返回值不一致等等)

测试使用attach的时候javassist找不到Object,加了pool.insertClassPath(classPath)解决;

 

 

 

 

 

 

 

参考:https://www.cnblogs.com/rickiyang/p/11368932.html

https://blog.csdn.net/sun_tantan/article/details/105330517

https://segmentfault.com/a/1190000022359295

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐