Search code examples
javadebuggingintellij-ideadynamic-proxy

IntelliJ IDEA debug enter method multiple times


When debugging the following code:

public class MyProxy {
    public static void main(String[] args){

        Consumer f = (Consumer) Proxy.newProxyInstance(
                Consumer.class.getClassLoader(),
                new Class[] { Consumer.class },
                new Handler(new ConsumerImpl())
        );

        f.consume("Hello");   // set breakpoint here
        System.out.println("done");
    }
}

interface Consumer {
    void consume(String s);
}

class ConsumerImpl implements Consumer {
    public void consume(String s) {
        System.out.println(s);
    }
}

class Handler implements InvocationHandler {
    private final Consumer original;
    public Handler(Consumer original) {
        this.original = original;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws IllegalAccessException, IllegalArgumentException,
            InvocationTargetException {
        System.out.println("BEFORE");
        method.invoke(original, args);
        System.out.println("AFTER");
        return null;
    }
}

The output is :

BEFORE
AFTER
BEFORE
AFTER
BEFORE
BEFORE
AFTER
Hello
BEFORE
AFTER
AFTER
BEFORE
AFTER
BEFORE
AFTER
BEFORE
AFTER
BEFORE
AFTER
done
BEFORE
AFTER

When the debugger pause at the breakpoint line, the output already have two lines, as I step into the invoke method, the output is like shown. It's like the debugger is entering the invoke method every step I make. Because if I don't step into the method, the output is :

BEFORE
AFTER
BEFORE
Hello
AFTER
BEFORE
AFTER
done
BEFORE
AFTER

If I run the code, output is as expected.

BEFORE
Hello
AFTER
done

Is it a bug of debugger or I do something wrong?

Env: Windows 64, Intellij IDEA, JDK8


Solution

  • It is not a bug in IDEA. if you try to debug this without any breakpoints you may get the same result as you expected. But if you put some breakpoint, IDEA will try to call the toString() method , then hashCode() method of the variables that it can evaluate.

    ultimately for all methods , it will call your InvocationHandler implementation (not only for the "consume" method, you can print the method.getName() to make sure this in the invocation handler implementation).