GitHub 18K Star Java engineer into god’s path, not to learn about it!

GitHub 18K Star Java engineer into god’s road, really not to learn about it!

GitHub 18K Star Java engineer into god’s road, really really don’t come to know it!

A few days ago, I just published an article called “Custom annotations! Are absolutely a great tool for programmers to install!” , explained how to use Spring AOP + custom annotations to improve the elegance of your code.

After reading many readers said that it is very cool to use, but some people said that after configuring Spring AOP, they found that the aspect does not work.

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

This shows that this problem is easily overlooked and the consequences of this problem can be extremely serious. So, let’s just briefly review what the problem is.

Problem reproduction

Initially I defined an annotation to make it easier to uniformly cache some database operations. Here is the code:

First, define an annotation:

@target (ElementType.METHOD) @Retention(retentionPolicy.runtime) public @interface Cacheable {/** * * * @return */ public String keyName(); /** @return */ public int expireTime(); }Copy the code

Then define a custom section for all methods that use this annotation:

@Aspect @Component public class CacheableAspect { private static final Logger LOGGER = LoggerFactory.getLogger(FacadeAspect.class); @Around("@annotation(com.hollis.cache.Cacheable)") public Object cache(ProceedingJoinPoint pjp) throws Throwable { // Check the cache first, if there is a value in the cache, return it. If not, the method is executed and the return value is stored in the cache. }}Copy the code

You can then use the annotation as follows:

@Component public class StrategyService extends BaseStrategyService { public PricingResponse getFactor(Map<String, String> pricingParams) {return this.loadFactor(tieredPricingParams); } @Override @Cacheable(keyName = "key0001", expireTime = 60 * 60 * 2) private PricingResponse loadFactor(Map<String, String> pricingParams) {// code execution}}Copy the code

Above, the loadFactor method has been added to the section. For ease of use, we also define a getFactor method, set to public, to facilitate external calls.

However, during debugging, I found that the section we set above the loadFactor method did not succeed and could not execute the section class.

I started to figure out what the problem was.

Troubleshoot problems

In order to check this problem, the first is to check all the code, to see if there is a problem in the section of the code, there may be a hand error typing and so on.

But they didn’t. So I tried to find a way to find the problem.

Then I changed the loadFactor access permission from private to public and found no effect.

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

Found that this can be successfully executed into the section.

When I discovered this, I suddenly did a double take and punched my thigh. That’s how it is, that’s how it’s supposed to be.

The cause of the problem suddenly occurred to me. In fact, the reason is quite simple, which is also the principle I have learned before. However, I did not think of this principle when the problem just happened. Instead, I suddenly thought of this principle after DISCOVERING this phenomenon through debugging.

So, let’s talk about why this happens.

How the proxy is invoked

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

1. Inside the class, call 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... }}Copy the code

2. Outside the class, call 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(); }}Copy the code

The class relationship and call process are shown as follows:

If the method is static, it can also be called directly from the class.

Outside the class, call from the class’s proxy object:

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(); }}Copy the code

The class relationship and call process are shown as follows:

So, Spring AOP is actually the third way to call, is through the proxy object call, only this call, before and after the execution of the real object, can let the proxy object also execute the relevant code, in order to play a cutting role.

In the case of a call using this, it is a self-call and does not use a proxy object, so it cannot execute the faceted class.

Problem solving

So, we know that to actually execute the proxy, we need to call through the proxy object instead of using the this call.

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

There are many ways to solve this problem on the Internet, but here is a relatively simple one. There are more ways to do this and you can find some examples on the Internet. Search for the keyword “AOP self-invocation “.

Get the proxy object for the call

We need to modify the StrategyService code to the following:

@Component public class StrategyService{ public PricingResponse getFactor(Map<String, String> pricingParams) {// Do some parameter verification and exception catching related things // Instead of using this.loadfactor, use aopContext.currentProxy (), If (aopContext.currentProxy () instanceof StrategyService) {return ((StrategyService)AopContext.currentProxy()).loadFactor(tieredPricingParams); } else {return loadFactor(tieredPricingParams); } } @Override @StrategyCache(keyName = "key0001", ExpireTime = 60 * 60 * 2) private PricingResponse loadFactor(Map<String, String> oricingParams) {Copy the code

Aopcontext.currentproxy () is used to get the proxy object and invoke the corresponding method through the proxy object.

One other thing to note is that this method also requires the Aspect’s expose-proxy to be set to true. If the configuration file is modified:

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

If SpringBoot is used, modify the annotation of the application launch entry class:

@EnableAspectJAutoProxy(exposeProxy = true)
public class Application {

}
Copy the code

conclusion

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

AOP failure is a serious problem, because if an unexpected failure occurs, the immediate problem is that the aspect method is not executed, and more serious consequences can be problems such as transactions not executed, logs not printed, caches not queried, and so on.

So, after reading this article, I recommend that you take a look at your own code to see if there is a case of method invocation. In this case, no section will work!

About the author: Hollis, a person with a unique pursuit of Coding, is a technical expert of Alibaba, co-author of “Three Courses for Programmers”, and author of a series of articles “Java Engineers becoming Gods”.

If you have any comments, suggestions, or want to communicate with the author, you can follow the public account [Hollis] and directly leave a message to me in the background.