I stepped on the same spring AOP pit twice a day. Deep pit

Posted by feign3 on Mon, 31 Jan 2022 17:25:38 +0100

A few days ago, I just published an article "custom annotations! Definitely a sharp weapon for programmers to install B!!", Introduced how to use Spring AOP + custom annotations to improve the elegance of the code.

Many readers said it was cool to use after reading it, but someone in the background left a message saying that after configuring Spring's AOP, they found that the aspect did not take effect.

In fact, I have encountered this problem in the process of using it, and I have encountered the same problem twice in a day.

It shows that this problem is easy to be ignored, and the consequences of this problem may be extremely serious. So, let's briefly review the problem.

Problem recurrence

At first, I defined an annotation, hoping to cache some database operations conveniently and uniformly. So you have the following code:

First, define an annotation:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Cacheable {

    /**
     * Policy name, which must be unique
     *
     * @return
     */
    public String keyName();

    /**
     * Timeout duration in seconds
     *
     * @return
     */
    public int expireTime();

}

Then customize a section and perform section processing on all methods using the annotation:

@Aspect
@Component
public class StrategyCacheAspect {
    private static final Logger LOGGER = LoggerFactory.getLogger(FacadeAspect.class);
    @Around("@annotation(com.hollis.cache.StrategyCache)")
    public Object cache(ProceedingJoinPoint pjp) throws Throwable {
        // Check the cache first. If there is a value in the cache, it will be returned directly. If not in the cache, execute the method first, and then store the return value in the cache.
    }

Then you can use the annotation as follows:

@Component
public class StrategyService extends BaseStrategyService  {

    public PricingResponse getFactor(Map<String, String> pricingParams) {
        // Do some parameter verification and exception capture related things
        return this.loadFactor(tieredPricingParams);
    }

    @Override
    @StrategyCache(keyName = "key0001", expireTime = 60 * 60 * 2)
    private PricingResponse loadFactor(Map<String, String> pricingParams) {
        //Code execution
    }
}

In the above, we added a facet to the loadFactor method. For convenience, we also defined a getFactor method, which is set to public to facilitate external calls.

However, during debugging, I found that the aspect we set on the loadFactor method was not successful and the aspect class could not be executed.

So he began to investigate the specific problem.

Troubleshooting

In order to check this problem, first of all, check all the codes to see if there is a problem with the code in the section, and whether there may be a mistake in typing by hand.

But I didn't find it. So I tried to find a problem.

Next, I changed the access permission of loadFactor from private to public, and found that it had no effect.

Then I try to call loadFactor directly outside the method instead of getFactor.

It is found that this can be successfully implemented into the section.

When I found this phenomenon, I suddenly realized it and pounded my thigh. I see. I see. It should be.

I suddenly thought of the reason for the problem. In fact, the reason is quite simple. It's also the principle I've learned before, but I didn't think of it when the problem just happened. I suddenly thought of it after I found this phenomenon through debug.

So, let's talk about why such a problem occurs.

Call method of agent

We found that the key to the above problem is that the loadFactor method is called in different ways. As we know, methods are usually called in the following ways:

1. Within the class, self call through this:

public class SimplePojo implements Pojo {

    public void foo() {
        // this next method invocation is a direct call on the 'this' reference
        this.bar();
    }

    public void bar() {
        // some logic...
    }
}

2. Outside the class, it is called through the object of the class

public class Main {

    public static void main(String[] args) {
        Pojo pojo = new SimplePojo();
        // this is a direct method call on the 'pojo' reference
        pojo.foo();
    }
}

Class relationship and calling process are shown in the following figure:

If it is a static method, it can also be called directly through a class.

3. Outside the class, call through the proxy object of the class:

public class Main {

    public static void main(String[] args) {
        ProxyFactory factory = new ProxyFactory(new SimplePojo());
        factory.addInterface(Pojo.class);
        factory.addAdvice(new RetryAdvice());

        Pojo pojo = (Pojo) factory.getProxy();
        // this is a method call on the proxy!
        pojo.foo();
    }
}

Class relationship and calling process are shown in the following figure:

Well, Spring's AOP is actually the third way to call, that is, through proxy object. Only this way can make the proxy object execute relevant code before and after the execution of the real object, so as to play the role of aspect.

For this method, this method is only self calling and will not be called with a proxy object, so the facet class cannot be executed.

Problem solving

Then, we know that if you want to really execute the proxy, you need to call through the proxy object instead of using this call.

Then, the solution to this problem is to find a way to call the target method through the proxy object.

There are many ways to solve this problem online. Here is a relatively simple one. Other more ways, you can find some cases on the Internet. Search for the keyword "AOP self call".

Get proxy object to call

We need to modify the previous code of StrategyService to the following content:

@Component
public class StrategyService{

    public PricingResponse getFactor(Map<String, String> pricingParams) {
        // Do some parameter verification and exception capture related things
        // This is not used here LoadFactor uses aopcontext instead Currentproxy() is called to solve the problem that AOP proxy does not support method self invocation
        if (AopContext.currentProxy() instanceof StrategyService) {
            return ((StrategyService)AopContext.currentProxy()).loadFactor(tieredPricingParams);
        } else {
            // If some implementations have not been proxied, they can be called directly
            return loadFactor(tieredPricingParams);
        }
    }

    @Override
    @StrategyCache(keyName = "key0001", expireTime = 60 * 60 * 2)
    private PricingResponse loadFactor(Map<String, String> oricingParams) {
        //Code execution
    }
}

That is, aopcontext Currentproxy () obtains the proxy object, and then calls the corresponding method through the proxy object.

It should also be noted that the above methods also need to set the expose proxy of Aspect to true. In case of profile modification:

 <aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/>

If it is SpringBoot, modify the annotation of the application startup entry class:

@EnableAspectJAutoProxy(exposeProxy = true)
public class Application {

}

summary

Above, we analyzed and solved a problem that Spring AOP does not support method self invocation.

In fact, the problem of AOP failure is still very serious, because if an unexpected failure occurs, the direct problem is that the aspect method is not executed. The more serious consequences may be various problems such as transaction failure, log failure to print, cache failure to query and so on.

last

Authoritative guide - the first Docker book

Lead the installation, deployment, management and expansion of Docker, let it go through the whole development life cycle from test to production, and have an in-depth understanding of what scenarios Docker is suitable for. In addition, this authoritative guide to Docker introduces the basic knowledge of its components, and then uses Docker to build containers and services to complete various tasks: using Docker to establish a test environment for new projects, demonstrating how to integrate Docker using continuously integrated workflow, how to build application services and platforms, how to use Docker's API, and how to expand Docker.

It includes nine chapters: introduction, installing Docker, getting started with Docker, using Docker image and warehouse, using Docker in testing, using Docker to build services, using Fig to configure Docker, using Docker API, getting help and improving Docker.

About the "K8S+Docker learning guide" strongly recommended by Ali - "in simple terms, Kubernetes: Theory + practice" and "authoritative guide - the first Docker book", after reading, it is described in two words: love, love!

If you love too, Click here to download the "K8S+Docker" learning guide for free

616019979)]

[external chain picture transferring... (img-UQFSG2Zi-1623616019980)]

About the "K8S+Docker learning guide" strongly recommended by Ali - "in simple terms, Kubernetes: Theory + practice" and "authoritative guide - the first Docker book", after reading, it is described in two words: love, love!

If you love too, Click here to download the "K8S+Docker" learning guide for free

Topics: Java Interview Programmer