byte-buddy icon indicating copy to clipboard operation
byte-buddy copied to clipboard

MethodDelegation's @Argument is working?

Open hi120ki opened this issue 3 years ago • 3 comments

Hi, thank you for the maintenance of this project. This project is so helpful for me.

I'd like to get Spring app's http request information by javaagent, and write some code like this repository. https://github.com/hi120ki/spring-javaagent-test

  • Spring app
@RestController
public class HelloController {

  @GetMapping("/")
  public String index() {
    return "Greetings from Spring Boot!";
  }
}
  • Agent
public class Agent {

  public static void premain(String agentArgs, Instrumentation inst) {
    System.out.println("agentArgs : " + agentArgs);

    new AgentBuilder.Default()
        .type(
            ElementMatchers.named(
                "org.springframework.web.servlet.DispatcherServlet"))
        .transform((builder, type, classLoader, module) -> builder
            .method(ElementMatchers.named("doDispatch"))
            .intercept(MethodDelegation.to(InstrumentInterceptor.class)))
        .installOn(inst);
  }
}
  • InstrumentInterceptor
public class InstrumentInterceptor {

  @RuntimeType
  public static Object intercept(
      @Argument(0) HttpServletRequest request,
      @SuperCall Callable<?> callable) {
    System.out.println(request.getMethod());
    try {
      return callable.call();
    } catch (Exception e) {
      System.out.println("Exception :" + e.getMessage());
      return null;
    } finally {
      System.out.println(request.getRequestURI());
    }
  }
}

I found org.springframework.web.servlet.DispatcherServlet.doDispatch 's first argument is javax.servlet.http.HttpServletRequest, and I tried to pick-up some HttpServletRequest properties.

But these code didn't work. (I can get http response by curl command, but no javaagent's stdout in spring app.)

Could you give me some comments?

Thank you.

hi120ki avatar Aug 08 '22 13:08 hi120ki

A servlet gets the response as a second argument. Did you try that after the super call? You can inject it as another annotated parameter.

raphw avatar Aug 08 '22 18:08 raphw

Thank you for comment.

I try this code, but no stdout.

public class InstrumentInterceptor {

  @RuntimeType
  public static Object intercept(
      @Argument(0) HttpServletRequest request,
      @Argument(1) HttpServletResponse response,
      @SuperCall Callable<?> callable) {
    System.out.println("[1] request : " + request.getMethod());
    try {
      System.out.println("[2] request : " + request.getMethod());
      Object ret = callable.call();
      System.out.println("[3] request : " + request.getMethod());
      System.out.println("[1] response : " + response.getContentType());
      return ret;
    } catch (Exception e) {
      System.out.println("Exception :" + e.getMessage());
      return null;
    } finally {
      System.out.println("[4] request : " + request.getMethod());
    }
  }
}

For MethodDelegation.to approach, we can use other annotations like @SuperCall Callable<?> callable, @Origin Method method , but @Argument doesn't work.

We can think of two reasons for these, first is there is no information in the method to get, and second is @Argument is not working.

I'm trying to intercept other method that argument is HttpServletRequest, but it doesn't work.

hi120ki avatar Aug 09 '22 11:08 hi120ki

Did you import the right annotation? It must not be from the advice package. If none of the code is called there must be an error during interception which should show in the logs.

raphw avatar Aug 11 '22 17:08 raphw

Yes, this code import net.bytebuddy.implementation.bind.annotation.Argument; https://github.com/hi120ki/spring-javaagent-test/blob/main/agent/src/main/java/com/example/profile/InstrumentInterceptor.java#L6

package com.example.profile;

import java.util.concurrent.Callable;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.bytebuddy.implementation.bind.annotation.Argument;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.SuperCall;

public class InstrumentInterceptor {

  @RuntimeType
  public static Object intercept(
      @Argument(0) HttpServletRequest request,
      @Argument(1) HttpServletResponse response,
      @SuperCall Callable<?> callable) {
    System.out.println("[ShowMethod method1] " + request.getMethod());
    try {
      System.out.println("[ShowMethod method1] " + request.getMethod());
      Object ret = callable.call();
      System.out.println("[ShowMethod method1] " + request.getMethod());
      return ret;
    } catch (Exception e) {
      System.out.println("[ShowMethod Exception] " + e.getMessage());
      return null;
    } finally {
      System.out.println("[ShowMethod method1] " + request.getMethod());
    }
  }
}

hi120ki avatar Aug 13 '22 13:08 hi120ki

What do you mean by "does not work" here, though? Looks right to me.

raphw avatar Aug 13 '22 19:08 raphw

This code try to print HttpServletRequest's property, and If it works, the output should be like this

[ShowMethod method1] GET

but there is no stdout and no error output.

hi120ki avatar Aug 14 '22 10:08 hi120ki

Did you register an AgentBuilder.Listener? It should print out any class that is instrumented or ignored.

raphw avatar Aug 14 '22 19:08 raphw

Thank you.

I add .with(AgentBuilder.Listener.StreamWriting.toSystemError().withErrorsOnly()) to new AgentBuilder.Default(), then got an error message.

[Byte Buddy] ERROR org.springframework.web.servlet.DispatcherServlet [org.springframework.boot.loader.LaunchedURLClassLoader@72e5a8e, unnamed module @f68f0dc, Thread[main,5,main], loaded=false]
java.lang.NoClassDefFoundError: javax/servlet/http/HttpServletRequest
        at java.base/java.lang.Class.getDeclaredMethods0(Native Method)
        at java.base/java.lang.Class.privateGetDeclaredMethods(Class.java:3434)

Then, change build.gradle file's import javax.servlet:javax.servlet-api:4.0.1 alias compileOnly to implementation

dependencies {
    ...
    implementation 'javax.servlet:javax.servlet-api:4.0.1'
    ...

And successfully get HttpServletRequest's property.

[ShowMethod method1] GET

Thank you for your help in resolving this issue.

hi120ki avatar Aug 15 '22 10:08 hi120ki