Java代理初学者指南

尽管Java初学者很快学会了键入public static void main来运行他们的应用程序,但是即使是经验丰富的开发人员也常常不知道JVM对Java流程的两个附加入口点的支持: premainagentmain方法。 这两种方法都允许所谓的Java代理在驻留在其自己的jar文件中的同时对现有Java程序做出贡献,即使没有被主应用程序显式链接。 这样做,有可能与托管它们的应用程序完全独立地开发,发行和发布Java代理,同时仍在同一Java进程中运行它们。

最简单的Java代理先于实际应用程序运行,例如执行一些动态设置。 代理可以例如安装特定的SecurityManager或以编程方式配置系统属性。 下面的类是一个不太有用的代理,仍然可以作为良好的入门演示:在将控制权传递给实际应用程序的main方法之前,该类仅将一行打印到控制台:

 <pre class= "wp-block-syntaxhighlighter-code" >package sample;  public class SimpleAgent<?> { public static void premain(String argument) { System.out.println( "Hello " + argument); }  }< /pre > 

要将此类用作Java代理,需要将其包装在jar文件中。 除常规Java程序外,无法从文件夹加载Java代理的类。 另外,需要指定一个清单条目,该清单条目引用包含premain方法的类:

 Premain-Class: sample.SimpleAgent 

通过此设置,现在可以在命令行上添加Java代理,方法是指向捆绑代理的文件系统位置,并可以选择在等号后添加单个参数,如下所示:

java -javaagent:/location/of/agent.jar=世界some.random.Program

现在在some.random.Program执行main方法之前,将打印出Hello World ,其中第二个单词是所提供的参数。

仪表API

如果抢占式代码执行是Java代理的唯一功能,那么它们当然将没有多大用处。 实际上,大多数Java代理仅是有用的,因为Java代理可以通过将类型为Instrumentation的第二个参数添加到代理的入口点方法来请求Java代理请求。 仪器API提供对Java代理专有的JVM提供的较低级别功能的访问,而JVM从不提供给常规Java程序。 工具API的核心是允许在Java类加载之前或之后对其进行修改。

任何已编译的Java类都存储为.class文件,该文件在首次加载时以字节数组的形式呈现给Java代理。 通过将一个或多个ClassFileTransformer注册到检测API来通知代理,该API会针对当前JVM进程的ClassLoader加载的任何类得到通知:

 package sample;  public class ClassLoadingAgent { public static void premain(String argument, Instrumentation instrumentation) { instrumentation.addTransformer(new ClassFileTransformer() { @Override public byte[] transform(Module module, ClassLoader loader, String name, Class<?> typeIfLoaded, ProtectionDomain domain, byte[] buffer) { System.out.println( "Class was loaded: " + name); return null; } }); }  } 

在上面的示例中,代理通过从转换器返回null来保持不运行状态,这使转换过程中止,但是仅将带有最近加载的类的名称的消息打印到控制台。 但是,通过转换buffer参数提供的字节数组,代理可以在加载任何类之前更改其行为。

转换已编译的Java类可能听起来很复杂。 但是幸运的是, Java虚拟机规范(JVMS)详细说明了代表类文件的每个字节的含义。 为了修改一种方法的行为,因此将识别该方法代码的偏移量,然后向该方法添加所谓的Java字节代码指令,以表示所需的已更改行为。 通常,这种转换不是手动应用的,而是通过使用字节码处理器(最著名的是ASM库)将类文件拆分为组件的应用。 这样,就可以孤立地查看字段,方法和注释,从而可以应用更有针对性的转换并节省一些记账。

无干扰的代理

尽管ASM使类文件转换更安全,更简单,但它仍然依赖于库用户对字节码及其特征的良好理解。 但是,其他通常基于ASM的库允许在更高级别上表达字节码转换,这使得这种理解成为必然。 此类库的一个示例是Byte Buddy ,它由本文的作者开发和维护。 Byte Buddy旨在将字节码转换映射到大多数Java开发人员已经知道的概念,以使代理开发更容易上手。

