1. 搭建一个工程测试premain在main之前执行

需求很简单,只需要让premain方法在main之前输出就行

  • premain类
package com.wql;import java.lang.instrument.Instrumentation;/*** @author wql* @date 2021/11/28 15:59*/
public class JavaAgentMain {public static void premain(String arg, Instrumentation instrumentation) {System.out.println("我是premain方法");}
}
  • HelloWql类
package com.wql;/*** @author wql* @date 2021/11/28 15:09*/
public class HelloWql {public  void helloWql(String name) {System.out.println("name");}
}
  • TestJavaAgentMain
package com.wql.test;/*** @author wql* @date 2021/11/28 16:01*/
public class TestJavaAgentMain {public static void main(String[] args) {System.out.println("我是main方法");}
}
  • pom 文件
    因为我是借助maven打包普通工程的,所以maven里面就没有啥依赖目前
<?xml version="1.0" encoding="UTF-8"?>
<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>testAgent</groupId><artifactId>testAgent</artifactId><version>1.0.0-SNAPSHOT</version><build><plugins><!--maven打包插件--><!--maven-jar-plugin用于生成META-INF/MANIFEST.MF文件的部分内容--><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-jar-plugin</artifactId><version>2.2</version><configuration><classesDirectory>target/classes/</classesDirectory><archive><manifestEntries><Project-name>${project.name}</Project-name><Project-version>${project.version}</Project-version><Premain-Class>com.wql.JavaAgentMain</Premain-Class><Can-Redefine-Classes>false</Can-Redefine-Classes><Can-Retransform-Classes>false</Can-Retransform-Classes></manifestEntries></archive><skip>true</skip></configuration></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>2.3.1</version><configuration><source>1.8</source><target>1.8</target><encoding>utf-8</encoding></configuration></plugin></plugins></build></project>
  • 打包
  • 在测试类里面配置生成的javaahent的jar包
  • 启动测试类测试:

2. 需求2

在helloWql方法执行之前打印当前时间

2.1 Javassist介绍

Javassit是一个开源的分析,编辑,和创建Java字节码的类库,其主要优点在于简单,而且快速,直接使用java编码的形式,而不需要了解java虚拟机指令,就能动态改变类结构,或者动态生成
注: 也可以使用ASM实现,但是需要学会操作字节码指令,学习成本高

运行时.jar javaagent.jar
入口方法 main(String[]) premain(String,instrumcent)
参数 main-class premain-class
运行 java-jar xxx.jar 只能附着于其他程序运行 -javaagent:xxx.jar

所以我们可以采用Javassis技术来完成插桩

  <dependency><groupId>org.javassist</groupId><artifactId>javassist</artifactId><version>3.18.1-GA</version></dependency>

官方文档: http://www.javassist.org/tutorial/tutorial2.html

2.2 使用javassit完成需求2

public class JavaAgentMain {public static void premain(String arg, Instrumentation instrumentation) {System.out.println("premain执行了");//基于javassist工具,在运行时修改class字节码,就是所谓的插桩final ClassPool classPool = new ClassPool();classPool.appendSystemPath();instrumentation.addTransformer((loader, className, classBeingRedefined, protectionDomain, classfileBuffer) -> {// 注意,这里是以/结尾if (!"com/wql/HelloWql".equals(className)) {return new byte[0];}//千万不要使用 HelloWql.class.getName() ,因为使用了就会加载,不会走插桩CtClass ctClass = null;try {// 获取类ctClass = classPool.get("com.wql.HelloWql");//获取类里面的方法CtMethod helloWql = ctClass.getDeclaredMethod("helloWql");//在方法前加入指令码helloWql.insertBefore("System.out.println(\"System.currentTimeMillis() = \" + System" +".currentTimeMillis());");return ctClass.toBytecode();} catch (Exception e) {e.printStackTrace();}return new byte[0];});}
}

重新打包运行,结果如下:

3. 需求3

在方法前面添加时间并且计算时间差

注意,插桩插入的是一个个代码块

举例:

这样写会报以下错误:

因为上面的代码就等于下面这种:

每一个{}都代表一个模块,变量是不可用跨模块的。所以会报变量end找不到的错误

所以我们会采用如下思想:

复制一个代理方法出来,就可以搞定,映射到字节码插桩里面,javassit也可以这样玩

public class JavaAgentMain {public static void premain(String arg, Instrumentation instrumentation) {System.out.println("premain执行了");//基于javassist工具,在运行时修改class字节码,就是所谓的插桩final ClassPool classPool = new ClassPool();classPool.appendSystemPath();instrumentation.addTransformer((loader, className, classBeingRedefined, protectionDomain, classfileBuffer) -> {// 注意,这里是以/结尾if (!"com/wql/HelloWql".equals(className)) {return new byte[0];}//千万不要使用 HelloWql.class.getName() ,因为使用了就会加载,不会走插桩CtClass ctClass = null;try {// 获取类ctClass = classPool.get("com.wql.HelloWql");//获取类里面的方法CtMethod helloWql = ctClass.getDeclaredMethod("helloWql");//在方法前加入指令码//1. 复制方法CtMethod copy = CtNewMethod.copy(helloWql, ctClass, null);// 给新的方法换一个名字copy.setName("helloWql$Agent");//将这个方法添加到类里面ctClass.addMethod(copy);// 2. 改变原有方法helloWql.setBody(" {long satrt = System.currentTimeMillis();\n" +"helloWql$Agent($1);" +"                long end = System.currentTimeMillis();\n" +"                System.out.println(\"end - satrt = \" + (end - satrt));}");return ctClass.toBytecode();} catch (Exception e) {e.printStackTrace();}return new byte[0];});}
}

注意几个细节:

