前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Skywalking之Java Agent基础

Skywalking之Java Agent基础

作者头像
Bug开发工程师
发布2019-10-15 17:10:00
3.4K0
发布2019-10-15 17:10:00
举报
文章被收录于专栏:码农沉思录码农沉思录

之前的文章提到,要用 Skywalking 监控一个应用,需要在其 VM 参数中添加 “-javaagent:skywalking-agent.jar”(省略skywalking-agent.jar的完整路径),这其实用了Java探针技术,算是个比较老的技术了,本节就简单介绍一下Java Agent。

Java Agent 入门

Java Agent是从 JDK1.5 开始引入的,用一句概括其功能的话就是“在main()函数之前的一个拦截器”,也就是在执行main函数前,先执行Agent中的代码。

来吧,直接上Demo吧,一边看代码一边说(Maven项目,不解释了,不是Maven项目的话,可以自己百度一下Java Agent怎么玩,也挺简单的):

先看TestAgent的代码:

代码语言:javascript
复制
public class TestAgent {
    public static void premain(String agentArgs, 
            Instrumentation inst) {
        System.out.println("this is a java agent.");
        System.out.println("参数:" + agentArgs + "\n");
    }
}

Java Agent的入口是premain()方法,听名字就知道是在main()方法前面执行的,有两个重载,如下所示:

代码语言:javascript
复制
public static void premain(String agentArgs, 
      Instrumentation inst); 【1】
public static void premain(String agentArgs); 【2】

如果两个同时存在的时候,【2】将会被忽略,只执行【1】

pom.xml里面我们需要用到一个插件:

代码语言:javascript
复制
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-jar-plugin</artifactId>
    <version>2.6</version>
    <configuration>
        <archive>
            <manifestEntries>
                <premain-class>com.xxx.TestAgent</premain-class>
            </manifestEntries>
        </archive>
    </configuration>
</plugin>

这个插件是在打包的时候,写一下MANIFEST.MF,重点是写premain-class:

代码语言:javascript
复制
Manifest-Version: 1.0
premain-class: com.xxx.TestAgent   # 这里这里
Archiver-Version: Plexus Archiver
Built-By: xxx
Created-By: Apache Maven 3.6.0
Build-Jdk: 1.8.0_191

最后来看Main这个类:

代码语言:javascript
复制
public class Main {
    public static void main(String[] args) {
        System.out.println("TestAgent Main!");
    }
}

接下来maven package一下,得到test-agent.jar这个包,然后设置VM options:

代码语言:javascript
复制
-javaagent:/Users/xxx/ks/TestAgent/target/test-agent.jar=TestAgentArgs

这里等号之后,就是传入premain()方法的参数哈

执行main()方法,会得到如下输出:

代码语言:javascript
复制
this is a java agent.
参数:TestAgentArgs
TestAgent Main!

注意:Java Agent的代码与你的main方法在同一个JVM中运行,并被同一个类加载器所加载。

搞点有意思的

我知道,Hello World肯定满足不了你们,下面用 Java Agent + Byte Buddy 实现一个统计方法执行时间的功能。

Byte Buddy 的 API 在后面会单独来一篇介绍一下,这里不深入追究,简单说明每个 API 的作用即可。

整个项目的结构不变,首先多加两个Byte Buddy的依赖:

代码语言:javascript
复制
<dependency>
    <groupId>net.bytebuddy</groupId>
    <artifactId>byte-buddy</artifactId>
    <version>1.9.2</version>
</dependency>
<dependency>
    <groupId>net.bytebuddy</groupId>
    <artifactId>byte-buddy-agent</artifactId>
    <version>1.9.2</version>
</dependency>

然后来看TestAgent的代码:

代码语言:javascript
复制
public class TestAgent {
    public static void premain(String agentArgs, Instrumentation inst) {
        AgentBuilder.Transformer transformer = new AgentBuilder.Transformer() {
            public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder,
                                                    TypeDescription typeDescription,
                                                    ClassLoader classLoader,
                                                    JavaModule module) {
                return builder
                        // 拦截任意方法
                        .method(ElementMatchers.<MethodDescription>any())
                        // 拦截到的方法委托给TimeInterceptor
                        .intercept(MethodDelegation.to(TimeInterceptor.class));
            }
        };
        new AgentBuilder // Byte Buddy专门有个AgentBuilder来处理Java Agent的场景
                .Default()
                // 根据包名前缀拦截类
                .type(ElementMatchers.nameStartsWith("com.xxx"))
                // 拦截到的类由transformer处理
                .transform(transformer)
                .installOn(inst);
    }
}

当 Agent 拦截到符合条件的类时,会交给我们的 AgentBuilder.Transformer 实现处理,当 Transformer 拦截到符合条件的方法时,会交给我们的 TimeInterceptor 处理。TimeInterceptor 的具体实现如下:

代码语言:javascript
复制
public class TimeInterceptor {
    @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.getName() + ":"
                    + (System.currentTimeMillis() - start)
                    + "ms");
        }
    }
}

TimeInterceptor 就类似于 AOP 的环绕切面。这里通过 @SuperCall 注解注入的 Callable 实例可以调到被拦截的目标方法(即使目标方法带参数,这里也不用传哈);这里通过 @Origin 注入的 Method 就是目标方法的元信息,没啥可说的。

在Skywalking中用到的 Byte Buddy 知识在下一篇文章中会进行说明的,容我整理整理。

Main.java 中sleep 10s, VM options与前面的示例相同,不再赘述。执行 main() 方法,得到输出如下:

代码语言:javascript
复制
TestAgent Main!
main:10001ms

好了,Java Agent的基础知识和练手功能,就这样吧,Hello World 总是简单的,

╮(╯_╰)╭ ~ see you~

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-10-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码农沉思录 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档