javaagent笔记及一个基于javassit的应用监控程序demo
javaagent基本用法
-
定义入口premain
public static void premain(String agentArgs, Instrumentation inst) { System.out.println("Hello, world! JavaAgent"); System.out.println("agentArgs: " + agentArgs); inst.addTransformer(new APMAentV1()); }
-
设置
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>2.6</version> <configuration> <archive> <manifestEntries> <Premain-Class>com.liq.app2.APMAgentV1</Premain-Class> </manifestEntries> </archive> </configuration> </plugin> </plugins> </build>
-
定义转换器对class进行处理
public class APMAentV1 implements ClassFileTransformer { @Override public byte[] transform(ClassLoader classLoader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { ... } }
-
给目标程序的启动增加-javaagent参数
-javaagent:/home/xxx/code/app2-1.0-SNAPSHOT.jar=javaagentArg1,javaagentArg2 # 后面的javaagentArg1,javaagentArg2会传入premain的agentArgs
这样,目标程序就会加载这个javaagent了
Javassit
javassit是一个开源的分析、编辑和创建Java字节码的工具。
优点:简单、快速,不需要了解虚拟机指令。
类似的工具还有ASM。
比较而言,javassit性能没有ASM高,所以一般可以使用javaassit实现功能之后,再基于ASM去优化性能。
javaassit使用步骤:
-
构造ClassPool对象;
-
插入类查找路径:insertClassPath();
-
获取CTclass;
- 构建新类makeClass;
- get加载已有类(找不到会报NotFound异常);
-
修改:
- 修改方法,如addMethod;
- 修改字段,如addField;
- 等等;
-
生成类并装载class:
- toClass;
- toByteCode;
-
例子
import javassist.*; public class JavaSsistDemo1 { public static void main(String[] args) throws NotFoundException, CannotCompileException, IllegalAccessException, InstantiationException { ClassPool pool = new ClassPool(true); pool.insertClassPath(new LoaderClassPath(JavaSsistDemo1.class.getClassLoader())); CtClass targetClass = pool.makeClass("com.liq.Hello"); targetClass.addInterface(pool.get(IHello.class.getName())); String methodName = "sayHello"; CtClass returnType = pool.get(void.class.getName()); CtClass[] parameters = new CtClass[]{pool.get(String.class.getName())}; CtMethod method = new CtMethod(returnType, methodName, parameters, targetClass); String src = "{" + "System.out.println(\"Hello \" + $1);" + "}"; method.setBody(src); targetClass.addMethod(method); Class cls = targetClass.toClass(); IHello hello = (IHello) cls.newInstance(); hello.sayHello("Tom"); } public interface IHello { void sayHello(String name); } }
基于Javassit的一个简单应用程序监控的javaagent的完整简单例子
-
APMAgentV1.java
package com.liq.app2; import com.liq.javaagent.collector.*; import javassist.ClassPool; import javassist.CtClass; import javassist.LoaderClassPath; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.lang.instrument.Instrumentation; import java.security.ProtectionDomain; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; public class APMAgentV1 implements ClassFileTransformer { private static Collector[] collectors; static { collectors = new Collector[]{OtherCollector.INSTANCE}; } private Map<ClassLoader, ClassPool> classPoolMap = new ConcurrentHashMap<>(); @Override public byte[] transform(ClassLoader classLoader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { if ((className == null) || (classLoader == null) || (classLoader.getClass().getName().equals("sun.reflect.DelegatingClassLoader")) || (classLoader.getClass().getName().equals("org.apache.catalina.loader.StandardClassLoader")) || (classLoader.getClass().getName().equals("javax.management.remote.rmi.NoCallStackClassLoader")) || (classLoader.getClass().getName().equals("com.alibaba.fastjson.util.ASMClassLoader")) || (className.indexOf("$Proxy") != -1) || (className.startsWith("java"))) { return null; } // 不同的ClassLoader使用不同的ClassPool ClassPool localClassPool; if (!this.classPoolMap.containsKey(classLoader)) { localClassPool = new ClassPool(); localClassPool.insertClassPath(new LoaderClassPath(classLoader)); this.classPoolMap.put(classLoader, localClassPool); } else { localClassPool = this.classPoolMap.get(classLoader); } try { className = className.replaceAll("/", "."); CtClass localCtClass = localClassPool.get(className); for (Collector collector : collectors) { if (collector.isTarget(className, classLoader, localCtClass)) { byte[] arrayOfByte = collector.transform(classLoader, className, classfileBuffer, localCtClass); System.out.println(String.format("%s APM agent insert success", new Object[]{className})); return arrayOfByte; } } } catch (Throwable localThrowable) { new Exception(String.format("%s APM agent insert fail", new Object[]{className}), localThrowable).printStackTrace(); } return new byte[0]; } public static void premain(String agentArgs, Instrumentation inst) { System.out.println("Hello, world! JavaAgen"); System.out.println("agentArgs: " + agentArgs); inst.addTransformer(new APMAgentV1()); } }
-
ClassWrapper.java
用于向目标类插入一些监控代码package com.liq.app2; import javassist.CtMethod; import javassist.NotFoundException; public class ClassWrapper { private String beginSrc; private String endSrc; private String errorSrc; public ClassWrapper beginSrc(String paramString) { this.beginSrc = paramString; return this; } public ClassWrapper endSrc(String paramString) { this.endSrc = paramString; return this; } public ClassWrapper errorSrc(String paramString) { this.errorSrc = paramString; return this; } public String beginSrc(CtMethod ctMethod) { try { String template = ctMethod.getReturnType().getName().equals("void") ? "{\n" + " %s \n" + " try {\n" + " %s$agent($$);\n" + " } catch (Throwable e) {\n" + " %s\n" + " throw e;\n" + " }finally{\n" + " %s\n" + " }\n" + "}" : "{\n" + " %s \n" + " Object result=null;\n" + " try {\n" + " result=($w)%s$agent($$);\n" + " } catch (Throwable e) {\n" + " %s \n" + " throw e;\n" + " }finally{\n" + " %s \n" + " }\n" + " return ($r) result;\n" + "}"; String insertBeginSrc = this.beginSrc == null ? "" : this.beginSrc; String insertErrorSrc = this.errorSrc == null ? "" : this.errorSrc; String insertEndSrc = this.endSrc == null ? "" : this.endSrc; String result = String.format(template, new Object[]{insertBeginSrc, ctMethod.getName(), insertErrorSrc, insertEndSrc}); return result; } catch (NotFoundException localNotFoundException) { throw new RuntimeException(localNotFoundException); } } }
-
ClassReplacer.java
用于使用包装过的类替换目标类package com.liq.app2; import javassist.CannotCompileException; import javassist.CtClass; import javassist.CtMethod; import javassist.CtNewMethod; import java.io.IOException; public class ClassReplacer { private final String className; private final ClassLoader classLoader; private final CtClass ctClass; public ClassReplacer(String className, ClassLoader classLoader, CtClass ctClass) { this.className = className; this.classLoader = classLoader; this.ctClass = ctClass; } public void replace(CtMethod ctMethod, ClassWrapper paramd) throws CannotCompileException { String methodName = ctMethod.getName(); CtMethod localCtMethod2 = CtNewMethod.copy(ctMethod, methodName, this.ctClass, null); localCtMethod2.setName(methodName + "$agent"); this.ctClass.addMethod(localCtMethod2); ctMethod.setBody(paramd.beginSrc(ctMethod)); } public byte[] replace() throws IOException, CannotCompileException { return this.ctClass.toBytecode(); } }
-
Collector.java
监控信息搜集器接口package com.liq.app2.collector; import javassist.CtClass; public interface Collector { boolean isTarget(String className, ClassLoader classLoader, CtClass ctClass); byte[] transform(ClassLoader classLoader, String className, byte[] classfileBuffer, CtClass ctClass); }
-
OtherCollector.java
监控信息搜集器实现,这里只是简单例子,所以写死了目标类名字“com.liq.service.UserService”package com.liq.app2.collector; import com.liq.app2.ClassReplacer; import com.liq.app2.ClassWrapper; import com.liq.app2.stat.Statistics; import javassist.*; public class OtherCollector implements Collector { public static final OtherCollector INSTANCE = new OtherCollector(); private OtherCollector() { } private static final String beginSrc; private static final String endSrc = "inst.end(statistic);"; private static final String errorSrc; static { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("com.liq.app2.collector.OtherCollector inst=com.liq.app2.collector.OtherCollector.INSTANCE;"); stringBuilder.append("com.liq.app2.stat.Statistics statistic = inst.start(\"%s\");"); beginSrc = stringBuilder.toString(); errorSrc = "inst.error(statistic,e);"; } @Override public boolean isTarget(String className, ClassLoader classLoader, CtClass ctClass) { return "com.liq.service.UserService".equals(className); } @Override public byte[] transform(ClassLoader classLoader, String className, byte[] classfileBuffer, CtClass ctClass) { try { ClassReplacer replacer = new ClassReplacer(className, classLoader, ctClass); for (CtMethod ctMethod : ctClass.getDeclaredMethods()) { String str; if ((Modifier.isPublic(ctMethod.getModifiers())) && (!Modifier.isStatic(ctMethod.getModifiers()) && (!Modifier.isNative(ctMethod.getModifiers())))) { ClassWrapper classWrapper = new ClassWrapper(); classWrapper.beginSrc(String.format(beginSrc, ctMethod.getLongName())); classWrapper.endSrc(endSrc); classWrapper.errorSrc(errorSrc); replacer.replace(ctMethod, classWrapper); } } return replacer.replace(); } catch (Exception e) { e.printStackTrace(); } return new byte[0]; } public Statistics start(String methodSign) { return new Statistics(methodSign); } public void end(Statistics statistics) { statistics.end(); } public void error(Statistics statistics, Throwable e) { statistics.error(e); } }
-
Statistics
package com.liq.app2.stat; public class Statistics { private String method; private long startTime; private long endTime; private Throwable error; public Statistics(String method) { this.method = method; this.startTime = System.currentTimeMillis(); } public void end() { endTime = System.currentTimeMillis(); System.out.println(this.toString()); } public void error(Throwable e) { error = e; } @Override public String toString() { final StringBuilder sb = new StringBuilder("Statistics{"); sb.append("method='").append(method).append('\''); sb.append(", startTime=").append(startTime); sb.append(", endTime=").append(endTime); sb.append(", error=").append(error); sb.append('}'); return sb.toString(); } }
-
maven依赖
<dependency> <groupId>javassist</groupId> <artifactId>javassist</artifactId> <version>3.12.1.GA</version> </dependency>
如果你觉得这篇文章对你有帮助或者使你有所启发,请点击右下角的推荐按钮,谢谢,:)