Java最高

通过。开始使用Spring 5和Spring Boot 2学习的春天课程:

>>查看课程

1.介绍

在本教程中,我们将讨论金宝搏官网188beJava工具API。它提供了向现有已编译的Java类添加字节码的能力。

我们还将讨论Java代理以及我们金宝搏官网188be如何使用它们来仪器仪器。

2.设置

在整篇文章中,我们将使用仪表构建一个应用程序。

我们的应用程序将包含两个模块:

  1. 一个允许我们取钱的ATM应用程序
  2. 还有一个Java代理,它将允许我们通过测量花费的时间来衡量ATM的性能

Java代理将修改ATM字节码,使我们无需修改ATM应用程序就可以测量取款时间。

我们的项目将有以下结构:

 com金宝搏188体育.baeldung.instrumentation   BASE   1.0.0   POM   代理 <模块>应用程序 

在深入了解插装的细节之前,让我们先看看什么是java代理。

3.什么是Java代理商

通常,java代理只是一个特别制作的jar文件。它利用仪表API是JVM提供的,用来更改JVM中加载的现有字节码。

要让代理工作,我们需要定义两个方法:

  • —将在JVM启动时使用-javaagent参数静态加载代理
  • agentmain-将动态加载代理到JVM中使用Java附加API

需要记住的一个有趣概念是,JVM实现(如Oracle、OpenJDK等)可以提供动态启动代理的机制,但这不是必需的。

首先,让我们看看如何使用现有的Java代理。

在此之后,我们将看看如何从头创建一个,以在字节码中添加所需的功能。

4.加载Java代理

为了能够使用Java代理,我们必须首先加载它。

我们有两种类型的负载:

  • 静态-利用使用-javaagent选项加载代理
  • 动态-利用agentmain控件将代理加载到JVM中Java附加API

接下来,我们将看看每种类型的加载,并解释它是如何工作的。

4.1。静载荷

在应用程序启动时加载Java代理称为静态加载。静态加载在启动时在任何代码执行之前修改字节码。

记住,静态加载使用方法,它将在任何应用程序代码运行之前运行,以便运行我们可以执行:

Java -JavaAgent:Agent.jar -Jar Application.jar

重要的是要注意,我们应该总是把-javaAgent.参数前的-jar参数。

下面是我们的命令的日志:

[main] INFO - [Agent] transform class MyAtm 22:24:39.407 [main] INFO - [Application] Starting ATM Application 22:24:41.409 [main] INFO - [Application] Successful Withdrawal [7] unit !22:24:41.410[主界面]提示-[应用程序]取款操作在:2秒内完成!22:24:53.411[主]信息-[应用]成功撤退[8]单位!22:24:53.411[主界面]信息-[应用程序]取款操作在:2秒内完成!

我们可以看到何时方法运行时MyAtm类是转换。我们还看到了两个ATM取款交易日志,其中包含了完成每个操作所花费的时间。

请记住,在最初的应用程序中,我们没有事务的完成时间,它是由Java代理添加的。

4.2。动态负载

将Java代理加载到已经运行的JVM中的过程称为动态加载。控件附加代理Java附加API

更复杂的场景是,我们已经在生产环境中运行ATM应用程序,我们希望动态地添加事务的总时间,而不需要应用程序停机。

让我们写一小段代码来做这个,然后调用这个类AgentLoader。为简单起见,我们将把这个类放在应用程序jar文件中。因此,我们的应用程序jar文件既可以启动我们的应用程序,也可以将代理附加到ATM应用程序:

VirtualMachine jvm = VirtualMachine.attach(jvmPid);jvm.loadAgent (agentFile.getAbsolutePath ());jvm.detach ();

现在我们有了AgentLoader,我们启动应用程序,以确保在事务之间的10秒暂停中,我们将使用AgentLoader

让我们添加胶水,它将允许我们启动应用程序或加载代理。

我们称之为类发射器它将是我们的主要jar文件类:

public class Launcher {public static void main(String[] args) throws Exception {if(args[0].equals("StartMyAtmApplication")) {new MyAtmApplication().run(args);} else if(args[0].equals("LoadAgent")) {new AgentLoader().run(args);}}}

启动应用程序

java -jar Application .jar StartMyAtmApplication 22:44:23. 154 [main] INFO - [Application] Starting ATM Application 22:44:23.157 [main] INFO - [Application] Successful Withdrawal of [7] units!

将Java代理

在第一次操作之后,我们将java代理附加到JVM上:

java -jar application.jar LoadAgent 22:44:27.022 [main] INFO - Attached to target JVM with PID: 6575 22:44:27.306 [main] INFO - Attached to target JVM and loaded java agent successfully

检查应用程序日志

现在我们将代理附加到JVM上,我们将看到第二次ATM取款操作的总完成时间。

这意味着我们是在应用程序运行时动态添加功能的:

22:44:27.229 [Attach Listener] INFO - [Agent] In agentmain method 22:44:27.230 [Attach Listener] INFO - [Agent] Transforming class MyAtm 22:44:33.157 [main] INFO - [Application] Successful Withdrawal [8] units!22:44:33.157[主]信息-[应用程序]取款操作在:2秒内完成!

5.创建Java代理

在学习了如何使用代理之后,让我们看看如何创建代理。我们来看看如何使用Javassist为了改变字节码,我们将把它与一些插装API方法结合起来。

