IntelliJ IDEA 插件与 Byte Buddy 字节码插桩在 CodeGuide 中落地研发交付质量自动分析【免费下载链接】CodeGuide:books: 本代码库是作者小傅哥多年从事一线互联网 Java 开发的学习历程技术汇总旨在为大家提供一个清晰详细的学习教程侧重点更倾向编写Java核心内容。如果本仓库能为您提供帮助请给予支持(关注、点赞、分享)项目地址: https://gitcode.com/gh_mirrors/code/CodeGuide本文以 CodeGuide 仓库中「《IntelliJ IDEA 插件开发》第10节基于字节码插桩采集数据实现代码交付质量自动分析」为核心骨架结合仓库内 Byte Buddy 系列、JavaAgent 系列与 IDEA 插件探针系列文档完整还原一套研发编码过程即采集、字节码插桩即增强、服务端汇总即评估的交付质量分析方案。读者读完将掌握如何用premain入口与-javaagent挂载探针、如何用 Byte Buddy 的Origin/SuperCall/AllArguments注解采集方法出入参与耗时、如何通过继承DefaultJavaProgramRunner或JavaProgramPatcher让 IDEA 插件在启动运行时自动注入探针最终把代码质量评估从提测后置提前到开发过程中。一、背景研发交付质量为什么要前置评估业务提需求、产品定方案、研发做实现、测试验流程四个角色的相互配合是确保一个需求上线的必备条件。在整个需求的交付质量级别划分中研发与测试是非常重的一环——如果研发提测的代码质量不高就会出现不同级别的修 BUG、返工甚至重做的风险。那么怎么来提高代码质量呢一般我们都会要求研发在开发代码的过程中编写单元测试验证自己的代码逻辑。如果最终单元测试覆盖度不足可以由测试拒绝研发提测。但是整个需求实现的代码是在全部开发完成后提测的也就是临近上线的最后一环大家才知道某个研发的某个功能域的实现是否具备提测条件。如果这个时候代码质量不高那么接下来就是项目风险的时候——压测试时间、调上线时间总之有病拖着最后成大病了。当然你可以在项目开发期间定期排查代码或者通过日会进度反馈等手段。但这样需要耗费大量时间的 1 对 1 人工排查方式很难满足复杂流程的较大型项目开发而且对项目风险把控也是不可预估的。所以我们希望采集研发在开发过程中的执行动作把风险判断提前。实际操作举例就是当你开发完成一个接口、开始测试运行时插件就可以采集到这个接口的全部信息包括接口名称、入参类型和内容、出参类型和内容、异常信息、调用关系链等。这些信息汇总提交到服务端后可以生成本次需求代码分支下的全部接口动作以及各系统间的关系链路并附带随时生成最新的接口文档和一键测试验证功能。后期测试人员介入时就可以参考研发在编码过程中的全部测试用例也可以查看整个功能的覆盖程度测试人员测试过程中的数据同样会被保留。有了这些数据就能完整生成一套研发测试质量交付全览图让整个工程开发交付质量评估透明化。这就是本文要实践的目标把代码质量检查从提测后前移到编码中。二、需求目的与总体技术选型要实现上述目标需要三项核心技术配合字节码插桩因为要采集到接口执行信息就需要使用字节码插桩组件给接口方法增强。这个实现有点类似谷歌 Dapper 大规模分布式架构的非入侵监控只不过这里需要采集的描述性信息更多。关于字节码插桩可以了解 ASM、Javassist、Byte-Buddy它们都可以做此项工作。IDEA 插件开发要在研发人员开发过程中进行采集、同时不破坏研发的操作习惯最好的方式就是嵌入到「启动运行」中——只要在开发过程中有运行代码的动作就采集相应的接口信息。数据传输与处理传输可以使用 MQ 或者直接用 Netty处理数据的过程相对比较复杂需要分析出有价值的数据、把同类数据合并成一条执行链路的数据并生成相关的接口文档和工程服务地图。围绕这三项技术仓库内的相关专题恰好形成一条完整的学习链路面经手册 · 第13篇《除了JDK、CGLIB还有3种类代理方式》铺垫代理/增强技术全家谱基于JavaAgent的全链路监控系列讲清premain与-javaagent的底层机制字节码编程Byte-buddy 系列掌握 Byte Buddy 的委托式插桩 API《IntelliJ IDEA 插件开发》第8节在插件中引入探针一个同构的落地案例采集执行 SQL本文要讲解的 第10节基于字节码插桩采集数据实现代码交付质量自动分析 以及其在中间件系列中的姊妹篇 第 18 章采集研发过程中代码执行信息。三、字节码插桩技术选型为什么用 Byte Buddy这里使用的字节码插桩组件是Byte Buddy。它是一个代码生成和操作库用于在 Java 应用程序运行时创建和修改 Java 类而无需编译器的帮助。除了 Java 类库附带的代码生成实用程序外Byte Buddy 还允许创建任意类并且不限于实现用于创建运行时代理的接口。此外Byte Buddy 提供了一种方便的 API可以使用 Java 代理或在构建过程中手动更改类。它的核心优势原文档结论无需理解字节码指令即可使用简单的 API 很容易地操作字节码控制类和方法已支持 Java 11库轻量仅依赖 Java 字节码解析器库 ASM 的访问者 API本身不需要其他任何依赖项比起 JDK 动态代理、CGLIB、JavassistByte Buddy 在性能上具有一定的优势。从仓库内 Byte-buddy 篇二《监控方法执行耗时动态获取出入参类型和值》 的实践可以看到用 Byte Buddy 监控一个业务方法只需要三步通过ByteBuddy().subclass(...)ElementMatchers.named(方法名)定位目标方法通过MethodDelegation.to(MonitorDemo.class)把方法执行委托给监控类在监控类中使用RuntimeType、Origin、SuperCall等注解获取方法、调用链与耗时信息。也就是说Byte Buddy 的使用方式有些像使用 AOP 的拦截方式把最核心的字节码增强逻辑封装成高级 API让开发者把精力放在采集什么信息而非如何改写字节码上。常用注解速查表在编写监控方法时Byte Buddy 提供了一系列注解用于绑定不同的上下文信息。下表整理自仓库 Byte-buddy 篇二文档注解说明Argument绑定单个参数AllArguments绑定所有参数的数组This当前被拦截的、动态生成的那个对象Super当前被拦截的、动态生成的那个对象的父类对象Origin可绑定到Method被调用的原始方法、Constructor、Class、MethodHandle、MethodType、String动态类的toString()、int动态方法的修饰符等类型DefaultCall调用默认方法而非 super 的方法SuperCall调用父类版本的方法RuntimeType用于返回值、参数上提示 Byte Buddy 禁用严格的类型检查Empty注入参数类型的默认值StubValue注入一个存根值对返回引用类型、void 的方法注入null对返回原始类型的方法注入0FieldValue注入被拦截对象的一个字段的值Morph类似于SuperCall但允许指定调用参数四、探针工程从 premain 入口到方法信息采集1. 方法入口premain如果你接触过 JavaAgent 开发对premain会比较熟悉。可以把premain理解为程序启动时的方法入口你可以从这个入口中拦截到你需要的方法之后对它进行字节码增强——其实也就是动态写代码在方法中插入你的代码来收集方法信息。public static void premain(String agentArgs, Instrumentation inst) { AgentBuilder.Transformer transformer (builder, typeDescription, classLoader, javaModule) - { return builder .method(ElementMatchers.any()) // 拦截任意方法 .intercept(MethodDelegation.to(MonitorMethod.class)); }; new AgentBuilder .Default() .type(ElementMatchers.nameStartsWith(agentArgs)) .transform(transformer) .installOn(inst); }这里的两个关键点.type(ElementMatchers.nameStartsWith(agentArgs))只对以传入的包名前缀开头的类做增强。在本文场景中agentArgs正是从 IDEA 侧传入的当前源码包名实现只采集本项目代码、不干扰框架类的精准控制.method(ElementMatchers.any()).intercept(MethodDelegation.to(MonitorMethod.class))拦截匹配类的任意方法并把方法执行委托给MonitorMethod这个监控类处理。关于premain与-javaagent的底层机制仓库的 基于JavaAgent的全链路监控一《嗨JavaAgent》 给出了最简可运行示例JVM 首先尝试在代理类上调用premain(String agentArgs, Instrumentation inst)如果代理类没有实现这个重载JVM 将尝试调用premain(String agentArgs)。同时需要把代理入口配置进MANIFEST.MFManifest-Version: 1.0 Premain-Class: org.itstack.demo.agent.MyAgent Can-Redefine-Classes: true再通过Run/Debug Configurations - VM options配置-javaagent:探针Jar绝对路径参数即可在程序启动时进入探针入口。这与本文案例中 IDEA 插件自动拼接-javaagent参数的思路完全一致。2. 采集信息拦截方法与出入参使用 Byte Buddy 可以采集到一个方法的全部信息方法名称、入参个数、入参类型和内容、出参类型和结果以及方法执行耗时。核心代码如下RuntimeType public static Object intercept(Origin Method method, SuperCall Callable? callable, AllArguments Object[] args) throws Exception { long start System.currentTimeMillis(); Object resObj null; try { resObj callable.call(); return resObj; } finally { System.out.println(方法名称 method.getName()); System.out.println(入参个数 method.getParameterCount()); for (int i 0; i method.getParameterCount(); i) { System.out.println(入参 Idx (i 1) 类型 method.getParameterTypes()[i].getTypeName() 内容 args[i]); } System.out.println(出参类型 method.getReturnType().getName()); System.out.println(出参结果 resObj); System.out.println(方法耗时 (System.currentTimeMillis() - start) ms); } }这段代码可以逐层拆解RuntimeType运行时返回类型标注允许拦截方法与原方法签名存在差异时仍能正确绑定Origin Method method拿到被调用的原始方法对象从而获取方法名、参数个数、参数类型、返回类型等描述信息SuperCall Callable? callable调用父类原方法版本的执行逻辑——callable.call()即执行真实业务方法返回值原样返回不改变业务行为AllArguments Object[] args拿到本次调用的全部实参配合method.getParameterTypes()即可输出每个入参的类型 内容整个采集放在finally块中保证无论方法正常返回还是抛出异常监控信息都能被记录System.currentTimeMillis()前后差值即为方法执行耗时。仓库 Byte-buddy 篇二 中对queryUserInfo方法的监控输出与本文结构一致验证了这一套注解采集方案的可行性方法名称queryUserInfo 入参个数2 入参类型java.lang.String、java.lang.String 出参类型java.lang.String 出参结果德莱联盟王牌工程师。小傅哥(公众号bugstack虫洞栈)申请出栈 方法耗时490ms而在仓库 第8节在插件中引入探针基于字节码插桩获取执行SQL 中同样的思路还被用于拦截com.mysql.jdbc.PreparedStatement#executeInternal通过This Object obj拿到当前执行对象后反射读取originalSql字段从而打印出可直接复制执行的替换 SQL——这充分说明IDEA 插件 字节码探针是一套可以复用到多种研发提效场景的通用骨架。五、IDEA 插件侧把探针挂载到「启动运行」IDEA 插件开发的知识内容较多此处演示案例的插件开发部分相对简单核心是在程序启动时添加我们的字节码插桩程序。1. 方案一继承 DefaultJavaProgramRunner 重写 doExecute本文主案例主要方式是继承com.intellij.execution.impl.DefaultJavaProgramRunner重写doExecute方法添加自己需要的内容Override protected RunContentDescriptor doExecute(NotNull RunProfileState state, NotNull ExecutionEnvironment env) throws ExecutionException { JavaParameters parameters ((JavaCommandLine) state).getJavaParameters(); // 信息获取 PsiFile psiFile env.getDataContext().getData(LangDataKeys.PSI_FILE); String packageName ((PsiJavaFileImpl) psiFile).getPackageName(); // 添加字节码插装 ParametersList parametersList parameters.getVMParametersList(); parametersList.add(-javaagent: this.getClass().getResource(/).getPath().substring(1) ProjectProbe.jar packageName); return super.doExecute(state, env); }这段代码的核心动作拆解((JavaCommandLine) state).getJavaParameters()拿到本次运行配置的 JVM 启动参数对象env.getDataContext().getData(LangDataKeys.PSI_FILE)从运行环境的数据上下文中取到当前编辑的 PSI 文件((PsiJavaFileImpl) psiFile).getPackageName()得到当前源码的包名——这个包名会作为探针的agentArgs传入用于限定插桩范围parameters.getVMParametersList().add(-javaagent:...ProjectProbe.jar packageName)把-javaagent参数拼接到本次运行进程的 VM 参数列表中。其中this.getClass().getResource(/).getPath().substring(1)用于定位插件类路径下探针 JarProjectProbe.jar的所在目录packageName即把包名作为 agent 参数传给premain最后调用super.doExecute(state, env)继续走原生运行流程对研发的启动运行习惯零侵入。此处最核心的就是-javaagent把ProjectProbe.jar工程探针程序的 Jar 包加载进去其余是一些PsiFileAPI 的使用。2. 方案二JavaProgramPatcher plugin.xml 声明仓库第8节同构实现仓库 第8节在插件中引入探针 提供了同一套能力在工程结构上的另一种组织方式工程拆分为两个模块guide-idea-plugin-probe ├── probe-agent # 探针模块编译打包字节码增强服务产出 Jar └── probe-plugin # 插件模块通过 java.programPatcher 加载字节码增强包探针侧在 gradle 打包时需引入shadowJar把Premain-Class打包进可执行 Jar插件侧通过build.gradle声明依赖本地 Jar 文件树dependencies { implementation fileTree(dir: libs, includes: [*jar]) }然后编写补丁类继承com.intellij.execution.JavaProgramPatcher重写patchJavaParameters向 VM 参数追加-javaagentpublic class PerRun extends JavaProgramPatcher { Override public void patchJavaParameters(Executor executor, RunProfile configuration, JavaParameters javaParameters) { RunConfiguration runConfiguration (RunConfiguration) configuration; ParametersList vmParametersList javaParameters.getVMParametersList(); vmParametersList.addParametersString(-javaagent: agentCoreJarPath); vmParametersList.addNotEmptyProperty(guide-idea-plugin-probe.projectId, runConfiguration.getProject().getLocationHash()); } }最后在plugin.xml中把补丁类注册到java.programPatcher扩展点extensions defaultExtensionNscom.intellij !-- Add your extensions here -- java.programPatcher implementationcn.bugstack.guide.idea.plugin.PerRun/ /extensions这样插件安装后每次通过 IDEA 运行代码都会自动执行到拦截采集逻辑。对比两种方案可以看出DefaultJavaProgramRunner.doExecute重写更贴近运行时自定义本文主案例采用的方式而JavaProgramPatcher则把参数补丁声明式地挂到扩展点上两种方式都能达到嵌入启动运行、自动注入探针的目标。3. 从控制台到服务端数据的传输与处理在本文的演示阶段采集到的接口信息直接输出到控制台。而落到真实生产环境还需要第三步数据的传输和处理。传输可以使用 MQ 或者直接用 Netty 把研发运行过程中采集到的方法信息异步上报处理服务端对上报的数据进行分析识别有价值的数据、把同类数据合并为一条执行链路的数据、按需求代码分支聚合全部接口动作、生成各系统间的关系链路并配套生成最新的接口文档与一键测试验证功能呈现测试人员介入时可以参考研发编码过程中的全部测试用例、查看功能覆盖程度测试过程中的数据也会被保留最终生成一套完整的研发测试质量交付全览图。六、效果演示安装插件安装方式与正常安装 IDEA 插件一致。由于该插件处于开发阶段需要通过本地安装Install Plugin from Disk...方式加载构建产物。运行效果在安装了插件的 IDEA 中运行任意含接口/方法的测试代码运行过程中接口的信息会被完整输出到控制台包括方法名称、入参个数与内容、出参类型与结果、方法耗时等。在实际使用中这部分信息会传回服务端由服务端分析处理后展示在页面上形成接口文档与工程服务地图。说明仓库中该案例为演示性质运行效果以控制台输出为准生产化的服务端采集与展示可参考「数据传输与处理」小节自行扩展。七、总结基于 IDEA 插件和字节码插桩技术能做的功能实现还有很多。本文仅仅演示了其中一种研发到测试痛点的解决方案把代码质量评估从提测后置前移到编码过程中让接口信息、出入参、异常、调用链在开发运行的那一刻就被采集沉淀。这一方案的技术栈可以完整复用到其他场景仓库 第8节在插件中引入探针基于字节码插桩获取执行SQL 展示了拦截 JDBC 执行 SQL 的变体基于JavaAgent的全链路监控系列 展示了线程耗时、链路追踪、JVM 与 GC 信息等更多采集维度。当你看到这样的案例以后希望能给你的是并不一定所有的技术点都是为了面试造火箭对答的。当你真的把它落地以后才会懂得自己需要很多知识——从字节码增强到 IDE 扩展点从进程内采集到 MQ/Netty 传输再到服务端的数据聚合与可视化每一个环节都是真实工程能力的锤炼。延伸阅读仓库内路径《IntelliJ IDEA 插件开发》第8节在插件中引入探针基于字节码插桩获取执行SQL中间件系列 第 18 章基于IDEA插件开发和字节码插桩技术采集研发过程中代码执行信息字节码编程Byte-buddy 篇一《基于Byte Buddy语法创建的第一个HelloWorld》字节码编程Byte-buddy 篇二《监控方法执行耗时动态获取出入参类型和值》基于JavaAgent的全链路监控一《嗨JavaAgent》基于JavaAgent的全链路监控二《通过字节码增加监控执行耗时》【免费下载链接】CodeGuide:books: 本代码库是作者小傅哥多年从事一线互联网 Java 开发的学习历程技术汇总旨在为大家提供一个清晰详细的学习教程侧重点更倾向编写Java核心内容。如果本仓库能为您提供帮助请给予支持(关注、点赞、分享)项目地址: https://gitcode.com/gh_mirrors/code/CodeGuide创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
企业数字化 ERP 产品动态
相关推荐
车辆管理系统网站源码效果展示与实战解析 在车队规模扩张到几十辆甚至上百辆时,很多管理者会发现传统的 Excel 表格或简单的记账软件已经无法支撑日常运营了。车辆保养逾期、司机调度冲突、油耗异常波动这些问题往往在事后才被发现,导致运营成本居高不下。更棘手的是,当需要向管理层汇… · 2026/9/23 10:18:02
德普微DPM32M系列MCU工业选型与外设资源深度解析 1. 这不是三款芯片,而是一套面向工业控制场景的MCU产品矩阵德普微DPM32M08X、DPM32M05X、DPM32M03X这三款型号,表面看是三个独立芯片,实则构成了一套完整覆盖高中低档需求的MCU产品矩阵。我在工控设备厂做过五年嵌入式系统设计,也… · 2026/9/23 11:11:30
无线通信基础精讲:从信道建模到分集与MIMO的双语学习路线 很多人第一次接触无线通信,都是在学完了《信号与系统》和《通信原理》之后。你原本以为通信就是把信号从A点搬到B点,结果翻开教材才发现,真实世界里信号是随便乱撞的:反射、散射、穿墙、被遮挡,连一阵风都能让接收端的… · 2026/9/23 11:11:30
myp2p性能优化实战:3个坑让你告别API噩梦 myp2p性能优化实战:3个坑让你告别API噩梦 刚把 myp2p 核心库从 v2.0 升到 v3.5,项目直接崩了。控制台满屏红字, undefined is not a function… · 2026/9/23 11:11:24
fun的用法:从源码看Kotlin性能优化实战 fun的用法:从源码看Kotlin性能优化实战 配置环境就卡半天?别慌,很多时候不是环境的问题,而是你对语言底层机制理解不够。在Kotlin开发中, fun… · 2026/9/23 11:11:24
3D打印全流程实战指南:从建模、切片到参数调优与无线打印 玩3D打印机这些年,我发现自己身边大多数人的误区都出在同一个地方:以为3D打印就是把模型丢进机器、摁个开始键那么简单。真正上手才知道,建模、切片、打印三个环节,每一步都有门道——建模决定能不能打,切片决定打得好… · 2026/9/23 11:11:24
AI生成代码安全审查:三条信任边界与实操清单 1. 从“看代码对不对”到“看边界在哪”:AI 生成代码审查的思维转变用 AI 写代码这件事,现在基本没有哪个团队能绕开了。不管是补全一个工具函数、生成一段正则、还是让 Agent 直接改好几个文件,AI 编程工具已经深度嵌进了日常开发流程。但随… · 2026/9/23 11:11:17
3招搞定手机怎么下载微信面试难题实战项目解析 3招搞定手机怎么下载微信面试难题实战项目解析 面试被问“手机怎么下载微信”背后的原理,90%的人答不上来。别笑,这看似弱智的问题,实则是考察你对移动应用分发机制、安全校验及网络协议理解的试金石。我带过不少校招新人,他们背了八股文,却连一个A… · 2026/9/23 0:00:03
你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 面试被问“高并发下如何保证消息不丢失”,你张口就是“用Redis”,结果面试官追问“如果Redis宕机了怎么办”,你瞬间卡壳。这种场景太常见了,很多新手在背八股文时,只记住了技术名词… · 2026/9/23 0:00:29