作者:小傅哥

博客:https://bugstack.cn

沉淀、分享、成长,让自己和他人都能有所收获!

一、前言

如何保证代码质量?

业务提需求,产品定方案,研发做实现,测试验流程。四种角色的相互配合是确保一个需求上线的必备条件。在整个需求的交付质量级别划分中,研发与测试是非常重的一环,如果研发提测的代码质量不高,就会出现不同级别的修BUG、返工甚至重做的风险。

那么,怎么来提高代码质量呢?一般我们都会要求研发在开发代码的过程中编写单元测试,验证自己的代码逻辑。如果最终单元测试覆盖度不足,可以由测试拒绝研发提测。

但是,整个需求实现的代码是在全部开发完成后提测的,也就是临近上线的最后一环,大家才知道某个研发的某个功能域的实现是否具备提测条件。如果这个时候代码质量不高,那么接下来就是项目风险的时候。压测试时间调上线时间,总之有病拖着最后成大病了!

当然,你可以在项目开发期间定期排查代码,或者在日会进度反馈等等手段。可这样需要耗费大量时间1拖1的开发排查方式很难满足复杂流程的较大型项目开发,而且对于项目风险把控也是不可预估的。

所以,我们希望采集研发在开发过程中的执行动作,把风险判断提前。实际操作举例就是,当你开发完成一个接口,开始测试运行时,我们的插件就可以采集到这个接口的全部信息,包括:接口名称、入参类型和内容、出参类型和内容、异常信息、调用关系链等。而再把这些信息汇总提交到服务端,生成本次需求代码分支下的全部接口动作,以及各系统间的关系链路,并附带随时生成最新的接口文档和一键测试验证功能。后期测试人员介入时就可以参考研发在编码过程中的全部测试用例,也可以查看整个功能的覆盖程度,此外测试人员测试过程中的数据也会被保留下。现在拥有这些数据信息以后,就可以完整的生成一套研发测试质量交付全览图,让整个工程开发交付质量评估透明化。

接下来我们就按照以上的描述性内容,实践开发一个案例体会下。走起!

二、技术实现准备

  1. 字节码插桩,因为我们需要采集到接口执行信息,那么就需要使用字节码插桩组件给接口方法增强。这个实现有点类似谷歌的Dapper,大规模分布式架构的非入侵监控。只不过我们需要采集的描述性信息更多。关于字节码插桩,可以了解ASM、Javassist、Byte-Buddy,它们都可以做此项工作。
  2. IDEA 插件开发,因为我们需要在研发人员开发过程中进行采集,也不破坏研发的操作习惯。那么最好的方式就是嵌入到启动运行中,只要在开发过程中有运行代码的动作,就采集相应的接口信息。
  3. 最后就是数据的传输和处理,传输可以使用MQ或者直接用Netty。而处理数据的过程会相对比较复杂,在这个过程需要分析出有价值的数据,同类的数据,合并一条执行链路的数据,以及生成相关的接口文档和工程服务地图。

三、对字节码插桩

这里我们使用的字节码插桩组件是 Byte-buddy,它是一个代码生成和操作库,用于在 Java 应用程序运行时创建和修改 Java 类,而无需编译器的帮助。除了 Java 类库附带的代码生成实用程序外,Byte Buddy 还允许创建任意类,并且不限于实现用于创建运行时代理的接口。此外,Byte Buddy 提供了一种方便的 API,可以使用 Java 代理或在构建过程中手动更改类。

  • 无需理解字节码指令,即可使用简单的 API 就能很容易操作字节码,控制类和方法。
  • 已支持Java 11,库轻量,仅取决于Java字节代码解析器库ASM的访问者API,它本身不需要任何其他依赖项。
  • 比起JDK动态代理、cglib、Javassist,Byte Buddy在性能上具有一定的优势。

1. 方法入口

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);
}

如果你接触过 Javaagent 开发,那么对于 premain 会比较熟悉。如果不清楚你可以把它理解为,它是程序启动的时的方法入口,你可以从这个入口中拦截到你需要的方法,之后对它进行字节码增强。其实也就是动态写代码,在方法中添加你的代码,来收集方法信息。

2. 采集信息

@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");
}
}

这个就是使用 Byte-Buddy 可以采集的信息,你可以通过注解入参,获取到一个方法的全部信息。方法名称、入参个数、入参类型和内容、出参类型和结果以及还能计算方法执行耗时。

四、IDEA 插件开发

关于 IDEA 插件开发的知识内容较多,可以从GitHub搜索一些资料和查阅官方文档:https://plugins.jetbrains.com/docs/intellij/gradle-build-system.html?from=jetbrains.org

此处演示案例关于插件开发的内容比较简单,主要是继承 com.intellij.execution.impl.DefaultJavaProgramRunner,Override 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);
}

此处最核心的就是 -javaagentProjectProbe.jar 工程探针程序的Jar包加载进去。其他的就是一些关于 PsiFile API 的使用,感兴趣可以阅读官方文档中的介绍。

五、效果演示