因为java代理使用Java工具API,在深入创建我们的代理之前,让我们看看这个API中最常用的一些方法,并简要说明它们的作用:

  • addTransformer-在仪表引擎中添加变压器
  • getAllLoadedClasses- 返回当前由JVM加载的所有类的数组
  • retransformClasses-通过添加字节码来方便插装已经加载的类
  • removeTransformer—取消注册供电变压器
  • redefineClasses-使用提供的类文件重新定义提供的类集,这意味着类将被完全替换,而不是被修改retransformClasses

5.1。创建Premain所Agentmain方法

我们知道每个Java代理都需要至少一个或者agentmain方法。后者用于动态加载,而前者用于静态地将java代理加载到JVM中。

让我们在我们的代理中定义它们,这样我们就可以静态和动态地加载这个代理:

public static void premain(String agentArgs, Instrumentation inst) {LOGGER.info("[Agent]在premain方法");String className = "com.金宝搏188体育 baeldun.instrument.application.myatm ";transformClass(类名,本月);} public static void agentmain(String agentArgs, Instrumentation inst) {LOGGER.info("[Agent] In agentmain method");String className = "com.金宝搏188体育 baeldun.instrument.application.myatm ";transformClass(类名,本月);}

在每个方法中,我们声明想要更改的类,然后使用transformClass方法。

代码如下transformClass方法,我们定义它来帮助我们进行转换MyAtm类。

在这个方法中,我们找到我们想要转换的类并使用变换方法。此外,我们还将变压器添加到仪表引擎:

私有静态void变换基金(String ClassName,仪器仪器){class <?> targetcls = null;classloader targetclassloader = null;//看看我们是否可以使用forname尝试{targetcls = class.forname(classname);targetclassloader = targetcls.getClassLoader();变换(targetcls,targetclassloader,仪器);返回;} catch(例外前){logger.error(“使用class.forname找不到”类[{}]);} //否则迭代所有加载的类并找到我们想要的(class <?> clazz:stormentation.getallloadedclasses()){if(clazz.getname()。等于(classname)){targetcls = clazz;targetclassloader = targetcls.getClassLoader();变换(targetcls,targetclassloader,仪器); return; } } throw new RuntimeException( "Failed to find class [" + className + "]"); } private static void transform( Class clazz, ClassLoader classLoader, Instrumentation instrumentation) { AtmTransformer dt = new AtmTransformer( clazz.getName(), classLoader); instrumentation.addTransformer(dt, true); try { instrumentation.retransformClasses(clazz); } catch (Exception ex) { throw new RuntimeException( "Transform failed for: [" + clazz.getName() + "]", ex); } }

有了这些之后,让我们定义for的转换器MyAtm类。

5.2。定义我们的变压器

类转换器必须实现ClassFileTransformer并实现了变换方法。

我们将使用Javassist将字节码添加到MyAtm类,并添加一个记录ATW取款交易总时间的日志:

公共类AtmTransformer实现ClassFileTransformer {@override public byte []变换(classloader loader,string classname,class <?> classbeingredefined,presiticaldomainectiondomain,byte [] classfilebuffer){byte [] bytecode = classfilebuffer;String FinalTargetClassName = this.targetclassname .replaceall(“\\。”,“/”);if(!classname.equals(finaltargetclassname)){return字节码;}如果(classname.equals(finaltargetclassname)&& loader.equals(targetclassloader)){logger.info(“[代理人]转换类myatm”);尝试{classpool cp = classpool.getdefault();ctclass cc = cp.get(targetclassname);ctmethod m = cc.getdeclaredmethod(authen_money_method);m.addlocalvariable(“starttime”,ctclass.longtype);m.insertbefore(“starttime = system.currenttimemillis();”);StringBuilder EndBlock = new StringBuilder(); m.addLocalVariable("endTime", CtClass.longType); m.addLocalVariable("opTime", CtClass.longType); endBlock.append( "endTime = System.currentTimeMillis();"); endBlock.append( "opTime = (endTime-startTime)/1000;"); endBlock.append( "LOGGER.info(\"[Application] Withdrawal operation completed in:" + "\" + opTime + \" seconds!\");"); m.insertAfter(endBlock.toString()); byteCode = cc.toBytecode(); cc.detach(); } catch (NotFoundException | CannotCompileException | IOException e) { LOGGER.error("Exception", e); } } return byteCode; } }

5.3。创建代理清单文件

最后,为了得到一个正常工作的Java代理,我们需要一个具有几个属性的清单文件。

因此,我们可以找到清单属性的完整列表仪器包官方文档。

在最终的Java代理jar文件中,我们将向清单文件添加以下行:

代理类:com. baeldun.金宝搏188体育instrument.agent . myinstrumentationagent Can-Redefine-Classes: true Can-Retransform-Classes: true Premain-Class: com. baeldun.instrument.agent . myinstrumentationagent . Can-Retransform-Classes: true

我们的Java插装代理现在已经完成了。要运行它,请参考加载Java代理部分。

6.结论

在本文中,我们讨论了Java插装API。金宝搏官网188be我们讨论了如何静态和动态地将Java代理加载到JVM中。

我们还讨论了如何从头开始创建我们自己的Java代理。金宝搏官网188be

与往常一样,可以找到示例的完整实现在Github

Java底部

通过。开始使用Spring 5和Spring Boot 2学习的春天课程:

>>查看课程
本文评论关闭!