  • 每一行代码执行完必须加上;结束符
  • 代码块需要使用{}包起来
  • 方法与方法之间传参,参数使用$0 $1 …表示,$0代表this

4. 需求4

假设现在项目里面有100个方法,我想检测每一个方法的执行时间,该怎么操作

package com.wql;import javassist.*;import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.util.Objects;/*** @author wql* @date 2021/11/28 15:59*/
public class JavaAgentMain {public static void premain(String arg, Instrumentation instrumentation) {final String config = arg;final ClassPool classPool = new ClassPool();classPool.appendSystemPath();instrumentation.addTransformer((loader, className, classBeingRedefined, protectionDomain, classfileBuffer) -> {if (className == null || !className.replaceAll("/", ".").startsWith(config)) {return new byte[0];}className = className.replaceAll("/", ".");try {CtClass ctClass = classPool.get(className);for (CtMethod declaredMethod : ctClass.getDeclaredMethods()) {newMethod(declaredMethod);}return ctClass.toBytecode();} catch (NotFoundException e) {e.printStackTrace();} catch (CannotCompileException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}return new byte[0];});}final static String source = "{ long begin = System.currentTimeMillis();\n" +" String method = Thread.currentThread() .getStackTrace()[1].getMethodName();"+"        Object result;\n" +"        try {\n" +"            result = ($w)%s$Agent($$);\n" +"        } finally {\n" +"            long end = System.currentTimeMillis();\n" +"            System.out.println(\"方法名:\"+method+\"=> 耗时:\"+(end-begin)+\"ms\");\n" +"        }\n" +"        return ($r)result;\n}";final static String voidSource = "{ long begin = System.currentTimeMillis();\n" +" String method = Thread.currentThread() .getStackTrace()[1].getMethodName();"+"        try {\n" +"            %s$Agent($$);\n" +"        } finally {\n" +"            long end = System.currentTimeMillis();\n" +"            System.out.println(\"方法名:\"+method+\"=> 耗时:\"+(end-begin)+\"ms\");\n" +"        }\n" +"       }";private static CtMethod newMethod(CtMethod oldMethod) throws CannotCompileException, NotFoundException {CtMethod copy = CtNewMethod.copy(oldMethod, oldMethod.getDeclaringClass(), null);copy.setName(oldMethod.getName() + "$Agent");//添加新方法oldMethod.getDeclaringClass().addMethod(copy);if (oldMethod.getReturnType().equals(CtClass.voidType)) {oldMethod.setBody(String.format(voidSource, oldMethod.getName()));} else {oldMethod.setBody(String.format(source, oldMethod.getName()));}return copy;}
}

修改以下插桩写法就行,上面代码有几个地方解释一下

  • $$ 表示多个参数,arg1,arg2,arg3
  • $1 表示 arg1
  • $2 表示 arg2
  • $args 表示 object[]
  • $w 表示类型转换 eg: (String)object
  • $r 表示返回值

最后记得配置一下参数就行

Java字节码插桩玩法(Javassist)相关推荐

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

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