为了编写Java代理,Byte Buddy提供了AgentBuilder API,该API在ClassFileTransformer创建并注册ClassFileTransformer 。 字节好友ClassFileTransformer直接注册ClassFileTransformer ,而是允许指定ElementMatcher来首先标识感兴趣的类型。 对于每种匹配类型,然后可以指定一个或多个转换。 然后,Byte Buddy将这些指令转换为可以安装到Instrumentation API中的转换器的高性能实现。 例如,以下代码在Byte Buddy的API中重新创建了先前的非运行转换器:

 package sample;  public class ByteBuddySampleAgent { public static void premain(String argument, Instrumentation instrumentation) { new AgentBuilder.Default() . type (ElementMatchers.any()) .transform((DynamicType.Builder<?> builder, TypeDescription type , ClassLoader loader, JavaModule module) -> { System.out.println( "Class was loaded: " + name); return builder; }).installOn(instrumentation); }  } 

应该提到的是,与前面的示例相反,Byte Buddy将转换所有发现的类型,而无需应用更改,而后者将完全忽略那些不需要的类型,效率较低。 另外,如果没有另外指定,默认情况下它将忽略Java核心库的类。 但是实质上,可以达到相同的效果,从而可以使用上述代码演示使用Byte Buddy的简单代理。

使用Byte Buddy建议测量执行时间

字节伙伴不是将类文件公开为字节数组,而是尝试将常规Java代码编织或链接到已检测类中。 这样,Java代理的开发人员无需直接产生字节码,而可以依赖于Java编程语言及其与之已有关系的现有工具。 对于使用Byte Buddy编写的Java代理,行为通常由建议类表示,在这些类中,带注释的方法描述了添加到现有方法的开头和结尾的行为。 例如,以下建议类用作模板,该模板将方法的执行时间打印到控制台:

 public class TimeMeasurementAdvice { @Advice.OnMethodEnter public static long enter() { return System.currentTimeMillis(); } @Advice.OnMethodExit(onThrowable = Throwable.class) public static void exit (@Advice.Enter long start, @Advice.Origin String origin) { long executionTime = System.currentTimeMillis() - start; System.out.println(origin + " took " + executionTime + " to execute" ); }  } 

在上面的建议类中,enter方法仅记录当前时间戳,并返回该时间戳以使其在方法末尾可用。 如图所示,在实际方法主体之前执行输入建议。 在方法结束时,将应用退出建议,在该建议中,将从当前时间戳中减去所记录的值,以确定该方法的执行时间。 然后将执行时间打印到控制台。

