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
?
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.