This is the 19th day of my participation in the More text Challenge. For details, see more text Challenge

This topic should start with invalidation of nested method call interception within the same object.

Suppose we have a target object class definition as shown below. Of course, such a class definition can be mapped to any possible business object in the system.

public class NestableInvocationBO {
    public void method1(a) {
        method2();
        System.out.println("method1 executed!");
    }
    
    public void method2(a) {
        System.out.println("method2 executed."); }}Copy the code

What we need to focus on in this class definition is that one of its methods calls other methods defined on the same object. This is usually quite common. In class NestableInvocationBO, method method1 calls method Method2 on the same object.

Now, we’ll use Spring AOP to intercept the method1 and method2 methods defined by this class, for example by adding a simple performance checking logic. You can define a PerformanceTraceAspect as follows:

@Aspect
public class PerformanceTraceAspect {
    private final Log logger = LogFactory = LogFactory.getLog(PerformanceTraceAspect.class);
    
    @Pointcut("execution(public void *.method1())")
    public void method1(a){}
    @Pointcut("execution(public void *.method2())")
    public void method2(a){}
    @Pointcut("method1() || method2()")
    public void compositePointcut(a){}
    
    @Around("compositePointcut()")
    public Object performanceTrace(ProceedingJoinPoint joinpoint) throws Throwable {
        StopWatch watch = new StopWatch();
        try {
            watch.start();
            return joinpoint.proceed();
        }
        finally {
            watch.stop();
            
            if (logger.isInfoEnabled()) {
                logger.info("PT in method[" +joinpoint.getSignature().getName()+ "] > >"+watch.toString()); }}}}Copy the code

The Around Advice definition intercepts the JoinPoint specified by the compositePointcut(), which is execution of method1 or method2.

If you weave the crosscutting logic defined in PerformanceTraceAspect into NestableInvocationBO, then run the following code and see the results:

AspectJProxyFactory weaver = new AspectJProxyFactory(new NestableInvocationBO());
weaver.setProxyTargetClass(true);
weaver.addAspect(PerformanceTraceAspect.class);
Object proxy = weaver.getProxy();
(NestableInvocationBO) proxy.method2();
(NestableInvocationBO) proxy.method1();
Copy the code

You get the following output:

method2 executed! 701 [main] INFO ... Performancetraceaspect-pt in method[method2]>>0:00:00.000 method2 executed! method1 executed! 701 [main] INFO ... Performancetraceaspect-pt in method[method1]>>0:00:00.000Copy the code

Do you see the problem? When we call Method2 of the NestableInvocationBO object directly from the outside, because the method signature matches the Pointcut definition of the Around Advice in PerformanceTraceAspect, so, The Around Advice logic executes, that is, PerformanceTraceAspect intercepts the execution of Method2 successfully. However, when method1 is called, only the execution of method1 is intercepted, and the execution of method2 inside method1 is not intercepted, because only PT in method[method1] has information in the input log.

Cause analysis,

This result comes down to the implementation mechanism of Spring AOP. As we know, Spring AOP implements AOP in the proxy pattern, where specific crosscutting logic is added to dynamically generated proxy objects. As long as methods on the target object’s proxy object are invoked, it is generally guaranteed that method execution on the target object can be intercepted. Just like NestableInvocationBO’s method2 method executes, when we call method2 on the proxy object, the target object’s method2 will be successfully intercepted.

However, the implementation mechanism of the proxy pattern creates a small “flaw” in handling the timing of method invocations for AOP products implemented using this mechanism. Let’s look at the general timing of proxy object methods and target object methods as follows:

Proxy. method2 {Record the start time of a method call; target.method2(); Record the end time of the method call; Calculate the elapsed time and log it; }Copy the code

In proxy object methods, no matter how much or how much crosscutting logic you add, one thing is certain: you will need to call the same method on the target object to perform the method logic originally defined.

If the original method call in the target object depends on another object, that’s fine. We can inject the proxy of the dependent object into the target object, and we can ensure that the corresponding Joinpoint is intercepted to inject the crosscutting logic. The problem arises when the original method call in the target object calls its own method directly, that is, it relies on other methods that it defines, as shown in the figure below:

After the execution of the proxy object’s method1 method goes through a series of interceptors that eventually redirect the call to method1 on the TargetObject, the rest of the call flow is all on top of TargetObject. When method1 calls method2, It calls method2 on the TargetObject, not on the ProxyObject. Note that the crosscutting logic for Method2 is only woven into the method2 method on the ProxyObject, so calls to method2 in method1 are not intercepted successfully.

The solution

Knowing the reason, we can “apply the right medicine”.

When the target object depends on other objects, we can solve the interception problem by injecting the proxy of the dependent object into the target object. Then, when the target object is dependent on itself, we can also try to expose the proxy object of the target object to it. Simply having the target object call methods on the proxy object solves the problem that the methods called internally are not intercepted.

Spring AOP provides AopContext to expose the proxy object of the current target object. We can simply use aopContext.currentProxy () in the target object to get the proxy object corresponding to the current target object. Now, let’s refactor the target object so that it calls the corresponding method on its proxy object directly:

public class NestableInvocationBO {
    public void method1(a) {
        ((NestableInvocationBO)AopContext.currentProxy()).method2();
        System.out.println("method1 executed!");
    }
    public void method2(a) {
        System.out.println("method2 executed!"); }}Copy the code

To make aopContext.currentProxy () work, we need to set the exposeProxy property of ProxyConfig or its corresponding subclass to true when generating a proxy object for the target object, as shown below:

AspectJProxyFactory weaver = new AspectJProxyFactory(new NestableInvocationBO());
weaver.setProxyTargetClass(true);
weaver.setExposeProxy(true); / / * * *
weaver.addAspect(PerformanceTraceAspect.class);
Object proxy = weaver.getProxy();
(NestableInvocationBO) proxy.method2();
(NestableInvocationBO) proxy.method1();
Copy the code

Now, we have the interception we want

method2 executed! 611 [main] INFO ... Performancetraceaspect-pt in method[method2]>>0:00:00.000 method2 executed! 611 [main] INFO ... Performancetraceaspect-pt in method[method2]>>0:00:00.000 method1 executed! 611 [main] INFO ... Performancetraceaspect-pt in method[method1]>>0:00:00.000Copy the code

The problem is solved, but not in a very elegant way, because our target objects are tied directly to Spring AOP’s specific APIS. So, let’s consider refactoring the target object definition. We can declare a dependency on its proxy object in the target object, and the IoC container helps us inject the proxy object.

Pay attention to

In fact, this situation is just a small trap caused by Spring AOP’s implementation mechanism. If you weave the crosscutting logic directly into the target object, as AspectJ does, then the proxy object and the target object are in effect one, and the invocation does not have this problem.