JavaAgent简单学习

1、Java Agent

  相当于main方法之前的一个拦截器

       本身是Java命令的一个参数,后面跟一个Jar包

       对Jar包的要求

    在META-INF目录下的MANIFEST.MF文件中必须指定premain-class配置项

    premain-class配置项指定的类必须提供premain方法

  针对Jar包的要求,我们可以使用maven插件来完成 maven-assembly-plugin

1.1 通过Java Agent调整加载的类信息

  创建TestClass,修改返回值,生成新的class文件存放到其他目录,之后就是将原来的class信息,修改成其他目录下的class文件信息

package com.fh;

public class TestClass {

    public int getNumber(){
        return 1;
    }
}
View Code

进行方法调用  启动类需要添加指定的参数

package com.fh;

/**
 * -javaagent:E:\idea_workspace\workspace_skywalking\TestAgent\target\TestAgent-1.0-SNAPSHOT.jar
 */
public class Main {

    public static void main(String[] args) throws InterruptedException {
        System.out.println(new TestClass().getNumber());
    }
}
View Code

创建一个新的项目作为agent的jar来使用

package com.fh;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.security.ProtectionDomain;

/**
 * 存在方法重载,如果两个都存在,一般会执行第一个
 * sun.instrument.InstrumentationImpl.java
 */
public class TestAgent1 {


    /**
     *
     * @param agentArgs -javaagent命令携带的参数
     * @param inst 提供了操作类定义的相关方法
     */
    public static void premain(String agentArgs, Instrumentation inst) throws UnmodifiableClassException {
//      注册/注销一个ClassFileTransformer类的实例,该Transformer实例会在类加载的时候被调用
//      可用于修改类定义
//      inst.addTransformer();
//      inst.removeTransformer();

//      针对的是已经加载的类,他会对已经传入的类进行重新定义
//      inst.redefineClasses();
//      返回虚拟机已经加载的所有类
//      inst.getAllLoadedClasses()
//      返回当前虚拟机已经初始化的类
//      inst.getInitiatedClasses()
//      获取参数指定的对象的大小
//      inst.getObjectSize()


//        System.out.println("this is a Java agent with two args");
//        System.out.println("参数:"+agentArgs);

        inst.addTransformer(new Transformer(),true);
        inst.retransformClasses(TestClass.class);
        System.out.println("premain done");
    }

    public static void premain(String agentArgs){
        System.out.println("this is a Java agent with only one args");
        System.out.println("参数:"+agentArgs);
    }

    static class Transformer implements ClassFileTransformer{

        @Override
        public byte[] transform(ClassLoader loader, String className, Class<?> c, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
            if(!c.getSimpleName().equals("TestClass")){
                return null;
            }
            return getBytesFromFile("E:\\idea_workspace\\workspace_skywalking\\agent-demo\\TestClass.class");
        }

        public byte[] getBytesFromFile(String filename){
            try {
                File file = new File(filename);
                InputStream is = new FileInputStream(file);
                long length = file.length();
                byte[] bytes = new byte[(int) length];

                //read the bytes
                int offset = 0;
                int numRead = 0;
                while (offset < bytes.length &&
                        (numRead = is.read(bytes,offset,bytes.length-offset))>=0){
                    offset +=numRead;
                }
                if(offset < bytes.length){
                    throw new Exception("Could not completely read file");
                }
                is.close();
                return bytes;
            }catch (Exception e){
                e.printStackTrace();
                return null;
            }

        }
    }
}
View Code

pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.fh</groupId>
    <artifactId>TestAgent</artifactId>
    <version>1.0-SNAPSHOT</version>


    <dependencies>
        <dependency>
            <groupId>com.fh</groupId>
            <artifactId>agent-demo</artifactId>
            <version>1.0-SNAPSHOT</version>
            <scope>compile</scope>
        </dependency>
        <!--统计方法耗时-->
        <dependency>
            <groupId>net.bytebuddy</groupId>
            <artifactId>byte-buddy</artifactId>
            <version>1.10.19</version>
        </dependency>
        <dependency>
            <groupId>net.bytebuddy</groupId>
            <artifactId>byte-buddy-agent</artifactId>
            <version>1.10.19</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>2.4</version>
                <configuration>
                    <appendAssemblyId>false</appendAssemblyId>
                    <!--将TestAgent的所有依赖包都打到jar包中-->
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                    <archive>
                        <manifest>
                            <!-- 添加MANIFEST.MF中的各项配置-->
                            <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
                            <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
                        </manifest>
                        <!-- 将 premain-class 配置项设置为com.xxx.TestAgent-->
                        <manifestEntries>
                            <Can-Retransform-Classes>true</Can-Retransform-Classes>
                            <Premain-Class>com.fh.TestAgent</Premain-Class>
 
                        </manifestEntries>
                    </archive>
                </configuration>
                <executions>
                    <execution>
                        <!-- 绑定到package生命周期阶段上 -->
                        <phase>package</phase>
                        <!-- 绑定到package生命周期阶段上 -->
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
View Code

