JavaAgent的agent与attach

简介

javaagent是什么?

从名字agent也可以看出,是一种代理。

javaagent用来做什么?

本质上是对class的一种增强,用来实现一些通用功能,例如链路追踪等。

和AOP有什么区别?

AOP和javaagent本质上都是通过修改class来实现额外功能,对代码逻辑本身无侵入,在运行时侵入。

AOP通常是项目内的代理增强,通常是增强业务逻辑,例如:公用授权检查逻辑。
javaagent是项目外独立的增强项目,通常是非业务逻辑,例如:arthas相关功能、debug、线上运行参数、返回值等数据临时打印等。

javaagent可能我们基本不会用,但是我们最好理解其原理,知道它能做什么,这样我们可以更好理解jacoco、arthas这些工具的原理。

可以丰富工具箱,在我们自己要做项目的时候,也有更多的工具可供选择。

本文重点介绍流程,具体的逻辑本质上还是对字节码的操作,可以看asm、javaassist、cglib、bytebuddy等字节码操作工具。

agent与attach

agent是在启动的时候就指定,在类加载到jvm之前就完成了类的增强,如jacoco

attach可以对已经启动项目,已经加载到jvm中的类进行增强,例如arthas。(这点非常有用,可以不用重启项目,甚至可以做热更新)

agent

首先,我们准备一个需要增强的类,简单点:

import java.util.Random;
import java.util.concurrent.TimeUnit;

public class Start {

    public static final Random random = new Random();

    public static void main(String[] args) throws InterruptedException {
        System.out.println("Start开始执行...");
        while (true) {
            doBusiness();
        }
    }

    public static void doBusiness() throws InterruptedException {
        System.out.println("doBusiness 执行开始");
        int time = random.nextInt(10) + 1;
        TimeUnit.SECONDS.sleep(time);
    }
}

就一个doBusiness业务方法,用来增强。

agent需要一个静态的premain方法,方法签名如下:

public static void premain(String arg, Instrumentation instrumentation)

这个方法在哪个类中不重要,重要的是方法签名要一样。

import vip.meet.transformer.LogTransformer;

import java.lang.instrument.Instrumentation;

public class PreMain {

    public static void premain(String arg, Instrumentation instrumentation) {
        System.out.println("执行premain 方法");
        System.out.println("执行premain参数:" + arg);
        instrumentation.addTransformer(new LogTransformer());
    }

}

LogTransformer类使用了javaassist,依赖看后面的pom文件

transform方法是在jvm加载类之前执行。

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

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;

public class LogTransformer implements ClassFileTransformer {

    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        String realClassName = className.replaceAll("/", ".");
        if (realClassName.equals("vip.meet.Start")) {
            CtClass ctClass;
            try {
                ClassPool classPool = ClassPool.getDefault();
                ctClass = classPool.get(realClassName);
                CtMethod ctMethod = ctClass.getDeclaredMethod("doBusiness");
                ctMethod.addLocalVariable("inject_start", CtClass.longType);
                ctMethod.insertBefore("System.out.println(\"---doBusiness agent 开始执行---\");");
                ctMethod.insertBefore("inject_start = System.currentTimeMillis();");
                ctMethod.insertAfter("System.out.println(\"---doBusiness agent 结束执行---\");");
                ctMethod.insertAfter("System.out.println(\"运行耗时: \" + (System.currentTimeMillis() - inject_start));");
                return ctClass.toBytecode();
            } catch (Throwable e) {
                System.out.println(e.getMessage());
                e.printStackTrace();
            }
        }
        return classfileBuffer;
    }
}

javaagent如何知道代理类入口呢?

答案是MANIFEST.MF的Premain-Class:

Premain-Class: vip.meet.agent.PreMain

可以在maven-jar-plugin、maven-assembly-plugin插件中配置,参考后面pom文件配置。

mvn clean package
java -javaagent:agent-learn-1.0.0-jar-with-dependencies.jar=hello,abc=123 -jar agent-learn-1.0.0-jar-with-dependencies.jar

agent-run

attach

attach的和agent非常相似,只是入口不一样。

attach方式的入口方法签名如下:

public static void agentmain(String arg, Instrumentation instrumentation)

attach通常就不使用ClassFileTransformer,因为这个是jvm加载类之前的调用。

而attach的时候,jvm已经启动了。

所以,我们需要获取已经加载的类:

Class<?>[] classes = instrumentation.getAllLoadedClasses();

修改类之后,再redefineClasses:

ClassDefinition classDefinition = new ClassDefinition(cls, ctClass.toBytecode());
instrumentation.redefineClasses(classDefinition);
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;

