I have a Spring Boot Java application, running in a Docker container.
I want to use AOP in order to use a custom annotation to intercept methods and calculate their timing. Typical use case.
Specifically, I need to be able to profile ANY method I stick that annotation to. That means private methods and methods called from within the same class as well. What I've learned
I've learned already that in a Spring app, I will need some additional configurations in order for that to work because Spring's AOP will only intercept public methods in one Bean called from another Bean. I'll need to set up AspectJ AOP WITHIN Spring and apparently will need to configure Load Time Weaving as well.
The question overall is how do I accomplish this. Below is everything I know, and have done already. What's working, what's not, what i'm not sure about, and all that.
Here is what I have done already
NOTE: I modified certain references and source code elements to keep things general. So when evaluating what's wrong, please be conscious that certain compilation issues may just be related to that
I created an Annotation
@Target({
ElementType.METHOD
})
@Retention(RetentionPolicy.RUNTIME)
public @interface Profile {
String value();
}
I created an Aspect
@Component
@Aspect
public class ProfilerAspect {
@Around("@annotation(Profile)")
public void profile(ProceedingJoinPoint joinPoint) throws Throwable {
// Do stuff
joinPoint.proceed();
}
}
@Pointcut("...")
and then another method with @Around("the previously mentioned method()")
. I know that my setup works with Spring AOP on public methods called from another beanI created an aop.xml
in META-INF
<aspectj>
<weaver>
<!-- only weave classes in our application-specific packages -->
<include within="my.app.packages.*"/>
</weaver>
<aspects>
<!-- weave in just this aspect -->
<aspect name="my.app.packages.ProfilerAspect"/>
</aspects>
</aspectj>
I enabled load time weaving via java configuration
@SpringBootApplication
@EnableLoadTimeWeaving
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
I have also tried enabling load time weaving via XML configuration in my controllers.xml
like
<context:load-time-weaver/>
I've also tried specifying InstrumentationLoadTimeWeaver
when I do this, the app starts, but the annotation only works for public methods called from a different bean
Is there a different custom load time weaver I should have used?
That's it. Aside from all kinds of poking and prodding, that's essentially the entire set up that I have and that doesn't work. What always winds up happening at the end of the day is this error upon startup.
Caused by: java.lang.IllegalStateException: ClassLoader [org.springframework.boot.loader.LaunchedURLClassLoader] does NOT provide an 'addTransformer(ClassFileTransformer)' method. Specify a custom LoadTimeWeaver or start your Java virtual machine with Spring's agent: -javaagent:spring-instrument-{version}.jar
I read in a bunch of places that I am supposed to start the application by passing the --javaagent arg
-javaagent:path/to/spring-instrument.jar
My questions there are
Do I absolutely have to?
I read somewhere that I might need more than one agents added, is that true?
How do I get the jar into the docker container
How do I know what the path is from within the build script
Do I absolutely have to?
I recommend to add -javaagent:/path/to/aspectjweaver.jar
instead. spring-instrument
tries to hot-attach aspectjweaver
, which, as you see in the error message, can fail or simply be too late for some class loaders. A big advantage of this approach is that Spring should auto-detect that the weaver is active and automatically switch from Spring AOP to AspectJ mode without any further configuration.
Is there ANY way to do this in code so that I don't have to modify build scripts (managed by another team entirely)
In Spring, it seems to be possible depending on the container in which the application runs. For executable JARs (e.g. Spring Boot ones), on JDK 9+ there also is another way, but I will not explain it here, not knowing if it even applies to your situation. The most reliable and canonical way to attach a java agent is the command line, and hot-attachment is deprecated already in JDK 21 and will be prohibited at some point in the future. So just stick to the standard. Updating a launch script is no rocket science. Admins do it for lesser reasons, such as activating logging, adjusting memory limits etc.
I read somewhere that I might need more than one agents added, is that true?
No, aspectjweaver
is enough.
How do I get the jar into the docker container
Out of scope, please ask a new question.
How do I know what the path is from within the build script
Unclear, cannot answer.