1.2 通过byte-buddy计算方法的耗时   byte-buddy是一个开源的Java库,可以帮助用户屏蔽字节码操作

package com.fh;

import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.matcher.ElementMatchers;

import java.lang.instrument.Instrumentation;

public class TestAgent2 {

    public static void premain(String agentArgs,
                               Instrumentation inst){
        // Byte Buddy会根据 Transformer指定的规则进行拦截并增强代码
        AgentBuilder.Transformer transformer = (builder, typeDescription, classLoader, javaModule) -> {
            // method()指定哪些方法需要被拦截,ElementMatchers.any()表
            return builder.method(ElementMatchers.<MethodDescription>any())
                    .intercept(MethodDelegation.to(TimeInterceptor.class));// intercept()指明拦截上述方法的拦截器
        };
        new AgentBuilder
                .Default()
                //根据包名前缀拦截类
                .type(ElementMatchers.nameStartsWith("com.fh"))
                .transform(transformer)//拦截到的类由transformer来处理
                .installOn(inst);//安装到Instrumentation
    }
}
View Code
package com.fh;

import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.SuperCall;

import java.lang.reflect.Method;
import java.util.concurrent.Callable;

public class TimeInterceptor {

    /**
     *
     * @param method 被拦截方法的Method对象
     * @param callable 可以调用到被拦截的目标方法,即使目标方法带参数,也不需要显示传递
     * @return
     * @throws Exception
     */
    @RuntimeType
    public static Object intercept(@Origin Method method,
                                   @SuperCall Callable<?> callable) throws Exception {
        long l = System.currentTimeMillis();
        try {
            return callable.call();
        }finally {
            System.out.println(method.getName()+":"+
                    (System.currentTimeMillis()-l)+"ms");
        }
    }
}
View Code

 

pom文件需要引入的信息,已经在上面的pom文件中添加,修改premain-class信息即可

还需要添加Can-Retransform-Classes标签为true

2、Attach API

  为了更好的灵活性,在Java6之后提供的,可以在main方法之后后执行agentmain方法添加一下特殊的功能

添加要被监听的方法main   不需要添加javaagent参数

package com.fh;

/**
 * -javaagent:E:\idea_workspace\workspace_skywalking\TestAgent\target\TestAgent-1.0-SNAPSHOT.jar
 */
public class Main {

    public static void main(String[] args) throws InterruptedException {
        System.out.println(new TestClass().getNumber());
        while (true){
            Thread.sleep(1000);
            System.out.println(new TestClass().getNumber());
        }
    }
}
View Code

添加加载jar包附着在虚拟机上的程序

package com.fh;

import com.sun.tools.attach.*;

import java.io.IOException;
import java.util.List;

public class AttachMain {

    public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException, InterruptedException {
        List<VirtualMachineDescriptor> listBefore = VirtualMachine.list();
        //agentmain()方法所在jar包
        String jar = "E:\\idea_workspace\\workspace_skywalking\\TestAgent\\target\\TestAgent-1.0-SNAPSHOT.jar";
        VirtualMachine virtualMachine = null;

        List<VirtualMachineDescriptor> listAfter = null;
        while (true){
            listAfter = VirtualMachine.list();
            for (VirtualMachineDescriptor descriptor : listAfter) {
                if(!listBefore.contains(descriptor)){//发现新的JVM
                    System.out.println("attach JVM");
                    virtualMachine = VirtualMachine.attach(descriptor);//attach到新JVM
                    virtualMachine.loadAgent(jar);//加载agentmain所在的jar包
                    virtualMachine.detach();
                    return;
                }
            }
            Thread.sleep(1000);
        }

    }
}
View Code

jar包内容

package com.fh;

import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;

/**
 * attach api
 */
public class TestAgent {

    public static void agentmain(String agentArgs,
                                 Instrumentation inst) throws UnmodifiableClassException {
//     是对一个Java虚拟机的抽象,在Attach工具程序监控目标虚拟机的时候会用到此类
//     提供类JVM枚举,Attach,Detach等基本操作
//     VirtualMachine
//     描述虚拟机的容器类
//     VirtualMachineDescriptor
        inst.addTransformer(new TestAgent1.Transformer(),true);
        inst.retransformClasses(TestClass.class);
        System.out.println("premain done");
    }

    public static void agentmain(String agentArgs){

    }

}
View Code

 

pom文件中的Premain-class替换成Agent-class,还需要添加Can-Retransform-Classes标签为true

之后进行进行启动,可以先启动attach程序,然后启动main方法,attachMain会检测运行中的虚拟机,然后将jar包附着着新的虚拟机上

 

  

posted @ 2021-05-23 16:51  默默行走  阅读(423)  评论(1编辑  收藏  举报