scene
Dubbo has learned that external services may have multiple methods. Generally, in order not to bury a hole for the caller, we will catch all exceptions in each method and return only one result. The caller will judge whether the call is successful according to the success in the result, for example
public class ServiceResultTO<T> extends Serializable { private static final long serialVersionUID = xxx; private Boolean success; private String message; private T data; } public interface TestService { ServiceResultTO<Boolean> test(); } public class TestServiceImpl implements TestService { @Override public ServiceResultTO<Boolean> test() { try { // Write the execution logic in the service here return ServiceResultTO.buildSuccess(Boolean.TRUE); } catch(Exception e) { return ServiceResultTO.buildFailed(Boolean.FALSE, "Execution failed"); } } }
For example, the above dubbo service (TestService) has a test method. In order to make exceptions when executing normal logic, we outsource the execution logic of this method with a layer of "try... catch..." If there is only one test method, of course, this is no problem,
But the problem is that in the project, we generally need to provide dozens or hundreds of services, and each service has dozens of methods such as test,
If each method needs to package a layer of "try... Catch..." during execution, Although feasible, the code will be ugly and poor readability. Can you think of ways to improve it?
It can be solved by cutting plane (AOP)
Since it is solved by cutting, let me explain what cutting is first.
As we know, object-oriented abstracts the program into multiple levels of objects, and each object is responsible for different modules. In this way, each object has a clear division of labor, performs its own duties, and is not coupled with each other. It really effectively promotes engineering development and division of labor and cooperation,
However, new problems come. Sometimes there will be public behavior between different modules (objects). This public behavior is difficult to realize by inheritance. If you use tool classes, it is not conducive to maintenance, and the code is extremely cumbersome.
The introduction of aspect (AOP) is to solve such problems. Its effect is to ensure that developers add some general functions to different business components in the system without modifying the source code.

For example, in the above example, during the execution of the three service objects, there are the same behaviors such as security, transaction, cache and performance. Obviously, these same behaviors should be managed in the same place,
Some people say that I can write a unified tool class and embed this tool class before / after the methods of these objects. The problem is that these behaviors are business independent. Using tool class embedding leads to tight coupling with business code, which is very inconsistent with engineering specifications and code maintainability is extremely poor!
Aspect is to solve such problems, which can achieve unified management of the same functions and no invasion of business code.
Taking performance as an example, what are the similar functions of the modules responsible for these objects
For example, each service has different methods. I want to count the execution time of each method. If you don't use the aspect, you need to calculate the time at the beginning and end of each method, and then subtract it

If I want to count the execution time of each method in each service, it can be imagined that if there is no aspect, I have to add logic similar to the above at the beginning and end of each method. Obviously, the maintainability of such code is very poor,
This is just a statistical time. If this method needs to add transaction and risk control, do you have to add transaction start, rollback and other codes at the beginning and end of the method? It can be imagined that business code and non business code are seriously coupled. This implementation method is a disaster for the project and is unacceptable!
What should I do if I use a section
Before talking about solutions, let's first look at several definitions related to aspects

Aspect: similar to class declaration in Java, it is often used to configure transaction or log management in applications. Generally, use @ aspect annotation or < AOP: aspect > to define an aspect.
Join point: a specific point in program execution, such as method execution, handling an exception, etc
Pointcut: through a regular expression of rule matching, when a connection point can match the pointcut, the specified notification associated with the change of pointcut will be triggered.
Advice: there are five notification methods for actions taken at a connection point in the aspect
- Before Advice: weave before JoinPoints execution
- After Advice: woven after the JoinPoints are executed (whether or not an exception is thrown)
- After returning advice: weaved after the JoinPoints exit normally (it will not be weaved if an exception is thrown)
- After throwing advice: weaves after throwing an exception during method execution
- Around Advice: This is the most powerful of all advice. It can weave section code before and after joinpoint s, and can also choose whether to execute the original normal logic. If the original process is not executed, it can even replace the original return value with its own return value, or even throw exceptions. To sum up, Aspect can be considered as pointcut and advice. Pointcut specifies which join points can be woven, while advice specifies the code weaving timing and logic on these join points
weaving: the process of applying facets to delegate objects to create advised objects (i.e., proxies, which will be mentioned below)
Explain it in popular language
For example, when ordering in a restaurant, there are 10 dishes on the menu. These 10 dishes are JoinPoint, but I only ordered dishes with radish name. The condition with radish name is the screening condition for JoinPoint (10 dishes), that is, pointcut,
Finally, only the two joinpoints of carrot and white radish meet the conditions. Then we can wash our hands before eating carrots, or pay after eating carrots, or count the time around advice,
These actions of washing hands, paying bills and counting time are decoupled from the business action of eating turnips, which are uniformly written in the logic of advice.
Can you program it? Talk is heap, show me your code!
public interface TestService { // Eat radish void eatCarrot(); // Eat mushrooms void eatMushroom(); // Eat cabbage void eatCabbage(); } @Component public class TestServiceImpl implements TestService { @Override public void eatCarrot() { System.out.println("Eat radish"); } @Override public void eatMushroom() { System.out.println("Eat mushrooms"); } @Override public void eatCabbage() { System.out.println("Eat cabbage"); } }
Suppose the above TestService implements three methods: eating radish, eating mushroom and eating cabbage. These three methods are woven with sections, so they are all joinpoints,
But now I just want to weave advice into the join points before and after eating radish. What should I do,
First of all, of course, we should declare a pointcut expression. This expression indicates that we only want to weave the join point of eating radish. After that, we can apply advice to this pointcut,
For example, if I want to wash my hands before eating radish and pay after eating radish, I can write the following logic
@Aspect @Component public class TestAdvice { // 1. Define PointCut @Pointcut("execution(* com.example.demo.api.TestServiceImpl.eatCarrot())") private void eatCarrot(){} // 2. Define the advice applied to all PointCut conditions in JoinPoint. Here, we use around advice to weave enhanced logic into it @Around("eatCarrot()") public void handlerRpcResult(ProceedingJoinPoint point) throws Throwable { System.out.println("Wash your hands before eating turnips"); // The original testserviceimpl Eatarrot logic, which determines whether to execute according to the situation point.proceed(); System.out.println("Pay after eating pineapple"); } }
We can see that through AOP, we skillfully insert relevant logic before and after method execution, without any intrusion into the original execution logic!
There is another problem. How do you solve the first scene with the section.
This is about the AspectJ pointcut expression language declarative expression of PointCut. The types supported by this expression are relatively comprehensive. You can specify the qualified joinpoint with regular and annotation,
For example, add after the class name* (..) This regular expression means that all methods in this class will be woven in. You can also specify to weave code for methods marked with such annotations by using @ annotation
First, we define the following annotation
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface GlobalErrorCatch { }
Then "try... catch..." in the methods of all service s Remove it and add the annotation we defined above to the method signature
public class TestServiceImpl implements TestService { @Override @GlobalErrorCatch public ServiceResultTO<Boolean> test() { // Write the execution logic in the service here boolean result = xxx; return ServiceResultTO.buildSuccess(result); } }
Then specify pointcuts and around advice in the form of annotations
@Aspect @Component public class TestAdvice { // 1. Define all annotation methods with GlobalErrorCatch as Pointcut @Pointcut("@annotation(com.example.demo.annotation.GlobalErrorCatch)") private void globalCatch(){} // 2. Apply around advice to globalCatch() {} This PointCut @Around("globalCatch()") public Object handlerGlobalResult(ProceedingJoinPoint point) throws Throwable { try { return point.proceed(); } catch (Exception e) { System.out.println("Execution error" + e); return ServiceResultTO.buildFailed("System error"); } } }
In this way, all methods marked with GlobalErrorCatch annotation will be executed in the handlerGlobalResult method,
We can uniformly catch exceptions in this method. The long and smelly "try...catch..." in all service methods Kill them all. It smells good!
Talk about the implementation principle of AOP
First, print the class of TestServiceImp bean
@Component public class TestServiceImpl implements TestService { @Override public void eatCarrot() { System.out.println("Eat radish"); } } @Aspect @Component public class TestAdvice { // 1. Define PointCut @Pointcut("execution(* com.example.demo.api.TestServiceImpl.eatCarrot())") private void eatCarrot(){} // 2. Define the advice applied to PointCut. Here we use around advice @Around("eatCarrot()") public void handlerRpcResult(ProceedingJoinPoint point) throws Throwable { // Omit related logic } } @SpringBootApplication @EnableAspectJAutoProxy public class DemoApplication { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(DemoApplication.class, args); TestService testService = context.getBean(TestService.class); System.out.println("testService = " + testService.getClass()); } }

After printing, I found a clue. The class of this bean is not TestServiceImpl! It's com example. demo. impl. TestServiceImplEnhancerBySpringCGLIB$$705c68c7!
So, why is such a class generated
We notice that there is an EnhancerBySpringCGLIB in the class name. Note that CGLiB is the dynamic proxy generated through it
Let's not talk about dynamic agents, let's talk about what agents are
Agents can be seen everywhere in life. For example, when I want to buy a house, I usually don't directly connect with the seller. I usually deal with intermediaries. Intermediaries are agents, sellers are the target object, and I am the caller,
The agent not only realizes the behavior of the target object (selling a house for the target object), but also adds its own actions (collecting deposit, signing a contract, etc.),

It is represented by UML diagram, which is as follows

The Client directly deals with the Proxy. The Proxy is the Proxy of the RealSubject that the Client really wants to call. It does execute the request method of the RealSubject,
However, before and after this execution, the Proxy also adds an additional PreRequest() afterRequest() method. Note that both Proxy and RealSubject implement the Subject interface,
In this way, it seems that there is no difference between the caller and the Client (interface oriented programming has no influence on the caller, because the implemented interface methods are the same),
Proxy holds the real target object (RealSubject) to be proxied through its properties to achieve the purpose of not only calling the method of the target object, but also injecting other logic before and after the method.
First introduce the types of agents
Agents are mainly divided into two types: static agents and dynamic agents. Dynamic agents include JDK agents and CGLib agents,
Let me first explain the meaning of static and dynamic:
To understand the two meanings of static and dynamic, we first need to understand the running mechanism of Java programs

Firstly, the Java source code is compiled to generate bytecode, and then the JVM loads, connects and initializes it into Java type,
We can see that bytecode is the key. The difference between static and dynamic is the timing of bytecode generation.
Static proxy:
Programmers create proxy classes or specific tools to automatically generate source code and then compile it. The interface, proxy class (delegate class) and proxy class have been determined during compilation. The. Class file of the proxy class already exists before the program runs.
Dynamic proxy:
After the program runs, the bytecode is created through reflection, and then loaded by the JVM.
Here, first write down the static proxy
According to the following UML

public interface Subject { public void request(); } public class RealSubject implements Subject { @Override public void request() { // Sell a house System.out.println("Sell a house"); } } public class Proxy implements Subject { private RealSubject realSubject; public Proxy(RealSubject subject) { this.realSubject = subject; } @Override public void request() { // Execute agent logic System.out.println("Before selling"); // Execute target object method realSubject.request(); // Execute agent logic System.out.println("After selling"); } public static void main(String[] args) { // Proxied object RealSubject subject = new RealSubject(); // agent Proxy proxy = new Proxy(subject); // Proxy request proxy.request(); } }
What are the disadvantages of static agents
Static agents have two main disadvantages
- The proxy class only represents one delegate class (in fact, it can represent more than one delegate class, but it does not conform to the principle of single responsibility), which means that if you want to represent more than one delegate class, you have to write multiple proxies (don't forget that the static proxy must be determined before compilation)
- The first point is not fatal. Consider this scenario: if each method of each delegate class needs to be woven into the same logic, for example, if I want to calculate the time consumption of each method of each delegate class mentioned above, I need to weave the code for calculating the time before and after the method starts, even if the proxy class is used, Its method also has countless such repeated code to calculate time
How can we improve it
It is necessary to mention dynamic agents. These disadvantages of static agents are mainly because these agent classes are determined before compilation. If these agent classes are generated dynamically, can a lot of agent code be omitted.
First write the dynamic agent of JDK and explain its principle
Dynamic agents are divided into dynamic agents provided by JDK and agents generated by CGLib used in Spring AOP,
Let's first look at how to write the dynamic proxy provided by JDK
// Delegate class public class RealSubject implements Subject { @Override public void request() { // Sell a house System.out.println("Sell a house"); } } import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class ProxyFactory { private Object target;// Maintain a target object public ProxyFactory(Object target) { this.target = target; } // Generate proxy object for target object public Object getProxyInstance() { return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Calculate start time"); // Execute target object method method.invoke(target, args); System.out.println("Calculation end time"); return null; } }); } public static void main(String[] args) { RealSubject realSubject = new RealSubject(); System.out.println(realSubject.getClass()); Subject subject = (Subject) new ProxyFactory(realSubject).getProxyInstance(); System.out.println(subject.getClass()); subject.request(); } } ``` The printing results are as follows: ```shell Primitive class:class com.example.demo.proxy.staticproxy.RealSubject proxy class:class com.sun.proxy.$Proxy0 Calculate start time Sell a house Calculation end time
We notice that the class of the proxy class is com sun. proxy.$ Proxy0, how is it generated? Notice that proxy is in Java Lang.reflect reflection package. Please note the newProxyInstance signature of proxy
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h);
- loader: the ClassLoader of the proxy class, which finally reads the dynamically generated bytecode and converts it into Java An instance (i.e. class) of lang. class can create the proxy object through the newInstance() method of this instance
- Interfaces: the interface implemented by the delegate class. JDK dynamic agent should implement the interfaces of all delegate classes
- InvocationHandler: all interface method calls of the delegate object will be forwarded to InvocationHandler Invoke(), we can add any logic that needs to be enhanced in the invoke() method, which is mainly generated through reflection according to the interface of the delegate class
What are the benefits of such an implementation
Since the dynamic agent is generated only after the program runs, which delegate class needs to be proxied, only the dynamic agent can be generated, avoiding the hard coding of static agents,
In addition, all the methods of the delegate class implementing the interface will be in the invocationhandler Execute in invoke(),
In this way, if you want to count the execution time of all methods, the same logic can be written in InvocationHandler,
This avoids the problem of inserting the same code into all methods as static agents, and the maintainability of the code is greatly improved.
So why not use Spring AOP for its implementation
Although JDK dynamic proxy is good, it also has weaknesses. We noticed the method signature of newProxyInstance
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h);
Note that the second parameter Interfaces is the interface of the delegate class and must be passed. JDK dynamic proxy is implemented by implementing the same interface as the delegate class and then enhancing it in the implemented interface method,
This means that if you want to use JDK proxy, the delegate class must implement the interface. This implementation method seems a little stupid. What's the better way? It's OK to inherit directly from the delegate class. In this way, the logic of the delegate class does not need to be changed. That's what CGlib does.
Let's talk about CGLib dynamic proxy
OK, the AOP mentioned at the beginning is generated in the form of CGLib,
JDK dynamic Proxy uses Proxy to create Proxy class, and the enhanced logic is written in invocationhandler In invoke(),
CGlib dynamic proxy also provides a similar enhancement class, and the enhancement logic is written in methodinterceptor In intercept(),
In other words, all non final methods of the delegate class will be intercepted by the method interceptor. Let's see how it works before we talk about its principle.
public class MyMethodInterceptor implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("Before target class enhancement!!!"); //Note that the method call here is not reflection!!! Object object = proxy.invokeSuper(obj, args); System.out.println("After the target class is enhanced!!!"); return object; } } public class CGlibProxy { public static void main(String[] args) { //Create an Enhancer object, which is similar to the Proxy class of JDK dynamic Proxy. The next step is to set several parameters Enhancer enhancer = new Enhancer(); //Set the bytecode file of the target class enhancer.setSuperclass(RealSubject.class); //Set callback function enhancer.setCallback(new MyMethodInterceptor()); //The Create method here is to formally creat e the proxy class RealSubject proxyDog = (RealSubject) enhancer.create(); //Call the eat method of the proxy class proxyDog.request(); } }
Print as follows
Agent class: class @ com example. demo. proxy. staticproxy. RealSubject$$EnhancerByCGLIB$$889898c5 Before target class enhancement!!! Sell a house After the target class is enhanced!!!
It can be seen that the class Enhancer is mainly used to set delegate classes and method interceptors,
In this way, all non final methods of the delegate class can be intercepted by the method interceptor, so as to realize the enhancement in the interceptor.
What is the underlying implementation principle
As mentioned earlier, it implements code enhancement by inheriting from the delegate class, rewriting the non final method of the delegate class (the final method cannot be overloaded), and calling the method of the delegate class in the method,
Its implementation is probably like this
public class RealSubject { @Override public void request() { // Sell a house System.out.println("Sell a house"); } } /** Generated dynamic proxy class (simplified version)**/ public class RealSubject$$EnhancerByCGLIB$$889898c5 extends RealSubject { @Override public void request() { System.out.println("Before enhancement"); super.request(); System.out.println("After enhancement"); } }
You can see that it does not require the delegate class to implement any interface,
Moreover, CGLIB is an efficient code generation package. The bottom layer relies on ASM (open source java bytecode editing Class Library) to operate bytecode, and its performance is better than JDK,
Therefore, Spring AOP finally uses CGlib to generate dynamic proxy
Are there any restrictions on the use of CGlib dynamic proxy
The first point has been mentioned before. You can only proxy any non final method in the delegate class. In addition, it generates the proxy by inheriting from the delegate class. Therefore, if the delegate class is final, it cannot be proxied (the final class cannot be inherited)
The intercepting object of JDK dynamic proxy calls the intercepted method through the reflection mechanism. What about CGlib? What mechanism does it use to improve the calling efficiency of the method.
Due to the low efficiency of reflection, CGlib adopts the FastClass mechanism to call the intercepted method.
FastClass mechanism is to index the methods of a class and directly call the corresponding methods through the index,
Recommended reference https://www.cnblogs.com/cruze/p/3865180.html Learn this link
There is another problem. We know that cglib generates a dynamic proxy such as realsubjectenhancerbycglib $$8898c5 by printing the class name. Have you decompiled its class file to understand the generation rules of cglib proxy class
Also in the reference link
Postscript
AOP is a very important feature of Spring. Through aspect programming, it effectively realizes the unified management of the same behavior of different modules and effectively decouples with business logic. Making good use of AOP can sometimes achieve a surprising effect. For example, we have such a demand in our business that we need to go through risk control before the implementation of some core logic in different modules, After the risk control is passed, these core logics can be executed. How to implement it? Of course, you can uniformly encapsulate a risk control tool class, and then insert the code of the risk control tool class before the execution of these core logics, However, in this way, the core logic and non core logic (risk control, transaction, etc.) are coupled together. Obviously, a better way should use AOP. These non core logic should be decoupled into the aspect for execution by using the annotation + AOP method described in this paper, which greatly improves the maintainability of the code.
Due to space constraints, this paper does not analyze the implementation of dynamic proxy generation of JDK and CGlib, but it is suggested that you can have a look if you have spare efforts. Especially the reference link at the end of this paper, the generation of dynamic proxy mainly uses the reflection feature. However, we know that reflection has certain performance problems. In order to improve performance, some buffer bytecodes are used at the bottom, FastClass and other technologies to improve performance. After reading through the source code, the understanding of reflection will be greatly deepened.
reference resources:
How does Spring AOP work? Complete this interview necessary question https://cloud.tencent.com/developer/article/1584491
https://mp.weixin.qq.com/s/NXZp8a3n-ssnC6Y1Hy9lzw