Java Agent简介及使用Byte Buddy和AspectJ LTW监控方法执行耗时

1、什么是Java Agent

Java Agent提供了一种在加载字节码时,对字节码进行修改的方法。一共有两种方式执行:一种是在main方法执行之前,通过premain来实现;另一种是在程序运行中,通过attach api来实现

1)、Instrumentation

Instrumentation是JDK1.5提供的API,用于拦截类加载事件,并对字节码进行修改,它的主要方法如下:

public interface Instrumentation {
    
    
  	
  	//注册一个转换器,类加载事件会被注册的转换器所拦截
    void
    addTransformer(ClassFileTransformer transformer, boolean canRetransform);
  	
  	//重新触发类加载
    void
    retransformClasses(Class<?>... classes) throws UnmodifiableClassException;
  	
  	//直接替换类的定义
    void
    redefineClasses(ClassDefinition... definitions)
        throws  ClassNotFoundException, UnmodifiableClassException;  

2)、premain()方法

premain()方法是在main()方法之前运行的方法,运行时需要将agent程序打包成jar包,并在启动时添加命令来执行:

-javaagent:<jarpath>[=<options>]

premain()共提供以下两种重载方法,JVM启动时会先尝试使用第一种方法,若没有会使用第二种方法

    public static void premain(String agentArgs, Instrumentation inst)

    public static void premain(String agentArgs)

2、使用Byte Buddy监控方法执行耗时

Byte Buddy是一个代码生成和操作库,用于在Java应用程序运行时创建和修改Java类,而无需编译器的帮助。除了Java类库附带的代码生成实用程序外,Byte Buddy还允许创建任意类,并且不限于实现用于创建运行时代理的接口。此外,Byte Buddy提供了一种方便的API,可以使用Java代理或在构建过程中手动更改类

pom.xml中引入Byte Buddy相关依赖,并指定MANIFEST.MF文件路径

<?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.ppdai</groupId>
    <artifactId>bytebuddy-agent-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>net.bytebuddy</groupId>
            <artifactId>byte-buddy</artifactId>
            <version>1.8.20</version>
        </dependency>
        <dependency>
            <groupId>net.bytebuddy</groupId>
            <artifactId>byte-buddy-agent</artifactId>
            <version>1.8.20</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <archive>
                        <!--指定MANIFEST.MF文件路径-->
                        <manifestFile>
                            src/main/resources/META-INF/MANIFEST.MF
                        </manifestFile>
                        <manifest>
                            <addClasspath>true</addClasspath>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

MethodCostTime

public class MethodCostTime {
    
    
    @RuntimeType
    public static Object intercept(@Origin Method method, @SuperCall Callable<?> callable) throws Exception {
    
    
        long start = System.currentTimeMillis();
        try {
    
    
            //原有函数执行
            return callable.call();
        } finally {
    
    
            System.out.println(method + " 方法耗时: " + (System.currentTimeMillis() - start) + "ms");
        }
    }
}

MyAgent

public class MyAgent {
    
    
    public static void premain(String agentArgs, Instrumentation inst) {
    
    
        System.out.println("MyAgent init...");

        AgentBuilder.Transformer transformer = (builder, typeDescription, classLoader, javaModule) -> builder
                //拦截任意方法
                .method(ElementMatchers.any())
                //委托
                .intercept(MethodDelegation.to(MethodCostTime.class));

        AgentBuilder.Listener listener = new AgentBuilder.Listener() {
    
    
            @Override
            public void onDiscovery(String s, ClassLoader classLoader, JavaModule javaModule, boolean b) {
    
    

            }

            @Override
            public void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, JavaModule javaModule, boolean b, DynamicType dynamicType) {
    
    

            }

            @Override
            public void onIgnored(TypeDescription typeDescription, ClassLoader classLoader, JavaModule javaModule, boolean b) {
    
    

            }

            @Override
            public void onError(String s, ClassLoader classLoader, JavaModule javaModule, boolean b, Throwable throwable) {
    
    

            }

            @Override
            public void onComplete(String s, ClassLoader classLoader, JavaModule javaModule, boolean b) {
    
    

            }

        };

        new AgentBuilder
                .Default()
                //指定需要拦截的类
                .type(ElementMatchers.nameStartsWith("com.ppdai"))
                .transform(transformer)
                .with(listener)
                .installOn(inst);
    }
}

