本文介绍一下,当下比较基础但是使用场景却很多的一种技术,稍微偏底层点,就是字节码插桩技术了...,如果之前大家熟悉了asm,cglib以及javassit等技术,那么下面说的就很简单了...,因为下面要说的功能就是基于javassit实现的,接下来先从javaagent的原理说起,最后会结合一个完整的实例演示实际中如何使用。

1、什么是javassist?

Javassist是一个开源的分析、编辑和创建Java字节码的类库。其主要的特点,在于简单,而且快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成

2、Javassist 作用?

a.运行时监控插桩埋点

b.AOP动态代理实现(性能上比Cglib生成的要慢)

c.获取访问类结构信息:如获取参数名称信息

3、Javassist使用流程

4、 如何对WEB项目对象进行字节码插桩

1.统一获取HttpRequest请求参数插桩示例

2.获取HttpRequest参数遇到ClassNotFound的问题

3.Tomcat ClassLoader介绍,及javaagent jar包加载机制

4.通过class加载沉机制实现在javaagent引用jar包

javaagent的主要功能有哪些?

  1. 可以在加载java文件之前做拦截把字节码做修改
  2. 获取所有已经被加载过的类
  3. 获取所有已经被初始化过了的类(执行过了clinit方法,是上面的一个子集)
  4. 获取某个对象的大小
  5. 将某个jar加入到bootstrapclasspath里作为高优先级被bootstrapClassloader加载
  6. 将某个jar加入到classpath里供AppClassloard去加载
  7. 设置某些native方法的前缀,主要在查找native方法的时候做规则匹配

定义一个业务类,类里面定义几个方法,然后在执行这个方法的时候,会动态实现方法的耗时统计。

看业务类定义:

package com.dxz.chama.service;import java.util.LinkedList;
import java.util.List;/*** 模拟数据插入服务**/
public class InsertService {public void insert2(int num) {List<Integer> list = new LinkedList<>();for (int i = 0; i < num; i++) {list.add(i);}}public void insert1(int num) {List<Integer> list = new LinkedList<>();for (int i = 0; i < num; i++) {list.add(i);}}public void insert3(int num) {List<Integer> list = new LinkedList<>();for (int i = 0; i < num; i++) {list.add(i);}}
}

删除服务:

package com.dxz.chama.service;import java.util.List;public class DeleteService {public void delete(List<Integer>list){for (int i=0;i<list.size();i++){list.remove(i);}}
}

ok,接下来就是要编写javaagent的相关实现:

定义agent的入口

package com.dxz.chama.javaagent;import java.lang.instrument.Instrumentation;/*** agent的入口类*/
public class TimeMonitorAgent {// peremain 这个方法名称是固定写法 不能写错或修改public static void premain(String agentArgs, Instrumentation inst) {System.out.println("execute insert method interceptor....");System.out.println(agentArgs);// 添加自定义类转换器inst.addTransformer(new TimeMonitorTransformer(agentArgs));}
}

接下来看最重要的Transformer的实现:

