尽管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

Java代理初学者指南相关推荐

  1. java初学者指南_Java代理初学者指南

    java初学者指南 尽管Java初学者很快学会了键入public static void main来运行他们的应用程序,但是即使是经验丰富的开发人员也常常不知道JVM对Java流程的两个附加入口点的支 ...

  2. Java多线程初学者指南(10):使用Synchronized关键字同步类方法

    要想解决"脏数据"的问题,最简单的方法就是使用synchronized关键字来使run方法同步,代码如下: public synchronized void run() {     ...

  3. Java多线程初学者指南(12):使用Synchronized块同步变量

    我们可以通过synchronized块来同步特定的静态或非静态方法.要想实现这种需求必须为这些特性的方法定义一个类变量,然后将这些方法的代码用synchronized块括起来,并将这个类变量作为参数传 ...

  4. Java多线程初学者指南(5):join方法的使用

    在上面的例子中多次使用到了Thread类的join方法.我想大家可能已经猜出来join方法的功能是什么了.对,join方法的功能就是使异步执行的线程变成同步执行.也就是说,当调用线程实例的start方 ...

  5. java初学者指南_企业Java中事务隔离级别的初学者指南

    java初学者指南 介绍 基于ACID事务属性的关系数据库强一致性模型. 在本文中,我们将阐明对资源本地事务和JTA事务使用不同的事务隔离级别和各种配置模式的背后原因. 隔离和一致性 在关系数据库系统 ...

  6. java初学者指南_Java初学者指南

    java初学者指南 Java编程的第一步. 对于Java中的入门教程,请参阅Sun的官方帮助这里 除了核心语言外,还有几种技术和API 介绍. 我们建议首先阅读涵盖 基础知识,并继续其余的教程. 我们 ...

  7. 怎么学习Java编程,Java初学者指南

    怎么学习Java编程,Java初学者指南.Java基于C和C++.第一个Java编译器是由Sun Microsystems开发的,是使用C++中的一些库用C编写的.Java文件使用编译器转换为位代码格 ...

  8. Java工程师学习指南(2019最新版)

    写过一个Java工程师学习指南,我把它分为了四个部分,第一步是打好Java基础,掌握Java核心技术,第二步是掌握Java Web技术栈,能够做一些项目,第三步是掌握Java方面的进阶技术,包括网络编 ...

  9. 《Python编程初学者指南》——1.2 Python简介

    本节书摘来自异步社区<Python编程初学者指南>一书中的第1章,第1.2节,作者[美]Michael Dawson,王金兰 译,更多章节内容可以访问云栖社区"异步社区" ...

最新文章

  1. 智简全渠道孟伟:做CRM十六年,企业的一百种死法
  2. Windows驱动开发 - 派遣函数
  3. 马云:新型企业想要保持创新力 规模要在60人以内
  4. 黑马程序员-异常介绍与处理
  5. 201112阶段二qt图形视图框架
  6. 更换 Centos 中的 yum 源
  7. python取文本中间_Python读取两个字符串之间的特定文本行
  8. git学习(8):windows系统下VI编辑器的基本使用
  9. php字符串以符号截取,PHP按符号截取字符串的指定部分的实现方法
  10. 动态代理:jdk和cglib区别
  11. 【Elasticsearch】Elasticsearch-Hadoop打通Elasticsearch和Hadoop
  12. 如何在EdrawMax中同时画有箭头和没箭头的直线
  13. 广东省计算机一级常考选择题,广东省计算机一级选择题
  14. Linux开发-数据流与管道
  15. React.Children.only expected to receive a single React element child.【react-transition-group】
  16. 计算机无法从usb启动不了,用u盘启动不了电脑解决方法
  17. html 定义列表dddt,TDDD 文件扩展名: 它是什么以及如何打开它?
  18. 线性插值 np.interp()
  19. linux的PXE批量高效自动装机
  20. python处理时间格式时分秒_python将时分秒转换成秒的实例

热门文章

  1. 牛客练习赛 58——树链剖分
  2. Educational Codeforces Round 94 (Rated for Div. 2)
  3. 动态规划训练21 [FatMouse and Cheese HDU - 1078 ]
  4. 汇编语言(二十九)之数值的二进制和十进制
  5. 你知道面试官是如何刷人的吗
  6. Java 8新特性探究(二)深入解析默认方法
  7. 避免代码冗余,使用接口和泛型重构Java代码
  8. 新闻发布项目——分页公共类(PageUitl )
  9. JDBC登录功能实现
  10. Android public class MyApplication extends MultiDexApplication使用