我想在方法代码中添加指令。这些指令应在到达之后和离开方法之前执行。
为了确保后面的指令总是在离开之前执行,我想将它们放在finally块中。
(我知道类AdviceAdapter
但是当调用的方法抛出异常时,它不能确保执行退出代码。)
我的问题是结果中的说明顺序错误。
要处理的方法:
@Test
public void original() {
assertTrue(true);
assertTrue(!(false));
}
期望的结果:
@Test
public void desired() {
//some logging X
try {
assertTrue(true);
assertTrue(!(false));
}
finally {
//some logging Y
}
}
(记录X也可以在try块的第一行进行。)
(所需结果的字节码等于以下Java代码的字节码:)
@Test
public void desired() {
//some logging X
try {
assertTrue(true);
assertTrue(!(false));
//some logging Y
}
catch (Throwable t) {
//some logging Y
throw t;
}
}
我使用ASM处理方法的代码:
@Override
public void visitCode() {
before();
super.visitCode();
after();
}
private void before() {
insertInstructionToSetMode(LoggingMode.TESTING);
this.l0 = new Label();
this.l1 = new Label();
visitLabel(l0);
}
private void after() {
visitTryCatchBlock(l0, l1, l1, null);
Label l2 = new Label();
visitJumpInsn(GOTO, l2);
visitLabel(this.l1);
visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/Throwable"});
visitVarInsn(ASTORE, 1);
insertInstructionToSetMode(LoggingMode.FRAMING);
visitVarInsn(ALOAD, 1);
visitInsn(ATHROW);
visitLabel(l2);
visitFrame(Opcodes.F_SAME, 0, null, 0, null);
insertInstructionToSetMode(LoggingMode.FRAMING);
}
private void insertInstructionToSetMode(LoggingMode mode) {
String modeValue = (mode == LoggingMode.TESTING ? FIELD_NAME_TESTING : FIELD_NAME_FRAMING);
visitFieldInsn(Opcodes.GETSTATIC, CP_LOGGING_MODE, modeValue, FIELD_DESC_LOGGING_MODE);
visitMethodInsn(INVOKESTATIC, CP_INVOCATION_LOGGER, METHOD_NAME_SET_MODE, METHOD_DESC_SET_MODE);
}
生成的字节码(指令顺序错误):
// logging X
01 getstatic instrumentation/LoggingMode/TESTING Linstrumentation/LoggingMode;
02 invokestatic instrumentation/InvocationLogger/setMode(Linstrumentation/LoggingMode;)V
// successfully passed the try block
03 goto 9
// catch block for the finally behaviour
04 astore_1
05 getstatic instrumentation/LoggingMode/FRAMING Linstrumentation/LoggingMode;
06 invokestatic instrumentation/InvocationLogger/setMode(Linstrumentation/LoggingMode;)V
07 aload_1
08 athrow
// logging Y
09 getstatic instrumentation/LoggingMode/FRAMING Linstrumentation/LoggingMode;
10 invokestatic instrumentation/InvocationLogger/setMode(Linstrumentation/LoggingMode;)V
// original code
11 iconst_1
12 invokestatic org/junit/Assert/assertTrue(Z)V
13 iconst_1
14 invokestatic org/junit/Assert/assertTrue(Z)V
15 return
01-02很好,但 09-10需要在原始代码(14)之后,但在返回指令之前。 11-14需要在03之前。< /强>
答案 0 :(得分:2)
我不确定你的方法中的错误在哪里。但是在使用AdviceAdapter进行了一些试验和错误后,我实现了类似的效果。
见
答案 1 :(得分:0)
您可以将JUnit注释@Before
和@After
放在应该在测试方法之前和之后调用的方法上。
答案 2 :(得分:0)
警告:此解决方案仅在方法包含一个返回指令时才起作用(例如:如果它只引发异常,则它不起作用)。 请参阅:Embed the existing code of a method in a try-finally block (2)
我发现了问题:
调用visitCode
时,现有代码未插入super.visitCode
方法中。这个方法在超类中是空的。这表明现有代码已添加到其他位置。
<强>解决方案:强>
我在before
方法中调用我的方法visitCode
(它为需要在开头的新行添加代码)。如果操作码是返回语句,我在after
中调用visitVarInsn
。
@Override
public void visitCode()
{
before();
}
@Override
public void visitInsn(int opcode)
{
if (OpcodesUtil.isXRETURN(opcode))
{
after();
}
super.visitInsn(opcode);
}
(AdviceAdapter
也有效,但确保使用ClassReader
调用每个accept
的{{1}}方法时存在一些问题。此外,它可能会提供更多建议退出点,当关闭一个try块时不起作用。)