javaagent笔记及一个基于javassit的应用监控程序demo

javaagent基本用法

  1. 定义入口premain

    public static void premain(String agentArgs, Instrumentation inst) {
        System.out.println("Hello, world! JavaAgent");
        System.out.println("agentArgs: " + agentArgs);
    
        inst.addTransformer(new APMAentV1());
    }
    
  2. 设置

    <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>
    
  3. 定义转换器对class进行处理

    public class APMAentV1 implements ClassFileTransformer {
        @Override
        public byte[] transform(ClassLoader classLoader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
            ...
        }
    }
    
  4. 给目标程序的启动增加-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使用步骤:

  1. 构造ClassPool对象;

  2. 插入类查找路径:insertClassPath();

  3. 获取CTclass;

    1. 构建新类makeClass;
    2. get加载已有类(找不到会报NotFound异常);
  4. 修改:

    1. 修改方法,如addMethod;
    2. 修改字段,如addField;
    3. 等等;
  5. 生成类并装载class:

    1. toClass;
    2. toByteCode;
  6. 例子

    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的完整简单例子

  1. 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());
        }
    }
    
    
  2. 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);
            }
        }
    }
    
  3. 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();
    }
    }
    
  4. 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);
    
    }
    
    
  5. 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);
        }
    }
    
    
  6. 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();
        }
    }
    
    
  7. maven依赖

    <dependency>
        <groupId>javassist</groupId>
        <artifactId>javassist</artifactId>
        <version>3.12.1.GA</version>
    </dependency>
    
posted @ 2018-08-05 23:51  liqipeng  阅读(912)  评论(0编辑  收藏  举报