java抓取网页数据(基于javaAgent和Java码注入技术的java探针工具技术原理)
优采云 发布时间: 2022-02-24 12:07java抓取网页数据(基于javaAgent和Java码注入技术的java探针工具技术原理)
今天公司组织技术培训,Java分布式链路跟踪框架(SkyWalking),SkyWalking是通过配置Agent实现的,查了一些资料和大家一起学习。
基于javaAgent和Java字节码注入技术的java探针工具技术原理:
我们使用Java代理和ASM字节码技术来开发java探针工具。实现原理如下:
Java代理技术是在jdk1.5之后引入的,Java代理在运行方法之前是一个*敏*感*词*。我们利用Java代理和ASM字节码技术,在JVM加载class二进制文件时,用ASM动态修改加载的class文件,并在监控方法前后添加定时器函数,计算监控方法的耗时。将方法耗时和内部调用条件放入处理器,处理器利用栈的先进后出特性来处理方法调用序列。处理请求时,将耗时的方法跟踪和输入参数映射输出到文件。然后根据map中对应的参数或者耗时方法轨迹中的key代码,我们可以区分我们想要捕获的耗时业务。最后去掉对应的耗时轨迹文件,转换成xml格式解析,通过浏览器展示代码层次结构,方便耗时分析,如图:
Java探针工具功能点:
1、支持方法执行耗时范围捕获设置,根据耗时范围捕获系统运行时出现在设置的耗时范围内的代码运行轨迹。
2、支持抓取具体的代码配置,方便抓取具体的配置方法,过滤掉相关代码的耗时执行。
3、支持APP层入口方法过滤,配置入口前方法运行监控。
4、支持输入法参数输出功能,方便时间高时跟踪对应输入参数。
5、提供WEB页面展示界面耗时展示、代码调用关系图展示、方法耗时百分比展示、可疑方法高亮显示等功能。
这是一个例子:
第一次:
JavaAgent是JDK1.5之后引入的,也可以称为Java代理。
JavaAgent 是一个在 main 方法之前运行的*敏*感*词*。它的默认方法名是premain,表示先执行premain方法,再执行main方法。
那么如何实现一个JavaAgent呢?很简单,只需要增加premain方法即可。
请参阅下面的代码和代码中的注释:
先写一个premain方法:
package agent;
import java.lang.instrument.Instrumentation;
public class pre_MyProgram {
/**
* 该方法在main方法之前运行,与main方法运行在同一个JVM中
* 并被同一个System ClassLoader装载
* 被统一的安全策略(security policy)和上下文(context)管理
*
* @param agentOps
* @param inst
* @author SHANHY
* @create 2016年3月30日
*/
public static void premain(String agentOps,Instrumentation inst){
System.out.println("====premain 方法执行");
System.out.println(agentOps);
}
/**
* 如果不存在 premain(String agentOps, Instrumentation inst)
* 则会执行 premain(String agentOps)
*
* @param agentOps
* @author SHANHY
* @create 2016年3月30日
*/
public static void premain(String agentOps){
System.out.println("====premain方法执行2====");
System.out.println(agentOps);
}
public static void main(String[] args) {
// TODO Auto-generated method stub
}
}
package agent;
import java.lang.instrument.Instrumentation;
public class pre_MyProgram {
/**
* 该方法在main方法之前运行,与main方法运行在同一个JVM中
* 并被同一个System ClassLoader装载
* 被统一的安全策略(security policy)和上下文(context)管理
*
* @param agentOps
* @param inst
* @author SHANHY
*/
public static void premain(String agentOps,Instrumentation inst){
System.out.println("====premain 方法执行");
System.out.println(agentOps);
}
/**
* 如果不存在 premain(String agentOps, Instrumentation inst)
* 则会执行 premain(String agentOps)
*
* @param agentOps
* @author SHANHY
*/
public static void premain(String agentOps){
System.out.println("====premain方法执行2====");
System.out.println(agentOps);
}
public static void main(String[] args) {
}
}
写完这个类,我们还需要再做一个配置。
在src目录下添加META-INF/MANIFEST.MF文件,内容定义如下:
清单版本:1.0
Premain-Class:agent.pre_MyProgram
可以重新定义类:真
特别注意,一共有四行,第四行是空行,冒号后面有一个空格,如下截图所示:
然后我们将代码打包为 pre_MyProgram.jar
注意打包的时候选择我们自己定义的MANIFEST.MF,这个是导出步骤:
(1)
(2)注意选择pre MF文件
接下来,我们正在使用 main 方法创建一个主程序项目。截图如下:
这次不要忘记:
main函数还有一个MF文件:不要写错,否则导出会报错:No main manifest attribute(表示MF文件错误)
Manifest-Version: 1.0
Main-Class: alibaba.MyProgram
以同样方式导出main的jar包命名为:MyProgram.jar
如下:
选择它的 MF 文件:
如何执行 MyProgram.jar?我们通过 -javaagent 参数指定我们的 Java 代理包。值得一提的是,-javaagent 参数的数量是无限的。如果指定了多个参数,则按照指定的顺序执行。每个代理执行完毕后,都会执行主程序的main方法。
命令如下:
C:\WINDOWS\system32>java -javaagent:C:\Users\z003fe9c\Desktop\tessdata\agent\pre
_MyProgram.jar=Hello1 -javaagent:C:\Users\z003fe9c\Desktop\tessdata\agent\pre_My
Program.jar=Hello2 -jar C:\Users\z003fe9c\Desktop\tessdata\agent\MyProgram.jar
输出结果:
====premain 方法执行
Hello1
====premain 方法执行
Hello2
=========main方法执行====
特别提醒:
(1)如果你把-javaagent放在-jar后面的话,是不会生效的,也就是放在主程序后面的agent是无效的。
例如,执行:
java -javaagent:G:\myagent.jar=Hello1 -javaagent:G:\myagent.jar=Hello2 -jar myapp.jar -javaagent:G:\myagent.jar=Hello3
(2)如果main函数忘了选择MF文件或是MF文件选择的不对,就会报错:
只有第一个有效,第三个无效。
命令中的hello1是我们传递给premain方法的字符串参数。
此时,我们将使用javaagent,但是仅仅看这个操作的效果,似乎并没有什么实际意义。
我们可以用 javaagent 做什么?下一篇文章我们将介绍如何在项目中应用javaagent。
最后,在main方法执行完之后,还有另一种执行代理的方法。因为不常用,而且主程序需要配置Agent-Class,所以不常用。如果需要了解 agentmain(String agentArgs, Instrumentation inst) 方法。
第二条:
从这里,到最后,我直接抄别人的,因为我自己的还没有调试过,但是思路很清晰:
第二篇可以直接看别人的JavaAgent应用(spring-loaded热部署),以下可以忽略:
上一篇文章简单介绍了javaagent,想了解更多可以移步“JavaAgent”
本文重点介绍JavaAgent能给我们带来什么?
基于JavaAgent的spring-loaded实现一个JavaAgent xxxxxx,实现jar包的热更新,即在不重启服务器的情况下重新加载我们更新的jar之一。一、基于JavaAgent的应用示例
在JDK5中,代理类只能通过命令行参数启动JVM时指定javaagent参数来设置。在JDK6中,不限于在启动JVM时通过配置参数设置代理类。在 JDK6 中,我们使用 Java Tool API 中的 attach 方法。在运行过程中动态设置加载代理类也很方便,达到插桩的目的。
Instrumentation 最大的作用是类定义的动态变化和操作。
最简单的例子就是计算一个方法执行所需的时间,无需修改源码,使用 Instrumentation 代理来实现这个功能。说白了,这种方式相当于在JVM层面做AOP支持,这样我们就可以在不修改应用的基础上做AOP,是不是有点悬?
创建ClassFileTransformer接口的实现类MyTransformer
实现ClassFileTransformer接口的目的是在类加载到JVM之前对类字节码进行转换,从而达到动态注入代码的目的。所以首先你要明白MonitorTransformer类的用途,就是对你要修改的类进行转换。这使用 javassist 来修改字节码。你可以暂时不关心jaavssist的原理。也可以用ASM修改字节码,只是比较麻烦。