-1

I have a concrete class we can call ServiceImpl who implements an interface called Service. I am using Spring AOP/Aspect-J to log the method execution times, which currently works just fine on the public method in that ServiceImpl class (which makes logical sense knowing how proxies work). One thing I would like to state right away is that I am using the swagger-codegen-maven-plugin plugin which basically generates code for you at compile time, in my case, it's generating some of my custom data types that I am using as a return type for my public entry method in my ServiceImpl that calls the nested private methods that I am unable to log/track using SpringAOP (why I'm making this thread). Also, I am using a multi-module maven project that has this structure:

  • api module (Spring Web REST Controller and where Application.java with main method is)
  • model module (where the POJOs/data classes are output after generated by swaggercodegen plugin)
  • service module (where my ServiceImpl, Service class/interface are and where my Spring AOP Aspect/Advice/Annotations are defined.

Now, before you tell me Spring AOP can't intercept nested private methods without enabling load time weaving in Spring AOP, I found this out the hard way after trying to Self Invocation and calling the nested private methods on that self reference (of type ServiceImpl which was @Autowired). I Got a Null Pointer Exception (NPE) everytime, as it seemed that reference (private ServiceImpl self) was null no matter if I Autowired the bean or got it from the ApplicationContext (i.e. applicationContext.get(ServiceImpl.class))

However, my question is simply if there are any alternatives to the private methods; Is there some changes I could make to my logic that would the cleanest way to solve this problem and finally successfully log the response/execution times of both my public and nested private methods in my Spring Boot application.

Below is a pretty much how my code is defined.

@Service
public class ServiceImpl implements Service {

@MySpringAOPAnnotationThatWorks
public SwaggerGeneratedDataType1 myPublicMethod(SwaggerGeneratedDataType2 myMethodArgument) {

    // ...

    // I WANT TO TRACK THESE NESTED PRIVATE METHOD EXECUTION TIMES 
    myNestedPrivateMethod1(myMethodArgument.getSomeAttribute1()); // NULL POINTER EXCEPTION when I tried using Self Invocation and using "self" instance to call method on
   
    // ...

    // I WANT TO TRACK THESE NESTED PRIVATE METHOD EXECUTION TIMES 
    myNestedPrivateMethod2(myMethodArgument.getSomeAttribute2());
}


private void myNestedPrivateMethod1(String myNestedMethodArgument1) {
    // some logic here
}


private void myNestedPrivateMethod2(String myNestedMethodArgument2) {
    // some logic here
}

What's the easiest way to simply log those execution times of the nested private methods?

ennth
  • 1,698
  • 5
  • 31
  • 63
  • Would you mind accepting my answer, if you believe I answered your question correctly? The question is still listed as open, even though I comprehensively answered it. Thank you. – kriegaex Nov 21 '21 at 21:54

1 Answers1

1

Why it does not work the way you expect

Before you created this question, you already commented on my answer here, i.e. you already know how Spring proxies work internally:

  • The proxy is a subclass of the target object's class, implementing the same interfaces as that class (if any).
  • The proxy internally holds a reference to the target object and delegates intercepted method calls to it (unless the aspect decides not to call the original method).

Anyway, completely unrelated to dynamic proxies is the fact that in Java, private methods can only be called by the original declaring class,

  • not by subclasses (for that we use protected),
  • not by other classes in the same package (for that we use package-protected),
  • not by unrelated classes in other packages (for that we use public).

Now, with the proxy being both a subclass instance and an outside object, you see that it is impossible that you can call a parent class's private method upon the proxy, i.e. even if you would auto-wire the proxy into the target class and then try to call PROXY.privateMethod(), it would not work, with or without Spring AOP. This is simply a JVM limitation. Private is private is private.

What you can do instead

1. Use native AspectJ

If you use native AspectJ, there are no more proxies. The code is woven directly into the original byte code. If done right, you can intercept private methods.

But why would you want to measure private method runtimes? You should focus on measuring public ones, because those are the ones that count from a user's perspective. For internal optimisations, rather use a profiler.

2. Make private methods (package-)protected

If you make your target methods at least protected, they can be called by subclasses, i.e. also by dynamic proxies. But in order to do it right you need to

  • @Autowire MyComponent INSTANCE into the target class,
  • change your formerly private, now protected method calls from simply internalMethod() or this.internalMethod() to INSTANCE.internalMethod() in order to enable the calls to be intercepted by Spring AOP aspects,
  • make sure to use CGLIB proxies and not JDK dynamic proxies. Spring Boot does that by default, even for beans wired as interfaces and not as implementing classes. Spring Core however defaults to JDK proxies for beans created using a call like appContext.getBean(MyInterface.class). So you need to activate target class auto-proxying in that case. The reason is that interfaces by design only have public methods, i.e. protected methods are not implemented by the proxy and therefore cannot be called upon it.

This will work, and making the methods protected also documents that they are meant to be used by subclasses. Auto-wiring the proxy instance into the target class is ugly IMO, however, because it makes the target class AOP-aware. Actually, aspects are cross-cutting concerns and the target class should be agnostic of them and work without any extra gymnastics.

My recommendation is the same as the Spring manual's: If you absolutely believe you need self-invocation support for aspects, use native AspectJ.

3. Factor out private methods into public methods of an extra component

The cleanest solution, if you absolutely want to stick with Spring AOP and want to avoid auto-wiring the proxy into the target class, is to create a separate Spring bean/component, exposing the formerly private methods as public ones there, wiring that helper component into your original target class and call the methods from component A to B. Then Spring AOP aspects just work. If it makes sense to expose the formerly private methods like that, is a design decision which can only be made on a case-by-case basis. If you had good reasons to hide the methods from your API before, then probably the answer is no. Otherwise, it might be yes. Making your components more fine-granular might even be beneficial, if the newly extracted component could also be used by other classes.

kriegaex
  • 63,017
  • 15
  • 111
  • 202
  • ...continuing my statement - yes, if I just track the outside method, there could be a few other calls happen so I wouldn't be exactly sure at that point where the high latency was coming from. – ennth Nov 13 '21 at 10:12
  • But again, actually, thank you for your response - it was incredibly informative. – ennth Nov 13 '21 at 10:17