src/main/resources目录下添加META-INF/MANIFEST.MF文件,内容如下:

Manifest-Version: 1.0
Premain-Class: com.ppdai.agent.MyAgent
Can-Redefine-Classes: true

使用mvn clean package打包

测试类

public class AgentTest {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        Thread.sleep(new Random().nextInt(500));
    }
}

运行测试类时需要指定agent的jar包路径

-javaagent:<jarpath>[=<options>]

运行结果

MyAgent init...
public static void com.ppdai.test.AgentTest.main(java.lang.String[]) throws java.lang.InterruptedException 方法耗时: 350ms

源码已上传GitHub:https://github.com/hxt970311/bytebuddy-agent-demo

3、使用AspectJ LTW监控方法执行耗时

AspectJ作为AOP编程的完全解决方案,提供了三种织入时机,分别为:

  • compile-time:编译期织入,在编译的时候直接编译出包含织入代码的.class文件
  • post-compile:编译后织入,增强已经编译出来的类
  • load-time:在JVM进行类加载的时候进行织入

引入aspectj相关依赖

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.8.13</version>
        </dependency>

编写Aspect

@Aspect
public class MethodCostTimeAspect {
    
    
    @Pointcut("execution(* com.ppdai..*(..))")
    public void pointcut() {
    
    
    }

    @Around("pointcut()")
    public Object logProfile(ProceedingJoinPoint joinPoint) throws Throwable {
    
    
        long start = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        System.out.println(joinPoint.getSignature() + " 方法耗时: " + (System.currentTimeMillis() - start) + "ms");
        return result;
    }
}

src/main/resources目录下添加META-INF/aop.xml文件,指定Aspect类和需要被织入的类,内容如下:

<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
    <aspects>
        <aspect name="com.ppdai.MethodCostTimeAspect"/>
        <!--需要被织入的类-->
        <weaver options="-verbose -showWeaveInfo">
            <include within="com.ppdai..*"/>
        </weaver>
    </aspects>
</aspectj>

测试类

public class AspectjLtwTest {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        Thread.sleep(new Random().nextInt(500));
    }
}

运行测试类时指定agent aspectjweaver的jar包路径

-javaagent:/Users/hanxiantao/IdeaProjects/aspectj-ltw/target/aspectjweaver-1.8.13.jar

通过引入aspectjweaver依赖,到本地maven仓库获取aspectjweaver-1.8.13.jar

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.13</version>
        </dependency>

运行结果

[AppClassLoader@18b4aac2] info AspectJ Weaver Version 1.8.13 built on Wednesday Nov 15, 2017 at 19:26:44 GMT
[AppClassLoader@18b4aac2] info register classloader sun.misc.Launcher$AppClassLoader@18b4aac2
[AppClassLoader@18b4aac2] info using configuration /Users/hanxiantao/IdeaProjects/aspectj-ltw/target/classes/META-INF/aop.xml
[AppClassLoader@18b4aac2] info register aspect com.ppdai.MethodCostTimeAspect
[AppClassLoader@18b4aac2] weaveinfo Join point 'method-execution(void com.ppdai.AspectjLtwTest.main(java.lang.String[]))' in Type 'com.ppdai.AspectjLtwTest' (AspectjLtwTest.java:12) advised by around advice from 'com.ppdai.MethodCostTimeAspect' (MethodCostTimeAspect.java)
[AppClassLoader@18b4aac2] weaveinfo Join point 'method-execution(void com.ppdai.MethodCostTimeAspect.pointcut())' in Type 'com.ppdai.MethodCostTimeAspect' (MethodCostTimeAspect.java:17) advised by around advice from 'com.ppdai.MethodCostTimeAspect' (MethodCostTimeAspect.java)
void com.ppdai.AspectjLtwTest.main(String[]) 方法耗时: 428ms

源码已上传GitHub:https://github.com/hxt970311/aspectj-ltw

参考

https://mp.weixin.qq.com/s?__biz=MzI3NzE0NjcwMg==&mid=2650130323&idx=3&sn=f22cf468dd7a85539c5ab40cee8bb9ef

https://bugstack.blog.csdn.net/article/details/100044939

https://blog.csdn.net/generalfu/article/details/106086475

https://www.javadoop.com/post/aspectj#Load-Time%20Weaving

猜你喜欢

转载自blog.csdn.net/qq_40378034/article/details/115281684