安装插件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z3Smwntr-1614045501972)(https://bugstack.cn/assets/images/2020/all-23-3.png)]

  • 安装插件就和我们正常安装一样,不过目前这个插件在开发阶段,所以需要本地安装。

运行效果

  • 上图就是运行效果的案例演示,我们把运行时接口的信息完整的输出到控制台。
  • 在实际使用的过程中,会把这部分信息传回服务端,由服务端分析处理后,展示在页面上。

六、总结

  • 基于IDEA插件和字节码插桩技术,能做的功能实现还有很多。本文仅仅是其中一种研发到测试痛点的解决方案,如果感兴趣可以一起深入研究。
  • 当你看到这样的案例以后,希望能给你的是并不一定所有的技术点都是为了面试造火箭对话的。当你真的把它落地以后,才会懂的自己需要很多知识。
  • 本文没有太过多的介绍插件开发和字节码技术,如果对字节码编程感兴趣,可以在公众号:bugstack虫洞栈,回复字节码编程。全书11万7千字,20个章节涵盖三个字节码框架(ASM、Javassist、Byte-budy)和JavaAgent使用并附带整套案例源码!

七、系列推荐

方案设计:基于IDEA插件开发和字节码插桩技术,实现研发交付质量自动分析的更多相关文章

  1. 开发 IDEA Plugin 引入探针,基于字节码插桩获取执行SQL

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 片面了! 一月三舟,托尔斯泰说:"多么伟大的作家,也不过就是在书写自己的片 ...

  2. Java Record 的一些思考 - 默认方法使用以及基于预编译生成相关字节码的底层实现

    快速上手 Record 类 我们先举一个简单例子,声明一个用户 Record. public record User(long id, String name, int age) {} 这样编写代码之 ...

  3. Java Instrumentation插桩技术学习

    Instrumentation基础 openrasp中用到了Instrumentation技术,它的最大作用,就是类的动态改变和操作. 使用Instrumentation实际上也可以可以开发一个代理来 ...

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

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

  5. 大话+图说:Java字节码指令——只为让你懂

    前言 随着Java开发技术不断被推到新的高度,对于Java程序员来讲越来越需要具备对更深入的基础性技术的理解,比如Java字节码指令.不然,可能很难深入理解一些时下的新框架.新技术,盲目一味追新也会越 ...

  6. 字节码暴力破解censum(老版本)

    声明 事先声明,本文仅提供破解方法以供个人及读者们学习Java字节码,不提倡破解程序. 本文是个人学习掘金小册张师傅的<JVM字节码从入门到精通>后,作为一个实践的记录,并无恶意. 关于c ...

  7. APK修改神器:插桩工具 DexInjector

    本文介绍了一个针对Dex进行插桩的工具,讲解了一下直接修改Dalvik字节码和Dex文件时遇到的问题和解决方法 作者:字节跳动终端技术-- 李言 背景 线下场景中,我们经常需要在APK中插入一些检测代 ...

  8. 插桩 inline hook 动态二进制插桩的原理和基本实现过程

    插桩测试 https://source.android.google.cn/compatibility/tests/development/instrumentation https://zhuanl ...

  9. JVM-5.字节码执行引擎

    一.概述 二.栈帧结构 三.方法调用 四.方法执行       一.概述 虚拟机与物理机 虚拟机是一个相对于物理机的概念,这两种机器都有代码执行能力,其区别是物理机的执行引擎是直接建立在处理器.硬件. ...

随机推荐

  1. Spring boot 集成MQ

    import lombok.extern.java.Log; import org.springframework.amqp.core.TopicExchange; import org.spring ...

  2. 27.SELinux 安全子系统

    1.SELinux(Security-Enhanced Linux)是美国国家安全局在Linux 开源社区的帮助下开发的一个强制访问控制(MAC,Mandatory Access Control)的安 ...

  3. C++ 标准模板库(STL):map

    目录 4. map 4.1 map的定义 4.2 map容器内元素的访问 4.3 map常用函数实例解析 4.4 map的常见用途 4. map map翻译为映射,也是常用的STL容器. 在定义数组时 ...

  4. jvm系列四类加载与字节码技术

    四.类加载与字节码技术 1.类文件结构 首先获得.class字节码文件 方法: 在文本文档里写入java代码(文件名与类名一致),将文件类型改为.java java终端中,执行javac X:...\ ...

  5. cassandra权威指南读书笔记--cassandra查询语言

    cassandra使用一个特殊主键(复合键)表示宽行,宽行也叫分区.复合键由一个分区键和一组可选的集群列组成.分区键用于确定存储行的节点,分区键也可以包含多个列.集群键用于控制数据如何排序以及在分区中 ...

  6. Jenkins开启丢弃旧的构建?你可要小心啊!

    玩Devops的小伙伴应该对Jenkins都有了解. Github上16.8k的Star的项目,1500+的构建.发布等自动化插件可供选择,事实上的业界CICD标准领导者. JFrog.Coding等 ...

  7. linux(11)配置环境变量

    前言 在自定义安装软件的时候,经常需要配置环境变量,下面进行详细解析 & nbsp; 环境变量配置文件 用户 配置文件 系统环境 /ect/profile /etc/bashrc /etc/e ...

  8. Java一些概念

    1.Java先编译后解释 同一个.class文件在不同的虚拟机会得到不同的机器指令(Windows和Linux的机器指令不同),但是最终执行的结果却是相同的. 2.JDK包含JRE,JRE包含JVM, ...

  9. MapReduce统计每个用户的使用总流量

    1.原始数据 2.使用java程序 1)新建项目 2)导包 hadoop-2.7.3\share\hadoop\mapreduce +hsfs的那些包 +common 3.写项目 1)实体类 注:属性 ...

  10. Luogu U13059 某种密码

    应该没什么用的题目链接 题目背景 关于某种密码有如下描述:某种密码的原文A是由N个数字组成,而密文B是一个长度为N的01数串,原文和密文的关联在于一个钥匙码KEY.若KEY=∑▒[Ai*Bi],则密文 ...