package com.dxz.chama.javaagent;import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.reflect.Modifier;
import java.security.ProtectionDomain;
import java.util.Objects;import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;/*** 类方法的字节码替换*/
public class TimeMonitorTransformer implements ClassFileTransformer {private static final String START_TIME = "\nlong startTime = System.currentTimeMillis();\n";private static final String END_TIME = "\nlong endTime = System.currentTimeMillis();\n";private static final String METHOD_RUTURN_VALUE_VAR = "__time_monitor_result";private static final String EMPTY = "";private String classNameKeyword;public TimeMonitorTransformer(String classNameKeyword){this.classNameKeyword = classNameKeyword;}/**** @param classLoader 默认类加载器* @param className  类名的关键字 因为还会进行模糊匹配* @param classBeingRedefined* @param protectionDomain* @param classfileBuffer* @return* @throws IllegalClassFormatException*/public byte[] transform(ClassLoader classLoader, String className, Class<?> classBeingRedefined,ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {className = className.replace("/", ".");CtClass ctClass = null;try {//使用全称,用于取得字节码类ctClass = ClassPool.getDefault().get(className);//匹配类的机制是基于类的关键字 这个是客户端传过来的参数 满足就会获取所有的方法 不满足跳过if(Objects.equals(classNameKeyword, EMPTY)||(!Objects.equals(classNameKeyword, EMPTY)&&className.indexOf(classNameKeyword)!=-1)){//所有方法CtMethod[] ctMethods = ctClass.getDeclaredMethods();//遍历每一个方法for(CtMethod ctMethod:ctMethods){//修改方法的字节码transformMethod(ctMethod, ctClass);   }}//重新返回修改后的类return ctClass.toBytecode();} catch (Exception e) {e.printStackTrace();}return null;}/*** 为每一个拦截到的方法 执行一个方法的耗时操作* @param ctMethod* @param ctClass* @throws Exception*/private void transformMethod(CtMethod ctMethod, CtClass ctClass) throws Exception {// 抽象的方法是不能修改的,或者方法前面加了final关键字if ((ctMethod.getModifiers() & Modifier.ABSTRACT) > 0) {return;}//获取原始方法名称String methodName = ctMethod.getName();String monitorStr = "\nSystem.out.println(\"method " + ctMethod.getLongName() + " cost:\" + (endTime - startTime) + \"ms.\");";//实例化新的方法名称String newMethodName = methodName + "$impl";//设置新的方法名称ctMethod.setName(newMethodName);//创建新的方法,复制原来的方法,名字为原来的名字CtMethod newMethod = CtNewMethod.copy(ctMethod, methodName, ctClass, null);StringBuilder bodyStr = new StringBuilder();//拼接新的方法内容bodyStr.append("{");//返回类型CtClass returnType = ctMethod.getReturnType();//是否需要返回boolean hasReturnValue = (CtClass.voidType != returnType);if (hasReturnValue) {String returnClass = returnType.getName();bodyStr.append("\n").append(returnClass + " " + METHOD_RETURN_VALUE_VAR + ";");}bodyStr.append(START_TIME);if (hasReturnType) {bodyStr.append("\n").append(METHOD_RETURN_VALUE_VAR + " = ($r)" + newMethodName + "($$);");} else {bodyStr.append("\n").append(newMethodName + "($$);");}bodyStr.append(END_TIME);bodyStr.append(monitorStr);if (hasReturnValue) {bodyStr.append("\n").append("return " + METHOD_RETURN_VALUE_VAR + " ;");}bodyStr.append("}");//替换新方法newMethod.setBody(bodyStr.toString());//增加新方法ctClass.addMethod(newMethod);}
}

其实也很简单就两个类就实现了要实现的功能,那么如何使用呢?需要把上面的代码打成jar包才能执行,建议大家使用maven打包,下面是pom.xml的配置文件

<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.dxz</groupId><artifactId>chama</artifactId><version>0.0.1-SNAPSHOT</version><packaging>jar</packaging><name>chama</name><url>http://maven.apache.org</url><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>javassist</groupId><artifactId>javassist</artifactId><version>3.12.1.GA</version></dependency><!-- https://mvnrepository.com/artifact/cglib/cglib --><dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.2.5</version></dependency><!-- https://mvnrepository.com/artifact/oro/oro --><dependency><groupId>oro</groupId><artifactId>oro</artifactId><version>2.0.8</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>3.8.1</version><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><source>1.8</source><target>1.8</target><encoding>utf-8</encoding></configuration></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-shade-plugin</artifactId><version>3.0.0</version><executions><execution><phase>package</phase><goals><goal>shade</goal></goals><configuration><transformers><transformerimplementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"><manifestEntries><Premain-Class>com.dxz.chama.javaagent.TimeMonitorAgent</Premain-Class></manifestEntries></transformer></transformers></configuration></execution></executions></plugin></plugins></build>
</project>

强调一下,红色标准的非常关键,因为如果要想jar能够运行,必须要把运行清单打包到jar中,且一定要让jar的主类是Permain-Class,否则无法运行,运行清单的目录是这样的.

mvn -clean package

如果打包正确的话,里面的内容应该如下所示:

OK至此整体代码和打包就完成了,那么接下来再讲解如何使用

部署方式:

1 基于IDE开发环境运行

首先,编写一个service的测试类如下:

package com.dxz.chama.service;import java.util.LinkedList;
import java.util.List;public class ServiceTest {public static void main(String[] args) {// 插入服务InsertService insertService = new InsertService();// 删除服务DeleteService deleteService = new DeleteService();System.out.println("....begnin insert....");insertService.insert1(1003440);insertService.insert2(2000000);insertService.insert3(30003203);System.out.println(".....end insert.....");List<Integer> list = new LinkedList<>();for (int i = 0; i < 29988440; i++) {list.add(i);}System.out.println(".....begin delete......");deleteService.delete(list);System.out.println("......end delete........");}
}

选择编辑配置:如下截图所示

service是指定要拦截类的关键字,如果这里的参数是InsertService,那么DeleteService相关的方法就无法拦截了。同理也是一样的。

chama-0.0.1-SNAPSHOT.jar这个就是刚刚编写那个javaagent类的代码打成的jar包,ok 让我们看一下最终的效果如何:

实际应用场景中,可以把这些结果写入到log然后发送到es中,就可以做可视化数据分析了...还是蛮强大的,接下来对上面的业务进行扩展,因为上面默认是拦截类里面的所有方法,如果业务需求是拦截类的特定的方法该怎么实现呢?其实很简单就是通过正则匹配,下面给出核心代码:

定义入口agent:

package com.dxz.chama.javaagent.patter;
import java.lang.instrument.Instrumentation;public class TimeMonitorPatterAgent {public static void premain(String agentArgs, Instrumentation inst) {inst.addTransformer(new PatternTransformer());}
}

定义transformer:

package com.dxz.chama.javaagent.patter;import javassist.CtClass;
import org.apache.oro.text.regex.PatternCompiler;
import org.apache.oro.text.regex.PatternMatcher;
import org.apache.oro.text.regex.Perl5Compiler;
import org.apache.oro.text.regex.Perl5Matcher;import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;public class PatternTransformer implements ClassFileTransformer {@Overridepublic byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {PatternMatcher matcher = new Perl5Matcher();PatternCompiler compiler = new Perl5Compiler();// 指定的业务类String interceptorClass = "com.dxz.chama.service.InsertService";// 指定的方法String interceptorMethod = "insert1";try {if (matcher.matches(className, compiler.compile(interceptorClass))) {ByteCode byteCode = new ByteCode(0;CtClass ctClass = byteCode.modifyByteCode(interceptorClass, interceptorMethod);return ctClass.toBytecode(0;}} catch (Exception e) {e.printStackTrace();}return null;}
}

修改字节码的实现:

package com.dxz.chama.javaagent.patter;import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;public class ByteCode {public CtClass modifyByteCode(String className, String method) throws Exception {ClassPool classPool = ClassPool.getDefault();CtClass ctClass = classPool.get(className);CtMethod oldMethod = ctClass.getDeclaredMethod(method);String oldMethodName = oldMethod.getName(0;String newName = oldMethodName + "$impl";oldMethod.setName(newName);CtMethod newMethod = CtNewMethod.copy(oldMethod, oldMethodName, ctClass, null);StringBuffer sb = newe StringBuffer();sb.append("{");sb.append("\nSystem.out.println(\"start to modify bytecode\"); \n");sb.append(newName + "($$);\n");sb.append("System.out.println(\"call method" + oldMethodName + "took\"+(System.currentTimeMillis()-start))");sb.append("}");newMethod.setBody(sb.toString());ctClass.addMethod(newMethod);return ctClass;}
}

OK,

修改下pom中的

<manifestEntries><Premain-Class>com.dxz.chama.javaagent.patter.TimeMonitorPatterAgent</Premain-Class>
</manifestEntries>

这个时候再重新打包,然后修改上面的运行配置之后再看效果,只能拦截到insert1方法

最后 再说一下如何使用jar运行,其实很简单如下:把各个项目都打成jar,比如把上面的service打成service.jar,然后使用java命令运行:

java -javaagent:d://chama-0.0.1-SNAPSHOT.jar=Service -jar service.jar,效果是一样的!

JVM插桩之三:javaagent介绍及javassist介绍相关推荐

  1. JVM插码之三:javaagent介绍及javassist介绍

    本文介绍一下,当下比较基础但是使用场景却很多的一种技术,稍微偏底层点,就是字节码插庄技术了...,如果之前大家熟悉了asm,cglib以及javassit等技术,那么下面说的就很简单了...,因为下面 ...

  2. JVM插桩之二:Java agent基础原理

    Javaagent只要作用在class被加载之前对其加载,插入我们需要添加的字节码. Javaagent面向的是我们java程序员,而且agent都是用java编写的,不需要太多的c/c++编程基础, ...

  3. JVM插桩之一:JVM字节码增强技术介绍及入门示例

    字节码增强技术:AOP技术其实就是字节码增强技术,JVM提供的动态代理追根究底也是字节码增强技术. 目的:在Java字节码生成之后,对其进行修改,增强其功能,这种方式相当于对应用程序的二进制文件进行修 ...

  4. JVM插桩之四:Java动态代理机制的对比(JDK和CGLIB,Javassist,ASM)

    一.class文件简介及加载 Java编译器编译好Java文件之后,产生.class 文件在磁盘中.这种class文件是二进制文件,内容是只有JVM虚拟机能够识别的机器码.JVM虚拟机读取字节码文件, ...

  5. 字节码插桩(javassist)之插入代码块|IOC框架(Hilt)之对象注入~研究

    Hilt对象注入 | javassist插桩 研究 Hilt对象注入 javassist字节码插桩 创建buildSrc的module 重写Transform 熟悉TransformInvocatio ...

  6. APM - 使用JavaAgent+Javassit 插桩C3P0

    文章目录 核心思想 编码实现 打包 配置验证 核心思想 <bean id="dataSource" class="com.mchange.v2.c3p0.Combo ...

  7. aop 获取方法入参出参_ASM字节码编程 | JavaAgent+ASM字节码插桩采集方法名称及入参和出参结果并记录方法耗时...

    作者:小傅哥 博客:bugstack.cn ❝ 沉淀.分享.成长,让自己和他人都能有所收获! ❞ 一.前言 在我们实际的业务开发到上线的过程中,中间都会经过测试.那么怎么来保证测试质量呢?比如:提交了 ...

  8. Java成神之路——javaAgent(插桩,attach)

    javaAgent Javaagent 是什么? javaAgent运行类加载器在加载类之前对类做出动态的修改. 运行java命令执行时添加参数 -javaagent指定打包好的agent的jar即可 ...

  9. ASM字节码编程 | JavaAgent+ASM字节码插桩采集方法名称以及入参和出参结果并记录方法耗时

    作者:小傅哥 博客:bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 在我们实际的业务开发到上线的过程中,中间都会经过测试.那么怎么来保证测试质量呢?比如:提交了多少代码 ...

最新文章

  1. java 堆大小_适当的Java堆大小的5个技巧
  2. mysql sum很慢,可以在MySQL中加快sum()吗?
  3. java分页数据导出excel
  4. volatile关键字的使用
  5. [国家集训队]middle
  6. 《Pytorch - CNN模型》
  7. php镂空窗,木窗镂空的雕刻象征着什么?黑色在彝族人民心中有何寓意?陆川猪为何是著名“陆川三宝”之一?...
  8. 大数据产业发展 三大模式可毕其功于一役
  9. 黑马程序员_java基础笔记(09)...HTML基本知识、CSS、JavaScript、DOM
  10. 【leetcode】1041. Robot Bounded In Circle
  11. centos7 安装java8
  12. 数学建模 ---斯皮尔曼相关系数
  13. AD 画PCB 布线时,连线就变绿
  14. android刷机工具mac版,刷机精灵mac版
  15. 最小二乘估计与卡尔曼滤波公式推导
  16. PMBOK(第六版) 学习笔记 ——《第八章 项目质量管理》
  17. R语言中的ggmap包
  18. 078 周期函数定积分性质及定积分三大性质总结
  19. 关于 js 闭包的理解
  20. 这年头,你还能不懂点IP地址吗?

热门文章

  1. k8s安装部署步骤_30分钟无坑部署K8S单Master集群
  2. 01.search_api_综述
  3. C++ cin cout
  4. 这或许是讲解Knuth(Shuffle)洗牌算法最好的文章
  5. 蓝桥杯 试题 基础练习 特殊回文数——16行代码AC
  6. ctype.h(cctype) 头文件函数大全
  7. Web前端开发笔记——第四章 JavaScript程序设计 第四节 条件语句和循环语句
  8. c++判断正在使用的显卡_7°C警告:廉价硅脂害死显卡系列!还在用¥5块钱一大碗的导热膏吗...
  9. 应付账款账龄分析模板_超全的财务会计表单模板分享
  10. android wear2.9新功能,Android Wear 2.0确认2月9日正式登场