1. 1 不可撤销
  2. 2 小年兽 程嘉敏
  3. 3 手放开 李圣杰
  4. 4 迷人的危险3(翻自 dance flow) FAFA
  5. 5 山楂树之恋 程佳佳
  6. 6 summertime cinnamons / evening cinema
  7. 7 不谓侠(Cover 萧忆情Alex) CRITTY
  8. 8 神武醉相思(翻自 优我女团) 双笙
  9. 9 空山新雨后 音阙诗听 / 锦零
  10. 10 Wonderful U (Demo Version) AGA
  11. 11 广寒宫 丸子呦
  12. 12 陪我看日出 回音哥
  13. 13 春夏秋冬的你 王宇良
  14. 14 世界が终わるまでは… WANDS
  15. 15 多想在平庸的生活拥抱你 隔壁老樊
  16. 16 千禧 徐秉龙
  17. 17 我的一个道姑朋友 双笙
  18. 18 大鱼  (Cover 周深) 双笙
  19. 19 霜雪千年(Cover 洛天依 / 乐正绫) 双笙 / 封茗囧菌
  20. 20 云烟成雨(翻自 房东的猫) 周玥
  21. 21 情深深雨濛濛 杨胖雨
  22. 22 Five Hundred Miles Justin Timberlake / Carey Mulligan / Stark Sands
  23. 23 斑马斑马 房东的猫
  24. 24 See You Again Wiz Khalifa / Charlie Puth
  25. 25 Faded Alan Walker / Iselin Solheim
  26. 26 Natural J.Fla
  27. 27 New Soul Vox Angeli
  28. 28 ハレハレヤ(朗朗晴天)(翻自 v flower) 猫瑾
  29. 29 像鱼 王贰浪
  30. 30 Bye Bye Bye Lovestoned
  31. 31 Blame You 眠 / Lopu$
  32. 32 Believer J.Fla
  33. 33 书信 戴羽彤
  34. 34 柴 鱼 の c a l l i n g【已售】 幸子小姐拜托了
  35. 35 夜空中最亮的星(翻自 逃跑计划) 戴羽彤
  36. 36 慢慢喜欢你 LIve版(翻自 莫文蔚) 戴羽彤
  37. 37 病变(翻自 cubi) 戴羽彤
  38. 38 那女孩对我说 (完整版) Uu
  39. 39 绿色 陈雪凝
  40. 40 月牙湾 LIve版(翻自 F.I.R.) 戴羽彤
夜空中最亮的星(翻自 逃跑计划) - 戴羽彤
00:00 / 04:10

夜空中最亮的星 能否听清

那仰望的人 心底的孤独和叹息

夜空中最亮的星 能否记起

那曾与我同行 消失在风里的身影

我祈祷拥有一颗透明的心灵

和会流泪的眼睛

给我再去相信的勇气

越过谎言去拥抱你

每当我找不到存在的意义

每当我迷失在黑夜里

噢喔喔 夜空中最亮的星

请指引我靠近你

夜空中最亮的星 是否知道

那曾与我同行的身影 如今在哪里

夜空中最亮的星 是否在意

是等太阳先升起 还是意外先来临

我宁愿所有痛苦都留在心底

也不愿忘记你的眼睛

哦 给我再去相信的勇气

哦 越过谎言去拥抱你

每当我找不到存在的意义

每当我迷失在黑夜里

噢喔喔 夜空中最亮的星

请照亮我向前行 哒~

我祈祷拥有一颗透明的心灵

和会流泪的眼睛 哦

给我再去相信的勇气

哦 越过谎言去拥抱你

每当我找不到存在的意义

每当我迷失在黑夜里

噢喔喔 夜空中最亮的星

请照亮我向前行

初探Java agent技术

前言

不知道各位小伙伴在此之前,是否有听过或者了解过agent相关技术,没有听说过也没有关系,我们今天的目的就是介绍agent的相关技术,探讨agent的应用场景,分享一些实际开发中的应用案例。

印象中,我第一次了解agent技术,是在分享skywalking这款工具的时候,skywalking与我们项目的绑定就是通过agent来实现的。好了,先说这么多,接下来我们就来详细介绍下agent的一些技术点。

Agent

Agent是什么

Agent中文含义代理,但是在java中我跟喜欢称它为探针而非代理,尽管他也属于代理技术,但是代理本身并不能体现agent的作用。

agent技术是在JDK1.5引入的,通过agent技术,我们可以构建一个独立于应用程序的代理程序,用来协助监测、运行甚至替换其他JVM上的程序。使用它可以实现虚拟机级别的AOP功能。

Agent分为两种,一种是在主程序之前运行的Agent,一种是在主程序之后运行的Agent(前者的升级版,1.6以后提供),稍后我们会有具体实例展示。

Agent能干什么

首先它最大的作用就是解耦,比如skywalking中的应用,我们不需要对我们的程序做任何修改,只需要通过agent技术引入skywalking的代理包即可;其次最常应用的场景就是jvm级的AOP,比如jvm的监测;另一种就是类似热部署这样的字节码动态操作。

Agent技术演示

说了这么多好多小伙伴肯定看的云里雾里的,接下来我们通过两个简单示例,来演示下Agent技术的神奇之处。

先看第一种,也就是在主程序之前运行的Agent

在主程序之前运行的Agent

首先我们创建一个maven项目,编写这样一个Agent类:

import java.lang.instrument.Instrumentation;
/**
 * 在主程序之前运行的Agent
 */
public class PremainAgent {
    public static void premain(String preArgs, Instrumentation instrumentation) {
        System.out.println("premainAgent.premain start");
        System.out.println("preArgs: " + preArgs);
        Class[] allLoadedClasses = instrumentation.getAllLoadedClasses();
        for (Class allLoadedClass : allLoadedClasses) {
            System.out.println("premainAgent LoadedClass: " + allLoadedClass.getName());
        }
    }
}

这里的方法名和参数列表是固定的,根据方法名我们能看出这个方法应该是运行在main方法之前的,等下测试下就知道了。

接着,我们在pom.xml文件中增加如下内容:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <version>2.4</version>
            <configuration>
                <archive>
                    <manifest>
                        <addClasspath>true</addClasspath>
                    </manifest>
                    <manifestEntries>
                        <Premain-Class>io.github.syske.agent.PremainAgent</Premain-Class>
                    </manifestEntries>
                </archive>
            </configuration>
        </plugin>
    </plugins>
</build>

上面这些内容是配置我们构建时生成的MANIFEST文件,通常我们打的jar包都有这个文件。最核心的配置就是Premain-Class,这里配置的是我们探针的类名,如果没有这个配置,我们的premain方法是不会被识别的。

然后我们通过maven把我们当前项目打成一个jar包,打完包之后的jar文件如上图,打开MANIFEST.MF文件,你会发现我们指定的Premain-Class也被写入了,这时候我们的包就是打好了,下面就是运行测试了。

运行也很简单,只需要找到一个可运行的jar包,比如一个springboot项目的包,然后在jar文件的启动命令中,增加如下配置即可:

--javaagent:你的agent文件完整路径/agent文件名.jar
# 例如我的:D:\workspace\learning\example-everyday\example-2021.07.02\target\example-2021.07.02-1.0-SNAPSHOT.jar

这里我用之前的一个springboot项目演示:

java -javaagent:D:\workspace\learning\example-everyday\example-2021.07.02\target\example-2021.07.02-1.0-SNAPSHOT.jar -jar 

大家注意,在javaagentagent文件之间不能有空格,否则会报如下错误

如果你的配置和启动命令都没有问题,在启动控制台应该会显示如下信息:

