Byteman是一个字节码操作工具,它使得在加载时或在应用程序运行时更改Java应用程序的操作变得简单。它无需重写或重新编译原始程序即可工作。实际上,Byteman甚至可以用来修改Java代码,这些代码构成了Java虚拟机的一部分,比如String、Thread等类。Byteman采用了一种基于Java的清晰、简单和易于使用的事件条件动作(ECA)规则语言。ECA规则用于指定应该在何处、何时以及如何转换原始Java代码以修改其操作。
Byteman的发明主要是为了使用一种称为错误注入的技术来支持多线程和多jvm Java应用程序的自动化测试。它包含了一些被设计用来解决这种类型的测试中出现的问题的特性。Byteman在四个主要领域为测试自动化提供了明确的支持:
- 跟踪特定代码路径的执行并显示应用程序或JVM状态
- 通过改变状态、进行计划外的方法调用或强制执行意外的返回或抛出来破坏正常执行
- 编排由独立应用程序线程执行的活动的计时
- 监控和收集汇总应用程序和JVM操作的统计信息
安装:
bash
vim /etc/profile
export BYTEMAN_HOME=/root/byteman-download-4.0.23/bin
export PATH=$PATH:$BYTEMAN_HOME/bin
source /etc/profile
启动应用:
bash
java -jar -javaagent:/root/byteman-download-4.0.23/lib/byteman.jar=listener:true,boot:/root/byteman-download-4.0.23/lib/byteman.jar -Dorg.jboss.byteman.transform.all shushan-server-v2-0.0.1-SNAPSHOT.jar
java
package com.kexuexiong.shushan.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import org.springframework.stereotype.Controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import com.kexuexiong.shushan.service.BannerService;
import com.kexuexiong.shushan.entity.Banner;
import org.springframework.beans.factory.annotation.Autowired;
/**
* <p>
* 轮播图 前端控制器
* </p>
*
* @author kexuexiong
* @since 2024-04-22
*/
@Controller
@Slf4j
@RequestMapping("/banner")
public class BannerController {
@Autowired
private BannerService bannerService;
@GetMapping(value = "/")
public ResponseEntity<Page<Banner>> list(@RequestParam(required = false) Integer current, @RequestParam(required = false) Integer pageSize) {
log.info("banner list");
if (current == null) {
current = 1;
}
if (pageSize == null) {
pageSize = 10;
}
Page<Banner> aPage = bannerService.page(new Page<>(current, pageSize));
return new ResponseEntity<>(aPage, HttpStatus.OK);
}
@GetMapping(value = "/{id}")
public ResponseEntity<Banner> getById(@PathVariable("id") String id) {
return new ResponseEntity<>(bannerService.getById(id), HttpStatus.OK);
}
@PostMapping(value = "/create")
public ResponseEntity<Object> create(@RequestBody Banner params) {
bannerService.save(params);
return new ResponseEntity<>("created successfully", HttpStatus.OK);
}
@PostMapping(value = "/delete/{id}")
public ResponseEntity<Object> delete(@PathVariable("id") String id) {
bannerService.removeById(id);
return new ResponseEntity<>("deleted successfully", HttpStatus.OK);
}
@PostMapping(value = "/update")
public ResponseEntity<Object> update(@RequestBody Banner params) {
bannerService.updateById(params);
return new ResponseEntity<>("updated successfully", HttpStatus.OK);
}
}
注入规则:
ruleList.btm:
bash
RULE trace bananerList entry
CLASS com.kexuexiong.shushan.controller.BannerController
METHOD list
AT ENTRY
IF true
DO throw new RuntimeException("list int")
ENDRULE
RULE trace bananerList exit
CLASS com.kexuexiong.shushan.controller.BannerController
METHOD list
AT EXIT
IF true
DO throw new RuntimeException("list out")
ENDRULE
规则解析:官方文档
bash
######################################
# Example Rule Set
#
# a single rule definition
RULE example rule
# comment line in rule body
. . .
ENDRULE
规则是在脚本中定义的,脚本由一系列规则定义和注释行交错组成。注释可以出现在规则定义的正文中,也可以出现在定义之前或之后,但注释必须与规则文本分开。注释是以#字符开头的行:
规则事件规范标识与目标类关联的目标方法中的特定位置。目标方法可以是静态方法,也可以是实例方法或构造函数。如果没有指定详细位置,则默认位置是目标方法的入口。因此,单个规则的基本模式如下:
bash
# rule skeleton
RULE <rule name>
CLASS <class name>
METHOD <method name>
BIND <bindings>
IF <condition>
DO <actions>
ENDRULE
rule关键字后面的规则名称可以是任何自由格式的文本,但限制是它必须包含至少一个非空格字符。规则名称不必是唯一的,但如果它们清楚地标识规则,则在调试规则脚本时显然会有所帮助。每当在解析、类型检查、编译或执行过程中遇到错误时,都会打印规则名称。
class和method关键字后面的类名和方法名必须在同一行。类名可以标识带有或不带有包限定符的类。方法名可以标识有或没有参数列表或返回类型的方法。构造函数方法使用特殊名称标识,类初始化方法使用特殊名称标识。例如,
bash
# class and method example
RULE any commit on any coordinator engine
CLASS CoordinatorEngine
METHOD commit
. . .
ENDRULE
此规则将仅匹配CoordinatorEngine包中的类 com.arjuna.wst11.messaging.engines,并且仅匹配没有参数且返回类型为 State 的方法提交。请注意,在此示例中,未指定类 State 的包。类型检查器将从省略参数或返回类型的匹配方法中推断出参数或返回类型的包。前面的示例还使用了位置说明符AT LINE。line 关键字后面的文本必须能够被解析以得出整数行号。这会指示代理在源代码中特定行的开头插入触发器调用。注意:
Byteman 代理通常不会转换包中的任何类java.lang,也永远不会转换包中的类org.jboss.byteman,即 byteman 包本身。
可以通过使用(内部格式) 分隔符来指定内部类,以区分内部类与其封闭的外部类 o r g . m y . L i s t 分隔符来指定内部类,以区分内部类与其封闭的外部类org.my.List 分隔符来指定内部类,以区分内部类与其封闭的外部类org.my.ListCons, 例如Map E n t r y Entry EntryWrapper。
其他详细的参考官方文档。
bash
# class and method example 2
RULE commit with no arguments on wst11 coordinator engine
CLASS com.arjuna.wst11.messaging.engines.CoordinatorEngine
METHOD State commit()
AT LINE 324
. . .
ENDRULE
注册注入规则:
bash
bmsubmit.sh -l /root/bytemanScript/ruleList.btm
请求访问:
卸载注入:
bash
bmsubmit.sh -u /root/bytemanScript/ruleList.btm
卸载后访问: