如何在Java 22 ClassFile预览API中合并ClassTransform?
Java 22 ClassFile API多转换合并失效问题
背景
开发性能分析器原型时,使用Java 22的ClassFile API操作类并注入字节码(统计方法调用次数、运行时长),通过Instrumentation API实现运行时类更新。单个类转换可正常工作,但多个转换无法合并,仅首个转换生效,其余被忽略。
生成CodeTransform的代码
private static CodeTransform generateMethodCodeTransform(int statisticsId, List<String> metricValues) { return new CodeTransform() { private int startSlot = 0; private int endSlot = 0; private int durationSlot = 0; @Override public void accept(CodeBuilder builder, CodeElement element) { if(metricValues.contains(ProfilerMethodMetric.RUNTIME.toString())) { if(element instanceof ReturnInstruction) { builder.invokestatic(ClassDesc.of(System.class.getName()), "nanoTime", MethodTypeDesc.of(ConstantDescs.CD_long)); endSlot = builder.allocateLocal(TypeKind.LongType); builder.lstore(endSlot); builder.lload(endSlot); builder.lload(startSlot); builder.lsub(); durationSlot = builder.allocateLocal(TypeKind.LongType); builder.lstore(durationSlot); builder.invokestatic(ClassDesc.of(Profiler.class.getName()), "getProfilerStatistics", MethodTypeDesc.of(ClassDesc.of(ProfilerStatistics.class.getName()))); builder.ldc(statisticsId); builder.invokevirtual(ClassDesc.of(ProfilerStatistics.class.getName()), "getMethodStatisticsById", MethodTypeDesc.of(ClassDesc.of(MethodStatistics.class.getName()), ConstantDescs.CD_int)); builder.lload(durationSlot); builder.invokevirtual(ClassDesc.of(MethodStatistics.class.getName()), "trackRuntime", MethodTypeDesc.of(ConstantDescs.CD_void, ConstantDescs.CD_long)); } } builder.with(element); } @Override public void atEnd(CodeBuilder builder) { } @Override public void atStart(CodeBuilder builder) { System.out.println("AT START METHOD CODE TRANSFORM"); if(metricValues.contains(ProfilerMethodMetric.DEBUG.toString())) { builder.getstatic(ClassDesc.of(System.class.getName()), "out", ClassDesc.of(PrintStream.class.getName())); builder.ldc("DEBUG: This method is successfully injected"); builder.invokevirtual(ClassDesc.of(PrintStream.class.getName()), "println", MethodTypeDesc.of(ConstantDescs.CD_void, ClassDesc.of(String.class.getName()))); } if(metricValues.contains(ProfilerMethodMetric.CALLS.toString())) { builder.invokestatic(ClassDesc.of(Profiler.class.getName()), "getProfilerStatistics", MethodTypeDesc.of(ClassDesc.of(ProfilerStatistics.class.getName()))); builder.ldc(statisticsId); builder.invokevirtual(ClassDesc.of(ProfilerStatistics.class.getName()), "getMethodStatisticsById", MethodTypeDesc.of(ClassDesc.of(MethodStatistics.class.getName()), ConstantDescs.CD_int)); builder.invokevirtual(ClassDesc.of(MethodStatistics.class.getName()), "trackCall", MethodTypeDesc.of(ConstantDescs.CD_void)); } if(metricValues.contains(ProfilerMethodMetric.RUNTIME.toString())) { builder.invokestatic(ClassDesc.of(System.class.getName()), "nanoTime", MethodTypeDesc.of(ConstantDescs.CD_long)); startSlot = builder.allocateLocal(TypeKind.LongType); builder.lstore(startSlot); } } }; }
添加ClassTransform到列表的方式
transforms.add(ClassTransform.transformingMethodBodies(mm -> mm.equals(methodModel), codeTransform));
尝试的两种合并方式及现象
方式1:使用andThen()链式组合
按文档说明应串联转换,但最终无任何注入效果,所有转换失效:
ClassTransform combined = null; for(ClassTransform t : transforms) { if(combined == null) { combined = t; } else { combined = combined.andThen(t); } } if(combined != null) { classfileBuffer = ClassFile.of().transform(classModel, combined); }
方式2:循环依次转换类模型
每次转换后重新解析新模型,但仅首个转换生效,后续转换无变化,atStart()中的调试打印仅执行一次:
for(ClassTransform t : transforms) { classfileBuffer = ClassFile.of().transform(classModel, t); classModel = ClassFile.of().parse(classfileBuffer); }
补充现象
3个转换中仅第一个生效,类文件哈希无后续变化,调试输出仅出现一次。BytecodeTransformer是ClassFileTransformer的包装接口,不影响问题。
内容的提问来源于stack exchange,提问作者Alexander Kiefer
相关产品推荐
相关产品推荐

