字节码Javassist 通过字节码插桩监控方法采集运行时入参 出参和异常信息
Posted 九师兄
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了字节码Javassist 通过字节码插桩监控方法采集运行时入参 出参和异常信息相关的知识,希望对你有一定的参考价值。
1.概述
上一篇文章:【字节码】使用Javassist在运行时重新加载类「替换 原方法输出不一样的结果」 实时加载类
转载来源于:小傅哥的字节码编程-(公众号:bugstack虫洞栈)
仅供学习。
转载:https://github.com/fuzhengwei/itstack-demo-bytecode
字节码编程插桩这种技术常与 Javaagent 技术结合用在系统的非入侵监控中
,这样就可以替代在方法中进行硬编码操作。比如,你需要监控一个方法,包括;方法信息、执行耗时、出入参数、执行链路以及异常等。那么就非常适合使用这样的技术手段进行处理。
为了能让这部分最核心的内容体现出来,本文会只使用 Javassist 技术对一段方法字节码进行插桩操作,最终输出这段方法的执行信息,如下;方法 - 测试方法用于后续进行字节码增强操作
public Integer strToInt(String str01, String str02)
return Integer.parseInt(str01);
监控 - 对一段方法进行字节码增强后,输出监控信息
监控 - Begin
方法:org.itstack.demo.javassist.ApiTest.strToInt
入参:["str01","str02"] 入参[类型]:["java.lang.String","java.lang.String"] 入数
[值]:["1","2"]
出参:java.lang.Integer 出参[值]:1
耗时:59(s)
监控 - End
有了这样的监控方案,基本我们可以输出方法执行过程中的全部信息。再通过后期的完善将监控信息展示到界面,实时报警。既提升了系统的监控质量,也方便了研发排查并定位问题。
好!那么接下来我们开始一步步使用 javassist 进行字节码插桩,已达到我们的监控效果。
2.技术实现
2.1 实体类 MethodDescription
import java.util.List;
public class MethodDescription
private String clazzName; // 类名称
private String methodName; // 方法名称
private List<String> parameterNameList; // 参数名称[集合]
private List<String> parameterTypeList; // 参数类型[集合]
private String returnType; // 返回类型
public String getClazzName()
return clazzName;
public void setClazzName(String clazzName)
this.clazzName = clazzName;
public String getMethodName()
return methodName;
public void setMethodName(String methodName)
this.methodName = methodName;
public List<String> getParameterNameList()
return parameterNameList;
public void setParameterNameList(List<String> parameterNameList)
this.parameterNameList = parameterNameList;
public List<String> getParameterTypeList()
return parameterTypeList;
public void setParameterTypeList(List<String> parameterTypeList)
this.parameterTypeList = parameterTypeList;
public String getReturnType()
return returnType;
public void setReturnType(String returnType)
this.returnType = returnType;
2.2 Monitor类
import com.alibaba.fastjson.JSON;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReferenceArray;
public class Monitor
public static final int MAX_NUM = 1024 * 32;
private final static AtomicInteger index = new AtomicInteger(0);
private final static AtomicReferenceArray<MethodDescription> methodTagArr = new AtomicReferenceArray<>(MAX_NUM);
public static int generateMethodId(String clazzName, String methodName, List<String> parameterNameList, List<String> parameterTypeList, String returnType)
MethodDescription methodDescription = new MethodDescription();
methodDescription.setClazzName(clazzName);
methodDescription.setMethodName(methodName);
methodDescription.setParameterNameList(parameterNameList);
methodDescription.setParameterTypeList(parameterTypeList);
methodDescription.setReturnType(returnType);
int methodId = index.getAndIncrement();
if (methodId > MAX_NUM) return -1;
methodTagArr.set(methodId, methodDescription);
return methodId;
public static void point(final int methodId, final long startNanos, Object[] parameterValues, Object returnValues)
MethodDescription method = methodTagArr.get(methodId);
System.out.println("监控 - Begin");
System.out.println("方法:" + method.getClazzName() + "." + method.getMethodName());
System.out.println("入参:" + JSON.toJSONString(method.getParameterNameList()) + " 入参[类型]:" + JSON.toJSONString(method.getParameterTypeList()) + " 入数[值]:" + JSON.toJSONString(parameterValues));
System.out.println("出参:" + method.getReturnType() + " 出参[值]:" + JSON.toJSONString(returnValues));
System.out.println("耗时:" + (System.nanoTime() - startNanos) / 1000000 + "(s)");
System.out.println("监控 - End\\r\\n");
public static void point(final int methodId, Throwable throwable)
MethodDescription method = methodTagArr.get(methodId);
System.out.println("监控 - Begin");
System.out.println("方法:" + method.getClazzName() + "." + method.getMethodName());
System.out.println("异常:" + throwable.getMessage());
System.out.println("监控 - End\\r\\n");
2.3 主体测试类
import com.agent.entity.MonitorMethod;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.bytecode.AccessFlag;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.LocalVariableAttribute;
import javassist.bytecode.MethodInfo;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
/**
* @author lcc
* @type GenerateClazzMethodV2
* @desc
* @date 2022/6/26 17:22
*/
public class GenerateClazzMethodV2 extends ClassLoader
public void strToInt() throws Exception
ClassPool pool = ClassPool.getDefault();
// 获取类
CtClass ctClass = pool.get(MonitorMethod.class.getName());
ctClass.replaceClassName("MonitorMethod", "MonitorMethod02");
String clazzName = ctClass.getName();
// 获取方法
CtMethod ctMethod = ctClass.getDeclaredMethod("strToInt");
String methodName = ctMethod.getName();
// 方法信息:methodInfo.getDescriptor();
MethodInfo methodInfo = ctMethod.getMethodInfo();
// 方法:入参信息
CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
CtClass[] parameterTypes = ctMethod.getParameterTypes();
boolean isStatic = (methodInfo.getAccessFlags() & AccessFlag.STATIC) != 0; // 判断是否为静态方法
int parameterSize = isStatic ? attr.tableLength() : attr.tableLength() - 1; // 静态类型取值
List<String> parameterNameList = new ArrayList<>(parameterSize); // 入参名称
List<String> parameterTypeList = new ArrayList<>(parameterSize); // 入参类型
StringBuilder parameters = new StringBuilder(); // 参数组装;$1、$2...,$$可以获取全部,但是不能放到数组初始化
for (int i = 0; i < parameterSize; i++)
parameterNameList.add(attr.variableName(i + (isStatic ? 0 : 1))); // 静态类型去掉第一个this参数
parameterTypeList.add(parameterTypes[i].getName());
if (i + 1 == parameterSize)
parameters.append("$").append(i + 1);
else
parameters.append("$").append(i + 1).append(",");
// 方法:出参信息
CtClass returnType = ctMethod.getReturnType();
String returnTypeName = returnType.getName();
// 方法:生成方法唯一标识ID
int idx = Monitor.generateMethodId(clazzName, methodName, parameterNameList, parameterTypeList, returnTypeName);
// 定义属性
ctMethod.addLocalVariable("startNanos", CtClass.longType);
ctMethod.addLocalVariable("parameterValues", pool.get(Object[].class.getName()));
// 方法前加强
ctMethod.insertBefore(" startNanos = System.nanoTime(); parameterValues = new Object[]" + parameters.toString() + "; ");
// 方法后加强
ctMethod.insertAfter(" com.agent.introduction.Monitor.point(" + idx + ", startNanos, parameterValues, $_);", false); // 如果返回类型非对象类型,$_ 需要进行类型转换
// 方法;添加TryCatch
ctMethod.addCatch(" com.agent.introduction.Monitor.point(" + idx + ", $e); throw $e; ", ClassPool.getDefault().get("java.lang.Exception")); // 添加异常捕获
// 输出类的内容
ctClass.writeFile();
// 测试调用
byte[] bytes = ctClass.toBytecode();
Class<?> clazzNew = new GenerateClazzMethodV2().defineClass("com.agent.entity.MonitorMethod", bytes, 0, bytes.length);
// 反射获取 main 方法
Method method = clazzNew.getMethod("strToInt", String.class, String.class);
Object obj_01 = method.invoke(clazzNew.newInstance(), "1", "2");
System.out.println("正确入参:" + obj_01);
Object obj_02 = method.invoke(clazzNew.newInstance(), "a", "b");
System.out.println("异常入参:" + obj_02);
运行结果如下
监控 - Begin
方法:com.agent.entity.MonitorMethod.strToInt
入参:["str01","str02"] 入参[类型]:["java.lang.String","java.lang.String"] 入数[值]:["1","2"]
出参:java.lang.Integer 出参[值]:1
耗时:251(s)
监控 - End
正确入参:1
监控 - Begin
方法:com.agent.entity.MonitorMethod.strToInt
异常:For input string: "a"
监控 - End
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.agent.introduction.GenerateClazzMethodV2.strToInt(GenerateClazzMethodV2.java:94)
at com.agent.entity.MonitorMethodTest.strToInt(MonitorMethodTest.java:27)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100以上是关于字节码Javassist 通过字节码插桩监控方法采集运行时入参 出参和异常信息的主要内容,如果未能解决你的问题,请参考以下文章
Android AOP编程——Gradle插件+TransformAPI+字节码插桩实战