作者:小傅哥
沉淀、分享、成长,让自己和他人都能有所收获!
到本章为止已经写了四篇关于字节码编程的内容,涉及了大部分的API方法。整体来说对 Javassist 已经有一个基本的使用认知。那么在 Javassist 中不仅提供了高级 API 用于创建和修改类、方法,还提供了低级 API 控制字节码指令的方式进行操作类、方法。
有了这样的 javassist API 在一些特殊场景下就可以使用字节码指令控制方法。
接下来我们通过字节码指令模拟一段含有自定义注解的方法修改和生成。在修改的过程中会将原有方法计算息费的返回值替换成 0,最后我们使用这样的技术去生成一段计算息费的方法。通过这样的练习学会字节码操作。
itstack-demo-bytecode-1-05,可以关注公众号:bugstack虫洞栈,回复源码下载获取。你会获得一个下载链接列表,打开后里面的第17个「因为我有好多开源代码」,记得给个Star!测试方法
@RpcGatewayClazz(clazzDesc = "用户信息查询服务", alias = "api", timeOut = 500)
public class ApiTest {
@RpcGatewayMethod(methodDesc = "查询息费", methodName = "interestFee")
public double queryInterestFee(String uId){
return BigDecimal.TEN.doubleValue(); // 模拟息费计算返回
}
}ClassPool pool = ClassPool.getDefault();
// 类、注解
CtClass ctClass = pool.get(ApiTest.class.getName());
// 通过集合获取自定义注解
Object[] clazzAnnotations = ctClass.getAnnotations();
RpcGatewayClazz rpcGatewayClazz = (RpcGatewayClazz) clazzAnnotations[0];
System.out.println("RpcGatewayClazz.clazzDesc:" + rpcGatewayClazz.clazzDesc());
System.out.println("RpcGatewayClazz.alias:" + rpcGatewayClazz.alias());
System.out.println("RpcGatewayClazz.timeOut:" + rpcGatewayClazz.timeOut());ctClass.getAnnotations(),可以获取所有的注解,进行操作输出结果:
RpcGatewayClazz.clazzDesc:用户信息查询服务
RpcGatewayClazz.alias:api
RpcGatewayClazz.timeOut:500CtMethod ctMethod = ctClass.getDeclaredMethod("queryInterestFee");
RpcGatewayMethod rpcGatewayMethod = (RpcGatewayMethod) ctMethod.getAnnotation(RpcGatewayMethod.class);
System.out.println("RpcGatewayMethod.methodName:" + rpcGatewayMethod.methodName());
System.out.println("RpcGatewayMethod.methodDesc:" + rpcGatewayMethod.methodDesc());class 获取的,这样按照名称可以只获取最需要的注解名称。输出结果:
RpcGatewayMethod.methodName:interestFee
RpcGatewayMethod.methodDesc:查询息费MethodInfo methodInfo = ctMethod.getMethodInfo();
CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
CodeIterator iterator = codeAttribute.iterator();
while (iterator.hasNext()) {
int idx = iterator.next();
int code = iterator.byteAt(idx);
System.out.println("指令码:" + idx + " > " + Mnemonic.OPCODE[code]);
}JVM 执行的操作流程。输出结果:
指令码:0 > getstatic
指令码:3 > invokevirtual
指令码:6 > dreturnConstPool cp = methodInfo.getConstPool();
Bytecode bytecode = new Bytecode(cp);
bytecode.addDconst(0);
bytecode.addReturn(CtClass.doubleType);
methodInfo.setCodeAttribute(bytecode.toCodeAttribute());addDconst,将 double 型0推送至栈顶addReturn,返回 double 类型的结果此时的方法的返回值已经被修改,下面的是新的 class 类;
@RpcGatewayClazz(
clazzDesc = "用户信息查询服务",
alias = "api",
timeOut = 500L
)
public class ApiTest {
public ApiTest() {
}
@RpcGatewayMethod(
methodDesc = "查询息费",
methodName = "interestFee"
)
public double queryInterestFee(String var1) {
return 0.0D;
}
}0.0D。如果你的程序被这样操作,那么还是很危险的。所以有时候会进行一些混淆编译,降低破解风险。ClassPool pool = ClassPool.getDefault();
// 创建类信息
CtClass ctClass = pool.makeClass("org.itstack.demo.javassist.HelloWorld");
// 添加方法
CtMethod mainMethod = new CtMethod(CtClass.doubleType, "queryInterestFee", new CtClass[]{pool.get(String.class.getName())}, ctClass);
mainMethod.setModifiers(Modifier.PUBLIC);
MethodInfo methodInfo = mainMethod.getMethodInfo();
ConstPool cp = methodInfo.getConstPool();// 类添加注解
AnnotationsAttribute clazzAnnotationsAttribute = new AnnotationsAttribute(cp, AnnotationsAttribute.visibleTag);
Annotation clazzAnnotation = new Annotation("org/itstack/demo/javassist/RpcGatewayClazz", cp);
clazzAnnotation.addMemberValue("clazzDesc", new StringMemberValue("用户信息查询服务", cp));
clazzAnnotation.addMemberValue("alias", new StringMemberValue("api", cp));
clazzAnnotation.addMemberValue("timeOut", new LongMemberValue(500L, cp));
clazzAnnotationsAttribute.setAnnotation(clazzAnnotation);
ctClass.getClassFile().addAttribute(clazzAnnotationsAttribute);AnnotationsAttribute,创建自定义注解标签Annotation,创建实际需要的自定义注解,这里需要传递自定义注解的类路径addMemberValue,用于添加自定义注解中的值。需要注意不同类型的值 XxxMemberValue 前缀不一样;StringMemberValue、LongMemberValuesetAnnotation,最终设置自定义注解。如果不设置,是不能生效的。// 方法添加注解
AnnotationsAttribute methodAnnotationsAttribute = new AnnotationsAttribute(cp, AnnotationsAttribute.visibleTag);
Annotation methodAnnotation = new Annotation("org/itstack/demo/javassist/RpcGatewayMethod", cp);
methodAnnotation.addMemberValue("methodName", new StringMemberValue("查询息费", cp));
methodAnnotation.addMemberValue("methodDesc", new StringMemberValue("interestFee", cp));
methodAnnotationsAttribute.setAnnotation(methodAnnotation);
methodInfo.addAttribute(methodAnnotationsAttribute);addAttribute 上。// 指令控制
Bytecode bytecode = new Bytecode(cp);
bytecode.addGetstatic("java/math/BigDecimal", "TEN", "Ljava/math/BigDecimal;");
bytecode.addInvokevirtual("java/math/BigDecimal", "doubleValue", "()D");
bytecode.addReturn(CtClass.doubleType);
methodInfo.setCodeAttribute(bytecode.toCodeAttribute());Javassist 中的指令码通过,Bytecode 的方式进行添加。基本所有的指令你都可以在这里使用,它有非常强大的 API。addGetstatic,获取指定类的静态域, 并将其压入栈顶addInvokevirtual,调用实例方法addReturn,从当前方法返回double// 添加方法
ctClass.addMethod(mainMethod);
// 输出类信息到文件夹下
ctClass.writeFile();Javassist 字节码开发常用的内容。添加方法和输出字节码编程后的类信息。@RpcGatewayClazz(
clazzDesc = "用户信息查询服务",
alias = "api",
timeOut = 500L
)
public class HelloWorld {
@RpcGatewayMethod(
methodName = "查询息费",
methodDesc = "interestFee"
)
public double queryInterestFee(String var1) {
return BigDecimal.TEN.doubleValue();
}
public HelloWorld() {
}
}TryCatch 中的开始位置。javassist 字节码编程本身常用的方法基本已经覆盖完成,后续会集合 JavaAgent 做一些案例汇总,将知识点与实际场景进行串联。