我们可以看到premain方法在我们springboot项目启动前被执行了,但是preArgsnull,这是由于我们没有注入参数,所以显示为空,我们可以通过这样的方式为preArgs注入参数:

java -javaagent:D:\workspace\learning\example-everyday\example-2021.07.02\target\example-2021.07.02-1.0-SNAPSHOT.jar=syske -jar  .\springboot-learning-0.0.1-SNAPSHOT.jar

也就是在我们的agent包后面直接=需要注入的参数值就可以了,然后再次执行你会发现参数已经被注入了:

关于Instrumentation这个参数,今天先不讲了,我们说的字节码操都是基于这个参数进行操作的。下面我们看下第二种Agent

在主程序之后运行的Agent

相比第一种agent,第二种是在main方法启动后运行agent方法,而且这种方式应用最广泛,比如我们前面说的热部署,就是这种方式实现的,下来我们看下具体如何实现。

第一步,也是写Agent实现类:

public class AgentMain {
    public static void agentmain(String args, Instrumentation instrumentation) {
        System.out.println("AgentMainTest.agentmain start");
        System.out.println("args: " + args);
        Class[] allLoadedClasses = instrumentation.getAllLoadedClasses();
//        for (Class allLoadedClass : allLoadedClasses) {
        System.out.println("AgentMainTest LoadedClass: " + allLoadedClasses[0].getName());
//        }
    }
}

和第一种agent不一样的只有方法名,连参数都一幕一样,这里为了方便查看,我只打印了一行数据。然后我们还需要修改下maven的打包配置,需要把之前的Premain-Class标签改成Agent-Class,其他都一样:

然后再打包,但是这一次运行方式和第一次不一样,这里的agent要通过代码来启动。

我们创建一个测试类,写一个main方法,因为要用到tools包下的类,所以要先引入tools包:

测试类如下:

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;

public class MainTest {
    public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {
        List<VirtualMachineDescriptor> machineDescriptorList = VirtualMachine.list();
        for (VirtualMachineDescriptor virtualMachineDescriptor : machineDescriptorList) {
            if ("io.github.syske.agent.MainTest".equals(virtualMachineDescriptor.displayName())) {
                String id = virtualMachineDescriptor.id();
                VirtualMachine virtualMachine = VirtualMachine.attach(id);
                virtualMachine.loadAgent("D:\\workspace\\learning\\example-everyday\\example-2021.07.02\\target\\example-2021.07.02-1.0-SNAPSHOT.jar",
                        "syske agentmain");
                virtualMachine.detach();
            }
        }
        System.out.println("MainTest start");
    }
}

这里解释下,VirtualMachine.list()是获取当前运行的所有jvm虚拟机,运行结果如下:

其中的VirtualMachineDescriptor包含如下信息:

我们需要从中拿出displayNameio.github.syske.agent.MainTest,也就是当前类的虚拟机描述信息,然后根据虚拟机id拿到对应虚拟机,然后为该虚拟机加载Agent包,同时我们还在加在Agent包的同时,传入了syske agentmain参数,这里的参数和我们第一种方式=的方式类似,就相当于给args赋值,然后断开虚拟机连接。

运行代码,结果如下:

根据运行结果,我们发现这种Agent并发是在main方法之后执行,而是可以在你任意需要的地方调用。相比于第一种,确实要灵活一些。

总结

Agent其实在日常开发中经常用到,但是由于我们大部分情况下都用的是继承开发环境,所以感知不强,像日志采集、热部署等基本上都是基于Agent来实现的。

当然,agent最大的好处在于,它可以有效解耦,实现jvm层面的AOP,而且它又支持字节码操作,如果你玩的够溜,你就可以实现更多强大功能,而且可玩性还高,简直可以为所欲为。

今天我们暂时就讲这么多,后面抽时间用agent实现一些具体的功能,比如字节码操作,让大家真正见识Agent的强大之处。

posted @ 2021-07-15 13:36  云中志  阅读(413)  评论(0编辑  收藏  举报