首页 > 编程知识 正文

java agent获取磁盘容量,java agent 读取springboot 配置文件

时间:2023-05-03 06:35:45 阅读:269734 作者:4531

在 Java 字节码 一文中有提到,使用 Java Agent 操控字节码,本文将讨论 Java Agent ,这是普通 Java 开发人员的真正的黑魔法。Java Agent 能够通过执行字节码的直接修改,在运行时 “侵入” JVM 上运行的 Java 应用程序的执行。Java Agent 很强大,但是也很危险:它们几乎可以完成所有操作,但是如果出现问题,也很容易导致 JVM 崩溃。
我们将通过解析 Java Agent 如何工作,如何运行它们以及展示 Java Agent 作为明显优势的一些简单示例来揭开 Java Agent 的神秘面纱。

1. Java Agent 基础知识

从本质上讲,Java Agent 是一个遵循一组严格约定的常规 Java 类。代理类必须实现一个 public static void premain(String agentArgs, Instrumentation inst) 成为代理入口点的 main 方法(类似于常规 Java 应用程序的方法)。

Java 虚拟机(JVM)初始化后,premain(String agentArgs, Instrumentation inst) 将按照在 JVM 启动时指定代理的顺序调用每个代理的每个此类方法。完成此初始化步骤后, main 将调用真正的 Java 应用程序方法。简单来讲,就是 premain 方法,在 main 方法之前执行。

但是,如果类没有实现 public static void premain(String agentArgs, Instrumentation inst) 方法,JVM 将尝试查找并调用另一个重载版本 public static void premain(String agentArgs) 。注意,每个 premain 方法都必须返回,以便启动阶段继续进行。

咋一看很简单,但 Java Agent 实现应该提供另外一件事作为其包装的一部分:manifest。通常在 META-INF 文件夹中,名为 MANIFEST.MF,包含于包分发相关的各种元数据。
点击阅读:Java Agent 官方文档

2. Java Agent 代理和检测

Java Agent 的检测功能是无限的。最值得注意的包括但不限于:

能够在运行时重新定义类。
重新定义可以改变方法体,常量池和属性。重定义不得添加,删除,重命名字段或方法,不得更改方法的签名或更改继承。能够在运行时重新转换类。
重新转换可以改变方法体,常量和属性。新转换不得添加,删除,重命名字段或方法,不得更改方法的签名或更改继承。能够允许使用应用于名称的前缀进行重试来修改本机方法解析的失败处理。

注意,在应用了转换或重新定义之后,不会检查,验证和安装重新转换或重新定义的类字节码。如果生成的字节码错误或不正确,则会抛出异常,这可能会导致 JVM 完全崩溃。

3. 编写一个简单的 Java Agent

我们将通过实现自己的类转换器来编写一个简单的 Java Agent。话虽如此,使用 Java Agent 的唯一缺点是,需要直接的字节码操作技能(如果大家对 Java 字节码 不是很了解,可以参考我的这篇文章: Java 字节码 )。而且,遗憾的是,Java 标准库不提供任何 API来使这些字节码操作成为可能。
为了填补这一空白,Java 社区提供了一些很成熟的库,比如 Javassist (Javassist 入门)。
现在,我们着手编写一个示例,我们假设想捕获 Java 应用程序中打开的每个 HTTP 连接的 URL。有很多方法可以通过直接修改 Java 源代码来实现,但让我们假设源代码由于许可证策略或其他原因而不可用。
为了方便操作 Java 字节码,首先引入 Javassist 的 maven 包:

<!-- https://mvnrepository.com/artifact/org.javassist/javassist --> <dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>3.24.1-GA</version> </dependency>

打开 HTTP 连接的类的典型示例如下所示:

public class SimpleClass { public static void main( String[] args ) throws IOException { System.out.println("===========执行main方法============="); fetch("http://www.baidu.com"); fetch("http://www.163.com"); } private static void fetch(final String address) throws IOException { final URL url = new URL(address); final URLConnection connection = url.openConnection(); try (final BufferedReader in = new BufferedReader( new InputStreamReader( connection.getInputStream()) )){ String inputLine = null; final StringBuffer sb = new StringBuffer(); while ( (inputLine = in.readLine()) != null){ sb.append(inputLine); } System.out.println("Content size:" + sb.length()); } }}

Java Agent 非常适合解决此类问题。我们只需要定义变换器,它将 sun.net.www.protocol.http.HttpURLConnection 通过注入代码来稍微修改构造函数。让我们来看看其实现:

public class SimpleClassTransformer implements ClassFileTransformer { @Override public byte[] transform( ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { if(className.endsWith("sun/net/www/protocol/http/HttpURLConnection")){ ClassPool classPool = ClassPool.getDefault(); CtClass clazz = null; try { clazz = classPool.get("sun.net.www.protocol.http.HttpURLConnection"); CtConstructor[] cs = clazz.getConstructors(); for(CtConstructor constructor: cs){ constructor.insertAfter("System.out.println(this.getURL());"); } byte[] byteCode = clazz.toBytecode(); clazz.detach(); return byteCode; } catch (NotFoundException | CannotCompileException | IOException e) { e.printStackTrace(); } } return null; }}

定义 premain 方法,将 SimpleClassTransformer 类的实例添加到检测上下文中:

public class SimpleAgent { public static void premain(String agentArgs, Instrumentation inst) { System.out.println("=========开始执行premain============"); SimpleClassTransformer transformer = new SimpleClassTransformer(); inst.addTransformer(transformer); }}

要完成 Java Agent,还需要提供正确的 MANIFEST.MF,以便 JVM 能够选择正确的类。在 META-INF 目录下找到你的 MANIFEST.MF 文件:

Manifest-Version: 1.0Premain-Class: com.demo.SimpleAgent

请注意,冒号后面一定要有空格,最后一行要为空。

4. 运行 Java Agent

从命令运行时,可以使用 -javaagent :

-javaagent:<path-to-jar>[=options]

类似如下:

java -javaagent:agent.jar

注意,这是条伪命令,因为 agent.jar 有引用 javassist.jar 包中的内容,想要执行成功还需要调用 javassist.jar,命令如下:

java -javaagent:agent.jar -jar javassist.jar

运行结果:

下面我们在 IDE 中运行 Java Agent。

使用 idea:


然后运行 SimpleClass 中的 main 方法。结果如下:

使用 Eclipse:



运行结果:

写到这里,突然想到以前公司花了150W买过一个链路监控的产品,就是基于Java Agent 做的,功能很强大。但是需要注意,任何错误或不准确的字节码生成都可能导致JVM崩溃,一把双刃剑,看你怎么用了。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。