raphw / byte-buddy

Runtime code generation for the Java virtual machine.
https://bytebuddy.net
Apache License 2.0
6.29k stars 807 forks source link

Return value if matches condition or call original method #1663

Closed KinComet closed 4 months ago

KinComet commented 4 months ago

Hi. I want to introduce a cache system here but I cannot find a way to inject my code.

    public static class Inject {
        @Advice.OnMethodEnter(skipOn = String.class)
        public static String met() {
            if (System.currentTimeMillis() > 0) { // If has cache (matches condition)
                return "inject";
            }
            // Call original method, else
            return null;
        }
    }

using

        ByteBuddyAgent.install(ByteBuddyAgent.AttachmentProvider.DEFAULT);
        new ByteBuddy().redefine(Source.class)
                .visit(Advice.to(Inject.class).on(ElementMatchers.named("test")))
                .make().load(Main.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
        System.out.println(new Source().test()); // returns null but not the value inject method returns

Also tried MethodDelegation, but @SuperCall and @DefaultCall gives null, so I cannot call original method

raphw commented 4 months ago

Have you tried to set -Dnet.bytebuddy.dump=/some/folder?

You can then investigate the generated class to see if it actually contains the new code using javap. If you set @Advice.OnMethodEnter(skipOn = String.class, inline = false) you might also be able to set a break point. If it never triggers, something else is wrong.

KinComet commented 4 months ago

Have you tried to set -Dnet.bytebuddy.dump=/some/folder?

You can then investigate the generated class to see if it actually contains the new code using javap. If you set @Advice.OnMethodEnter(skipOn = String.class, inline = false) you might also be able to set a break point. If it never triggers, something else is wrong.

Looked into the dump, it's

    public String test() {
        if ((System.currentTimeMillis() > 0 ? "inject" : null) instanceof String) {
            return null;
        }
        return "test";
    }

It returns null, but not inject String.

KinComet commented 4 months ago

I could skip original method codes and add a OnMethodExit, but it adds one more of condition check. It will take more time. If all things can be done in OnMethodEnter or something like that, it would be great.

raphw commented 4 months ago

Indeed, now I understand what you are trying to do. You would need to add the following:

@Advice.OnMethodExit
static void exit(@Advice.Enter String enter, @Advice.Return(readOnly = false) String returned) {
  if (enter != null) {
    returned = enter;
  }
}

Otherwise, you will not assign the returned value.

KinComet commented 4 months ago

This works! Thank you so much.