Search code examples
javabyte-buddyjavaagents

What algorithm does ByteBuddy's ElementMatchers#nameStartsWith use?


Let's suppose that I want to apply an @Advice.OnMethodEnter to the methods declared in org.springframework.web.context.support.GenericWebApplicationContext. To do that, I've written this minimal agent:

public class SequenceAgent {

  public static void premain(final String args,
                             final Instrumentation instrumentation) {
    new AgentBuilder.Default()
        .with(new AgentBuilder.InitializationStrategy.SelfInjection.Eager())
        .type(nameStartsWith(
            "org.springframework.web.context.support.GenericWebApplicationContext"))
        .transform((builder, typeDescription, classLoader, module) -> builder
            .method(any()).intercept(Advice.to(SequenceAdvice.class)))
        .installOn(instrumentation);
  }

  public static class SequenceAdvice {

    @Advice.OnMethodEnter
    static void enter(@Advice.This Object thiz, @Advice.Origin Method method,
                      @Advice.AllArguments Object... args) {
      String className = thiz.getClass().getName();
      String methodName = method.getName();

      System.out.println("Entered: " + className + "#" + methodName);
    }
  }
}

I expected this configuration to filter out org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext as it doesn't match org.springframework.web.context.support.GenericWebApplicationContext but looks like invocation of methods on objects of this class are also intercepted:

import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;

public class AgentTest {

    public static void main(String[] args) {
        AnnotationConfigServletWebServerApplicationContext context =
                new AnnotationConfigServletWebServerApplicationContext();

        context.containsBean("SomeBean");
    }

}

When attached to the agent and ran, it prints out (wrapped for readability):

Entered: org.springframework.boot.web.servlet.context.
    AnnotationConfigServletWebServerApplicationContext
    #getResourcePatternResolver
.
.
.
Entered: org.springframework.boot.web.servlet.context.
    AnnotationConfigServletWebServerApplicationContext
    #getResourceCache

Entered: org.springframework.boot.web.servlet.context.
    AnnotationConfigServletWebServerApplicationContext
    #getClassLoader

Entered: org.springframework.boot.web.servlet.context.
    AnnotationConfigServletWebServerApplicationContext
    #containsBean

And the class hierarchy for org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServe.ApplicationContext is:

org.springframework.core.io.DefaultResourceLoader
        ⇧
org.springframework.context.support.AbstractApplicationContext
        ⇧
org.springframework.context.support.GenericApplicationContext
        ⇧
⤏⤏⤏⤏ org.springframework.web.context.support.GenericWebApplicationContext
        ⇧
org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext
        ⇧
org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServe.ApplicationContext

And the containsBean method is declared and overridden in:

org.springframework.beans.factory.BeanFactory#containsBean
        ⇧
org.springframework.context.support.AbstractApplicationContext#containsBean

Why for containsBean, @Advice.This Object thiz is resolved to org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext and not org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext?


Solution

  • Byte Buddy uses String.startsWith.

    What you are seeing is due to Byte Buddy instrumenting classes, not instances. In a way, think of Byte Buddy copying the advice code into the target method.

    As a result, all subclasses will be affected. For doing what you are doing, you need to check the instance class's type during invocation, just as if you wanted to implement this in Java.