为了利用建议,需要将其应用在先前示例中仍未运行的变压器中。 为避免打印任何方法的运行时,我们将建议的应用程序条件MeasureTime自定义的,保留了运行时的注释MeasureTime ,应用程序开发人员可以将其添加到其类中。

 package sample;  public class ByteBuddyTimeMeasuringAgent { public static void premain(String argument, Instrumentation instrumentation) { Advice advice = Advice.to(TimeMeasurementAdvice.class); new AgentBuilder.Default() . type (ElementMatchers.isAnnotatedBy(MeasureTime.class)) .transform((DynamicType.Builder<?> builder, TypeDescription type , ClassLoader loader, JavaModule module) -> { return builder.visit(advice.on(ElementMatchers.isMethod()); }).installOn(instrumentation); }  } 

给定上述代理程序的应用程序之后,如果通过MeasureTime注释了一个类,则现在将所有方法执行时间打印到控制台。 实际上,以更结构化的方式收集此类指标当然更有意义,但是在已经完成打印输出之后,这不再是要完成的复杂任务。

动态代理附件和类重新定义

在Java 8之前,这要归功于JDK的tools.jar中存储的实用程序,该实用程序可以在JDK的安装文件夹中找到。 从Java 9开始,此jar已分解到jdk.attach模块中,该模块现在可在任何常规JDK发行版中使用。 使用包含的工具API,可以使用以下代码将JAR文件附加到具有给定进程ID的JVM:

 VirtualMachine vm = VirtualMachine.attach(processId);  try { vm.loadAgent( "/location/of/agent.jar" );  } finally { vm.detach();  } 

当调用上述API时,JVM将使用给定的ID定位进程,并在该远程虚拟机内的专用线程中执行agent agentmain方法。 此外,此类代理可能会要求有权在其清单中重新转换类,以更改已加载的类的代码:

 Agentmain-Class: sample.SimpleAgent  Can-Retransform-Classes: true 

给定这些清单条目之后,代理现在可以请求考虑将任何已加载的类进行重新转换, ClassFileTransformer可以使用附加的布尔参数来注册先前的ClassFileTransformer ,从而指示需要在重新转换尝试时得到通知:

 package sample;  public class ClassReloadingAgent { public static void agentmain(String argument, Instrumentation instrumentation) { instrumentation.addTransformer(new ClassFileTransformer() { @Override public byte[] transform(Module module, ClassLoader loader, String name, Class<?> typeIfLoaded, ProtectionDomain domain, byte[] buffer) { if (typeIfLoaded == null) { System.out.println( "Class was loaded: " + name); } else { System.out.println( "Class was re-loaded: " + name); } return null; } }, true ); instrumentation.retransformClasses( instrumentation.getAllLoadedClasses()); }  } 

为了表明已经加载了一个类,现在将已加载类的实例提供给转换器,对于之前未加载的类,该实例为null 。 在以上示例的末尾,请求仪表API获取所有已加载的类,以提交任何此类类进行重新转换,从而触发转换器的执行。 和以前一样,出于演示工具API的目的,将类文件转换器实现为不可操作。

当然,Byte Buddy还通过注册重新转换策略在其API中涵盖了这种转换形式,在这种情况下,Byte Buddy还将考虑所有类别以便进行重新转换。 这样做,可以调整以前的时间测量代理程序,使其在动态连接的情况下也考虑加载的类:

 package sample;  public class ByteBuddyTimeMeasuringRetransformingAgent { public static void agentmain(String argument, Instrumentation instrumentation) { Advice advice = Advice.to(TimeMeasurementAdvice.class); new AgentBuilder.Default() .with(AgentBuilder.RetransformationStrategy.RETRANSFORMATION) .disableClassFormatChanges() . type (ElementMatchers.isAnnotatedBy(MeasureTime.class)) .transform((DynamicType.Builder<?> builder, TypeDescription type , ClassLoader loader, JavaModule module) -> { return builder.visit(advice.on(ElementMatchers.isMethod()); }).installOn(instrumentation); }  } 

为了最终方便,Byte Buddy还提供了一个用于附加到JVM的API,该API对JVM版本和供应商进行了抽象,以使附加过程尽可能地简单。 给定一个进程ID,Byte Buddy可以通过执行一行代码将代理附加到JVM:

 ByteBuddyAgent.attach(processId, "/location/of/agent.jar" ); 

此外,甚至可以将当前正在运行的同一虚拟机进程附加到测试代理程序时特别方便的进程:

 Instrumentation instrumentation = ByteBuddyAgent. install (); 

此功能可以作为其自己的工件byte-buddy-agent使用 ,由于使用Instrumentation实例可以直接(例如,从一个单元中直接调用premain或agentmain方法)成为可能,因此自己尝试尝试自定义代理很简单。测试,无需任何其他设置。

翻译自: https://www.javacodegeeks.com/2019/12/a-beginners-guide-to-java-agents.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/341077.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

npm WARN logfile could not create logs-dir: Error: EPERM: operation not permitted, mkdir ‘地址

场景&#xff1a;在windows系统下&#xff0c;安装node之后&#xff0c;查看npm版本&#xff0c;报错如图所示&#xff1a; 原因&#xff1a;是node目录权限不够&#xff1b; 解决方法&#xff1a;找到node目录&#xff0c;右键属性 > 安全 > 设置users用户完全控制权限…

javafx 自定义控件_JavaFX技巧10:自定义复合控件

javafx 自定义控件用JavaFX编写自定义控件是一个简单直接的过程。 需要一个控件类来控制控件的状态&#xff08;因此命名&#xff09;。 外观需要控件的外观。 而且通常不是用于自定义外观CSS文件。 控件的一种常见方法是将其正在使用的节点隐藏在其外观类中。 例如&#xff0…

虚拟机与容器 的 区别

VM和容器都可以帮助您充分利用可用的计算机硬件和软件资源。容器是块中的新孩子&#xff0c;但VM已经并且将继续在各种规模的数据中心中非常受欢迎。 如果您正在寻找在云中运行自己的服务的最佳解决方案&#xff0c;您需要了解这些虚拟化技术&#xff0c;它们如何相互比较&…

thymeleaf片段使用_Thymeleaf –片段和angularjs路由器局部视图

thymeleaf片段使用百里香叶许多很酷的功能之一就是能够渲染模板片段–我发现这是与AngularJs一起使用的特别有用的功能。 可以将AngularJS $ routeProvider或AngularUI路由器配置为返回不同“路径”的部分视图&#xff0c;使用百里香叶返回这些部分视图确实效果很好。 考虑一…

经典 Linux 协议栈——网络子系统

目录&#xff1a; 1.Linux网络子系统的分层 2.TCP/IP分层模型 3.Linux 网络协议栈 4.Linux 网卡收包时的中断处理问题 5.Linux 网络启动的准备工作 6.Linux网络包&#xff1a;中断到网络层接收 7.总结 Linux网络子系统的分层 Linux网络子系统实现需要&#xff1a; l …

Java和JavaScript之间的区别

1.简介 我们将在本文中比较Java语言和JavaScript语言。 JavaScript由Netscape开发。 它最初是用于客户端的脚本语言&#xff0c;后来又用作客户端和服务器脚本的语言。 Java由James Gosling由Sun Microsystems开发。 这些天来&#xff0c;JavaScript在服务器中以node.js的形式使…

《汇编语言》王爽实验DOS 环境 Win10 配置

下载这两个软件。 软件链接百度网盘 请输入提取码 提取码: y1j4 1. 将debug.exe放入一个文件夹中&#xff0c;用英文名&#xff0c;不要用中文。 我这里放在E盘下的Debug文件夹。 2 然后安装DOSBox软件。 安装好后在其文件目录下找到DOSBox 0.74-3 Options.bat 打开这个文件&…

硒4 Alpha –期望什么?

硒4 Alpha-期望什么&#xff1f; 早在2018年8月&#xff0c;整个测试自动化社区就受到了一个重大新闻的打击&#xff1a;Selenium的创始成员Simon Stewart在班加罗尔Selenium会议上正式确认了Selenium 4的发布日期和一些重大更新。 世界最受欢迎的Web测试自动化框架的4.0版本计…

8. 字符串转换整数 (atoi) [2022.10.21]

题目链接&#xff1a; 8. 字符串转换整数 (atoi) 一 题意介绍 请你来实现一个 myAtoi(string s) 函数&#xff0c;使其能将字符串转换成一个 32 位有符号整数&#xff08;类似 C/C 中的 atoi 函数&#xff09;。 函数 myAtoi(string s) 的算法如下&#xff1a; 读入字符串…

26. 删除有序数组中的重复项[2022.10.24]

题目链接力扣 难度&#xff1a;简单 一 题目大意 26. 删除有序数组中的重复项给你一个 升序排列 的数组 nums &#xff0c;请你 原地 删除重复出现的元素&#xff0c;使每个元素 只出现一次 &#xff0c;返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。 由于在某…

hibernate jpa_JPA / Hibernate实体状态转换的初学者指南

hibernate jpa介绍 Hibernate将开发人员的思维方式从SQL语句转移到实体状态转换。 一旦由Hibernate主动管理实体&#xff0c;所有更改将自动传播到数据库。 操作域模型实体&#xff08;及其关联&#xff09;比编写和维护SQL语句容易得多。 如果没有ORM工具&#xff0c;则添加新…

STL容器----map

一 基本概念 1. map/multimap map/multimap属于关联式容器&#xff0c;底层结构是用二叉树实现。 其中所有元素都是pair, pair中第一个元素为key&#xff08;键值&#xff09;&#xff0c;起到索引作用&#xff0c;第二个元素为value&#xff08;实值&#xff09;&#xff0…

Spring @RequestParam批注

介绍&#xff1a; Spring RequestParam批注可用于在处理程序方法中提取查询参数。 在本快速教程中&#xff0c;我们将学习其用法。 首先让我们展示一个API&#xff0c;该API返回具有给定名字和年龄的用户列表&#xff1a; RestController public class UserController {...Ge…

win10任务栏怎样居中win10任务栏居中设定教程

win11系统内置任务栏居中的设置项&#xff0c;但是win10系统没有&#xff0c;倘若win10顾客也想让自己的任务栏居中的话&#xff0c;应当怎样设置呢&#xff1f;你先撤销任务栏锁住&#xff0c;随后新建菜单栏。之后选定一个空白文件夹&#xff0c;之后任务栏就会发生两条竖杠&…

java与java ee_RxJava + Java8 + Java EE 7 + Arquillian =幸福

java与java ee微服务是一种体系结构样式&#xff0c;其中每个服务都实现为一个独立的系统。 他们可以使用自己的持久性系统&#xff08;尽管不是强制性的&#xff09;&#xff0c;部署&#xff0c;语言等。 由于系统由一个以上的服务组成&#xff0c;因此每个服务将与其他服务…

【PPT】折线线条怎么画?

大家晚上好~ 今天跟大家分享3种绘制折线线条的方法。在模仿PPT的时候发现没有折线形状&#xff0c;这可怎么好呢&#xff1f; 今天带来了3种快速制作折线线条的方式&#xff0c;让我们一起围观学习吧~ 方法1 形状布尔运算出折线线条 在PPT默认的形状里没有折线&#xff0c;那…

创建通用数组的问题

在这篇文章中&#xff0c;我们将介绍一篇全面的文章&#xff0c;其中介绍了创建通用数组的问题。 Java编程语言于2004年9月在Java 5.0“ Tiger”发行版中添加了泛型。 泛型或类型参数化系统在提供类型安全性的同时扩展了Java现有的类型系统。 1.简介 Java具有Collections Fram…

Citavi阅读PDF文件中目录位置

一般阅读pdf文件&#xff0c;如果文件内内嵌目录数据&#xff0c;可以根据目录&#xff0c;跳转到PDF文件的对应章节。 citavi也不例外 citavi调出目录章节位置如下图所示&#xff1a; 1. 点击左下方的搜索框 2. 点击目录标志 即可调出目录窗口

Ubuntu下命令行解析

Linux命令通常由以下三部分组成&#xff1a;Command [-option] [argument] 其中&#xff0c;命令为程序的名称。选项和参数可以省略 选项中one dash&#xff08;-&#xff09;与two dashes&#xff08;--&#xff09;区别 使用命令时常看到有时候为选项为-&#xff0c;有时候…

使用JMeter进行性能测试

在开发复杂的高可用性软件项目时&#xff0c;性能至关重要。 在当今这样的现代时代尤其如此&#xff0c;除了闪电般的快速访问实时数据之外&#xff0c;其他任何事情都受到惩罚。 当谈论有时需要的大量数据时&#xff0c;这并不总是一件容易的事。 在本文中&#xff0c;我们将…