import java.lang.instrument.ClassDefinition;
import java.lang.instrument.Instrumentation;

public class AgentMain {

    public static void agentmain(String arg, Instrumentation instrumentation) {
        System.out.println("agentmain启动");
        System.out.println("agentmain参数:" + arg);
        Class<?>[] classes = instrumentation.getAllLoadedClasses();
        for (Class<?> cls : classes) {
            String name = cls.getName();
            if (name.equals("vip.meet.Start")) {
                CtClass ctClass;
                try {
                    ClassPool classPool = ClassPool.getDefault();
                    ctClass = classPool.get(name);
                    CtMethod ctMethod = ctClass.getDeclaredMethod("doBusiness");
                    ctMethod.addLocalVariable("inject_start", CtClass.longType);
                    ctMethod.insertBefore("System.out.println(\"---doBusiness agent 开始执行---\");");
                    ctMethod.insertBefore("inject_start = System.currentTimeMillis();");
                    ctMethod.insertAfter("System.out.println(\"---doBusiness agent 结束执行---\");");
                    ctMethod.insertAfter("System.out.println(\"运行耗时: \" + (System.currentTimeMillis() - inject_start));");
                    ClassDefinition classDefinition = new ClassDefinition(cls, ctClass.toBytecode());
                    instrumentation.redefineClasses(classDefinition);
                } catch (Throwable e) {
                    System.out.println(e.getMessage());
                    e.printStackTrace();
                }
            }
        }
    }

}

如何配置attach入口呢?

答案是MANIFEST.MF的Agent-Class:

Agent-Class: vip.meet.attach.AgentMain

如何attach

现在,我们有attach了,如何attach到已经运行的jvm进程上呢?

可以通过VirtualMachine来实现,注意VirtualMachine是sun的私有实现接口,依赖tools.jar

import com.sun.tools.attach.AgentInitializationException;
import com.sun.tools.attach.AgentLoadException;
import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.VirtualMachineDescriptor;

import java.io.IOException;
import java.util.concurrent.TimeUnit;


public class AttachUseMain {

    public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException, InterruptedException {
        System.out.println("AttachUseMain启动");
        for (VirtualMachineDescriptor vmd : VirtualMachine.list()) {
            String name = vmd.displayName();
            System.out.println(name);
            if (name.equals("agent-learn-1.0.0-jar-with-dependencies.jar")) {
                VirtualMachine vm = VirtualMachine.attach(vmd.id());
                vm.loadAgent("E:\\app\\me\\learn\\agent-learn\\target\\agent-learn-1.0.0-jar-with-dependencies.jar=hello,ok,aa,bb,cc=3");
                TimeUnit.MINUTES.sleep(1);
                vm.detach();
            }
        }
    }
}

上面的代码,首先列出所有jvm进程,然后匹配到需要attach的pid,然后执行attach。

首先运行,需要被代理的项目:

java -jar agent-learn-1.0.0-jar-with-dependencies.jar

然后启动AttachUseMain:

attach

注意:项目运行的java的版本和AttachUseMain一样,否则会出错:Non-numeric value found - int expected

动态attach

其实jstack、jmap都是通过attach方式实现,不过没有使用jar包,而是通过JVMTI的其他接口实现。

pom.xml

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>vip.meet</groupId>
    <artifactId>agent-learn</artifactId>
    <version>1.0.0</version>
    <name>agent-learn</name>
    <description>agent-learn</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.28.0-GA</version>
        </dependency>
        <dependency>
            <groupId>com.sun</groupId>
            <artifactId>tools</artifactId>
            <version>1.8</version>
            <scope>system</scope>
            <systemPath>D:/Env/JDK/Java8/lib/tools.jar</systemPath>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.12.1</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>3.6.0</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                        <configuration>
                            <archive>
                                <manifest>
                                    <mainClass>vip.meet.Start</mainClass>
                                </manifest>
                                <manifestEntries>
                                    <Menifest-Version>1.0</Menifest-Version>
                                    <Premain-Class>vip.meet.agent.PreMain</Premain-Class>
                                    <Agent-Class>vip.meet.attach.AgentMain</Agent-Class>
                                    <Can-Redefine-Classes>true</Can-Redefine-Classes>
                                    <Can-Retransform-Classes>true</Can-Retransform-Classes>
                                </manifestEntries>
                            </archive>
                            <descriptorRefs>
                                <descriptorRef>jar-with-dependencies</descriptorRef>
                            </descriptorRefs>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>
  • 6
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值