Java 在try-finally块中嵌入方法的现有代码

Java 在try-finally块中嵌入方法的现有代码,java,bytecode,instrumentation,java-bytecode-asm,bytecode-manipulation,Java,Bytecode,Instrumentation,Java Bytecode Asm,Bytecode Manipulation,我想在方法代码中添加指令。这些指令应在到达方法后和离开方法前执行。 为了确保后面的指令总是在离开前执行,我想把它们放在finally块中。 (我知道类AdviceAdapter,但它不能确保在调用的方法引发异常时执行退出代码。) 我的问题是结果中的指令顺序错误。 要处理的方法: @Test public void original() { assertTrue(true); assertTrue(!(false)); } @Test public void desired()

我想在方法代码中添加指令。这些指令应在到达方法后和离开方法前执行。 为了确保后面的指令总是在离开前执行,我想把它们放在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
    }
}
@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);
}
所需结果:

@Test
public void original() {
    assertTrue(true);
    assertTrue(!(false));
}
@Test
public void desired() {
    //some logging X

    try {
        assertTrue(true);
        assertTrue(!(false));
    }
    finally {
        //some logging Y
    }
}
@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);
}
(也可以在try块的第一行中记录X。)

(所需结果的字节码等于以下Java代码的字节码:)

使用ASM处理方法的我的代码:

@Test
public void original() {
    assertTrue(true);
    assertTrue(!(false));
}
@Test
public void desired() {
    //some logging X

    try {
        assertTrue(true);
        assertTrue(!(false));
    }
    finally {
        //some logging Y
    }
}
@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);
}
生成的字节码(指令顺序错误):


01-02可以,但是09-10必须在原始代码(14)之后,但在返回指令之前11-14必须在03之前。

您可以在测试方法之前和之后调用的方法上添加JUnit注释
@before
@before

我不确定你的方法中的错误在哪里。但在使用AdviceAdapter进行了一些尝试和错误之后,我实现了类似的目标


警告:只有当一个方法恰好包含一条返回指令时,此解决方案才有效(例如:如果只引发异常,则此解决方案不起作用)。 请参阅:


我发现了问题: 调用
super.visitCode
时,现有代码不会插入到
visitCode
方法中。此方法在超类中为空。这清楚地表明,现有代码是在其他点添加的

解决方案: 我在
visitCode
方法中调用我的方法
before
(它为需要在开头的新行添加代码)。如果操作码是返回语句,我在
visitVarInsn
中调用
after

@Override
public void visitCode()
{
    before();
}

@Override
public void visitInsn(int opcode)
{
    if (OpcodesUtil.isXRETURN(opcode))
    {
        after();
    }

    super.visitInsn(opcode);
}

(虽然
AdviceAdapter
也起了作用,但是在确保每个
ClassReader
accept
方法都被
EXPAND\u FRAMES
调用时出现了一些问题。此外,它可能会建议更多的退出点,而在关闭一个try块时,这些建议就不起作用了。)

谢谢您的回答。我知道,但那不是我想要的。其他
@Before
@After
(或
@BeforeClass
@AfterClass
)方法可能已经存在,然后在我的方法之前执行。请注意,return也可能引发异常。@锑:return本身(第15行)不会导致异常,因为它只是弹出并返回堆栈上的值。返回值的计算(可能引发异常)发生在返回之前的指令中,并且应该仍然在try块中。(尽管测试用例通常是无效的方法。)一般来说,当监视器处于非法状态时,返回指令本身可以引发异常。但这应该是个问题。好的,没错。你知道为什么说明书的顺序不对吗?谢谢,我试着把你的解决方案和我的比较一下。相关的字节码指令几乎相同,但现在我发现了问题所在。