  2. Java ASM框架与字节码插桩的常见用法(生成类,修改类,方法插桩,方法注入)

    前言 ASM 是一款读写Java字节码的工具,可以达到跳过源码编写,编译,直接以字节码的形式创建类,修改已经存在类(或者jar中的class)的属性,方法等. 通常用来开发一些Java开发的辅助框架, ...

  3. 字节码插桩之Java Agent

    字节码插桩之Java Agent 本篇文章将详细讲解有关Java Agent的知识,揭开它神秘的面纱,帮助开发人员了解它的黑魔法,帮助我们完成更多业务需求 What is Java Agent Jav ...

  4. 调研字节码插桩技术,用于系统监控设计和实现

    作者:小傅哥 博客:https://bugstack.cn ❝ 沉淀.分享.成长,让自己和他人都能有所收获!???? ❞ 目录 一.来自深夜的电话! 二.准备工作 三.使用 AOP 做个切面监控 1. ...

  5. Android AOP之字节码插桩

    背景   本篇文章基于<网易乐得无埋点数据收集SDK>总结而成,关于网易乐得无埋点数据采集SDK的功能介绍以及技术总结后续会有文章进行阐述,本篇单讲SDK中用到的Android端AOP的实 ...

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

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

  7. 看完这一篇,你也可以自如地掌握字节码插桩

    /   今日科技快讯   / 近日,一些国家的黑客频繁对俄罗斯发动网络攻击,以阻止它们正常运行.未来几天,俄罗斯可能与全球互联网断开.针对网络威胁,俄罗斯政府准备启动自己的"大局域网&quo ...

  8. Android字节码插桩

    什么是字节码插桩 字节码插桩就是在构建的过程中,通过修改已经编译完成的字节码文件,也就是class文件,来实现功能的添加. 简单来讲,我们要实现无埋点对客户端的全量统计.这里的统计概括的范围比较广泛, ...

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

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

  10. 字节码插桩(四): AST

    我们通过 AndroidStudio 生成Bean对象一般是通过注解来实现自动生成getter/setter方法.equals()和hashCode()方法,其中类(或接口)要符合驼式命名法,首字母大 ...

最新文章

  1. 算法笔记_218:花朵数(Java)
  2. mysql 5.7 存储引擎_mysql5.7——innodb存储引擎总结
  3. 查看电脑python虚拟环境-Linux系统创建python虚拟环境
  4. Vue.js——60分钟快速入门
  5. java:socket通信
  6. 推荐系统笔记:无任何限制的矩阵分解
  7. RabbitMQ——无法连接错误[AmqpConnectException: java.net.ConnectException: Connection refused: connect]解决方案
  8. 【博客话题】技术生涯中的出与入
  9. linux 查看端口 程序,linux开发:Linux下查看端口占用
  10. LeetCode 127. 单词接龙(图的BFS/双向BFS)
  11. 常用函数式接口-Consumer
  12. Werkzeug Turorial
  13. c语言数组中循坏移动问题,如何将一个数组的元素循环左移?
  14. 如何将IP地址批量改变为城域网的IP地址
  15. linux 上如何测速
  16. linux添加静态ipv6路由,请问如何在CentOS7上配置已经静态路由好的IPv6地址块?
  17. 等保-机房项目验收方法
  18. 提示502的解决办法
  19. viewpager 与 pageradapter
  20. python爬取阿里巴巴网站实现

热门文章

  1. 开源加速器Gemmini代码解析(一):脉动阵列
  2. 【芝麻背调百科】已婚员工入职新公司后申请休婚假,公司能否拒绝?
  3. 【进击全栈 2,毕向东Java教程百度云
  4. telnet 命令参数及其应用方式
  5. 修改 xweibo 的memcache代码,让xweibo支持wincache,加快xweibo速度
  6. 泛微oa系统服务器怎么填写,泛微协同办公平台Ecology8安装部署手册(105页)-原创力文档...
  7. NB-IoT 无线烟感在文物古建筑的防火安全应用
  8. 串口转以太网使用方法
  9. 计算机网络(二)—— 物理层(1、2、3):物理层的基本概念、物理层的下面的传输媒体、传输方式
  10. hbase 2.0.5的下载及安装