[Spring] In-depth exploration of Spring AOP: analysis of concepts, usage and implementation principles


foreword

In today's software development, Object-Oriented Programming (OOP) has become the mainstream programming paradigm. It uses objects as the basic component units of programs, and organizes code through features such as encapsulation, inheritance, and polymorphism. maintainability and scalability of the application . However, with the continuous expansion of software scale and complexity, OOP may face some challenges in some cases.

A common problem is the coupling between business logic and横切关注点(cross-cutting concerns) . The so-called 横切关注点就是那些在应用程序中分布广泛,与核心业务逻辑代码相互交织在一起的功能, such as login judgment, logging, transaction management, authority control, etc. Tightly coupling these concerns with the core business logic leads to duplicative and difficult-to-maintain code . Therefore, in this case, a programming paradigm of aspect-oriented programming (Aspect-Oriented Programming, referred to as AOP) came into being.

1. Getting to know Spring AOP for the first time

In traditional object-oriented programming, we organize code through encapsulation, inheritance, and polymorphism, and encapsulate related functional logic into object methods. However, in actual development, in addition to the core business logic, there are often some irrelevant but necessary functions, such as logging, performance monitoring, security control, etc. These functions are often scattered in multiple places in the code, resulting in repetitive and difficult to maintain code.

1.1 What is AOP

Aspect-Oriented Programming (AOP) is a programming paradigm created to solve this problem of code repetition and difficulty in maintenance. AOP 的核心思想是将横切关注点(cross-cutting concerns)从主要的业务逻辑中分离出来,以模块化的方式进行管理. Cross-cutting concerns are those functions that are widely distributed in the application and intertwined with the core business logic, such as logging, transactions, permissions, etc.

AOP divides the system into two main parts: 核心关注点和横切关注点. Core concerns are the main business logic of the application, while crosscutting concerns are functions that are not related to the core concerns but are necessary .

AOP abstracts cross-cutting concerns into modules called aspects (Aspect), and then manages and maintains them independently of core business logic . In this way, we can flexibly add, modify or delete various functions without modifying the main business logic, thereby improving the maintainability and scalability of the code.

1.2 What is Spring AOP

Just like the relationship between IoC and DI, AOP是一种思想,而 Spring AOP 则是 AOP 思想的一种实现. At the same time, Spring AOP is also an important module in the Spring framework, which allows developers to easily implement aspect-oriented programming.

In short, Spring AOP builds on traditional AOP concepts to provide a simpler and more convenient way to manage cross-cutting concerns. It implements the modularization of cross-cutting concerns by executing the logic of the aspect before, after or around the core concerns through proxy technology. Compared with traditional object-oriented programming, using Spring AOP can better separate concerns, making the code clearer and more maintainable.

Spring AOP supports two types of proxies: 基于接口的 JDK 动态代理and 基于类的 CGLIB 动态代理. It also provides a series of annotations and configuration options, allowing developers to flexibly define aspects and advice (Advice), and apply them to different target objects.

2. The core concept of AOP

In Aspect-Oriented Programming (AOP), several core concepts are key to help us understand how to manage and apply cross-cutting concerns. These concepts include aspects, pointcuts, advice, and joinpoints, which together form the basis of AOP.

2.1 Aspect

切面(Aspect)由切点(Pointcut)和通知(Advice)组成,它是对横切关注点的抽象化. It defines what kind of crosscutting logic should be executed at which pointcuts. An aspect is a modular unit that separates common cross-cutting concerns from the main business for better management and maintenance.

More generally speaking, an aspect is equivalent to a class in Java, which represents the specific content of a certain aspect, similar to a code module.

  • For example, we can regard the user login judgment as an "aspect", which contains the logic that needs to be executed during the user login process. Similarly, the statistical record of the log is also a "section", which includes related operations that need to be logged when the code is executed.
  • An aspect is like a toolbox with specific functions, and each toolbox contains a set of related operations. In code, an aspect defines where (pointcuts) and how to perform these operations. When we need to apply similar functions in different places, we can create an aspect to encapsulate related operations in it.

2.2 Pointcut

切点(Pointcut)可以被理解为匹配连接点(Join Point)的谓词,或者说它是一组规则的集合,用于选择在哪些连接点上应用横切逻辑. Pointcuts AspectJ pointcut expression languagedescribe these rules using , a language that allows you to flexibly define matching conditions to precisely select specific join points.

AspectJ pointcut expression languageIs an expression language used to define pointcuts (Pointcut), which is a key part of the AspectJ framework. This expression language allows us to flexibly describe at which join points advice should be applied.

The role of the pointcut is to provide us with a way to define rules in order to filter out the connection points that meet the conditions. When the connection points meet the rules defined by the pointcut, we can apply advice (Advice) to these connection points, so as to insert crosscutting logic at these specific positions. This allows us to selectively insert specific behaviors into the code without coupling crosscutting concerns with the main business logic.

2.3 Advice

In AOP, an aspect is not just an abstract concept, it has a clear goal. Aspects need to complete specific tasks, and in AOP terminology, these tasks are called advice (Advice) . 通知定义了切面的具体行为:它解决了切面是什么、何时使用以及在何时执行这些行为的问题.

In Spring's aspect class, different annotations can be used to mark methods as notification methods , which will be called when conditions are met:

  • Use of pre-notification@Before : the notification method will be executed before the target method is called, and some preprocessing operations can be performed.

  • Use of post-notification@After : the notification method will be called after the target method returns or throws an exception, and some aftermath operations can be performed.

  • Use of notification after return@AfterReturning : the notification method will be called after the target method returns successfully, and the return value can be processed.

  • Use of notification after throwing an exception@AfterThrowing : the notification method will be called after the target method throws an exception, which can handle the exception.

  • Surround notification use@Around : notification wraps the notified method, executes custom behavior before and after the target method is called, and can also control whether to call the target method and how to handle the return value.

2.4 Join Points

A join point represents an actual point in program execution, and it includes all points that can trigger a pointcut . In other words, the join point is the actual occurrence in the code of the pointcut. Advice (Advice) will be executed on the connection point, so as to realize the addition or modification of cross-cutting concerns. A join point is a point in AOP that is intercepted.

The concept of the entire component of AOP is shown in the figure below, taking multiple pages to access user login permissions as an example:

In addition to aspects, pointcuts, advice, and joinpoints, AOP covers some other important concepts and terms. Here are some related concepts:

  1. Introduction: An introduction is a special type of notification that allows adding a new method or field to an existing class. It is thus achieved that new functionality can be introduced into existing classes without modifying their code.

  2. Target Object (Target Object): The target object is the object in the application, which is affected and intercepted by the aspect. Aspects apply advice on join points on target objects.

  3. Proxy (Proxy): The proxy is the packaging of the target object. It allows the aspect to insert advice into the connection point of the target object to implement cross-cutting logic. Proxies can be implemented through static proxies, JDK dynamic proxies, or CGLIB dynamic proxies.

  4. Weaving: Weaving is the process of connecting aspects with target objects. At compile time, load time or run time, aspect advice is inserted into the join point of the target object to implement the logic of cross-cutting concerns.

  5. Advice Order: If there are multiple advices on a pointcut, the order in which the advices are executed can be important. Advice order defines the order in which multiple advices are executed on pointcuts.

  6. Aspect Priority: If there are multiple aspects in the application, the priority of the aspects can affect the execution order of the advice. The aspect priority determines the order of execution among multiple aspects.

  7. Dynamic Aspects (Dynamic Aspects): Dynamic aspect is a mechanism to determine whether to apply an aspect based on certain conditions at runtime. Allows you to dynamically choose whether to apply facets to the target object as needed.

3. Use of Spring AOP

The above concepts can be said to be obscure and difficult to understand. The following uses Spring AOP to simulate and realize the functions of AOP, which can better help us understand AOP. At this point, our goal is to intercept UserControllerall methods in , and each time one of the methods is called, the corresponding notification event will be executed.

The steps to use Spring AOP are as follows:

  1. Add Spring AOP framework support;
  2. Define facets and points;
  3. Define related notification methods.

3.1 Add Spring AOP framework support

pom.xmlAdd the following configuration in :

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
    <version>2.7.14</version>
</dependency>

3.2 Defining facets and points

The aspect represents the specific content of a certain aspect, such as the judgment of user login authority, and the aspect here is the UserControllerprocessing of the class. One of them is to define specific interception rules. For example the following code:

@Aspect // 表示定义一个切面(类)
@Component
public class UserAspect {
    
    
    // 创建切点(方法)定义拦截规则
    @Pointcut("execution(public * com.example.demo.controller.UserController.*(..))")
    public void pointcut() {
    
    
    }
}

In this code, an aspect class is created UserAspectand @Aspectannotated to mark it as an aspect. At the same time, use @Componentannotations to declare this aspect as a Spring-managed component so that the Spring container can manage and recognize it.

In UserAspectthe class, @Pointcutan annotation is used to define a pointcut, and the interception rule of the pointcut is to match UserControllerall public methods in the class (public * com.example.demo.controller.UserController.*(..)). This means our pointcut will UserControllerfire on all public methods of the class.

It should be noted that pointcutthe method body of the method can be empty, because @Pointcutthe annotation is only used to define the pointcut expression, and the actual logic code will be implemented in the advice method.

Pointcut expression description:
Pointcut expressions are used to define matching rules for pointcuts, which determine which join points to apply advice on. AspectJ supports three kinds of wildcards to build pointcut expressions:

  • *: Match any character, only one element (package, class, or method, method parameter).
  • ..: Match any character and can match multiple elements. When representing a class, it must *be used in conjunction with , for example: com.cad..*to represent com.cadall classes in all descendant packages under the package.
  • +: Indicates to match all classes of the specified class by type. It must be followed by the class name, such as com.cad.Car+means that all subclasses inheriting this class include itself.

Pointcut expressions typically use execution()pointcut functions, which are the most commonly used pointcut functions for matching methods.

Pointcut Expression Examples:
Here are some examples of pointcut expressions to better understand how to build pointcut rules:

  • execution(* com.cad.demo.User.*(..)): Matches Userall methods in the class.
  • execution(* com.cad.demo.User+.*(..)): Matches Userall methods in subclasses of the class, including itself.
  • execution(* com.cad.*.*(..)): Match com.cadall methods of all classes under the package.
  • execution(* com.cad..*.*(..)): Matches com.cadall methods of all classes under the package and all its descendant packages.
  • execution(* addUser(String, int)): Match addUsermethod, and the first parameter type is Stringand the second parameter type is int.

3.3 Define related notification methods

The notification defines the specific business to be performed by the intercepted method. For example, the user login authority verification method is the specific business to be performed, but this time it is only a simple printout operation. In Spring AOP, the following annotations can be used on the method, which will set the method as a notification method, and will notify the method to call after the condition is met:

  • Use of pre-notification@Before : the notification method will be executed before the target method is called, and some preprocessing operations can be performed.

  • Use of post-notification@After : the notification method will be called after the target method returns or throws an exception, and some aftermath operations can be performed.

  • Use of notification after return@AfterReturning : the notification method will be called after the target method returns successfully, and the return value can be processed.

  • Use of notification after throwing an exception@AfterThrowing : the notification method will be called after the target method throws an exception, which can handle the exception.

  • Surround notification use@Around : notification wraps the notified method, executes custom behavior before and after the target method is called, and can also control whether to call the target method and how to handle the return value.

The notification code implemented here is as follows:

// 创建一个切面(类)
@Aspect
@Component
public class UserAscept {
    
    
    // 创建切点(方法)定义拦截规则
    @Pointcut("execution(public * com.example.demo.controller.UserController.*(..))")
    public void pointcut() {
    
    
    }

    // 前置通知
    @Before("pointcut()")
    public void doBefore() {
    
    
        System.out.println("执行了前置通知:" + LocalDateTime.now());
    }

    // 后置通知
    @After("pointcut()")
    public void doAfter() {
    
    
        System.out.println("执行了后置通知:" + LocalDateTime.now());
    }

    // 返回后通知
    @AfterReturning("pointcut()")
    public void doAfterReturning() {
    
    
        System.out.println("执行了返回后通知:" + LocalDateTime.now());
    }

    // 抛异常后通知
    @AfterThrowing("pointcut()")
    public void doAfterThrowing() {
    
    
        System.out.println("抛异常后通知:" + LocalDateTime.now());
    }

    // 环绕通知
    @Around("pointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint) {
    
    

        Object proceed = null;
        System.out.println("Around 方法开始执行:" + LocalDateTime.now());
        try {
    
    
            // 执行拦截的方法
            proceed = joinPoint.proceed();
        } catch (Throwable e) {
    
    
            e.printStackTrace();
        }
        System.out.println("Around 方法结束执行: " + LocalDateTime.now());
        return proceed;
    }
}

In this code, different types of notification methods are defined, and each method is marked with corresponding annotations, such as @Before, @After, @AfterReturning, @AfterThrowingand @Around. These annotations associate advice methods with pointcuts ( pointcut()pointcuts defined using the method). When UserControllerthe method in is called, it will be intercepted by the pointcut, and then these corresponding notification methods will be executed.

A note about the wraparound method above:

Among them, surrounding advice is the most special and flexible advice type in Spring AOP. It allows developers full control over intercepted method calls, including the ability to insert custom logic before and after target method execution and when exceptions are caught. This feature makes surround advice very powerful for scenarios where full control over crosscutting logic is required.

The above code shows how to implement surround notifications. Surrounding notifications are marked with @Aroundannotations, and the parameter of the notification method is an ProceedingJoinPointobject, which allows us to call the method at an appropriate time proceed()to execute the intercepted method.

In this surround notification method:

  1. The parameter of the method joinPointis an ProceedingJoinPointobject, which represents the call point of the target method, and proceed()the method can be called to execute the intercepted method when needed.

  2. You can proceed()insert custom logic before and after the method call to achieve specific cross-cutting behavior.

  3. The exception that may be thrown can be caught and the exception situation can be handled.

  4. The return value allows you to control the return value of the target method or perform appropriate return value handling.

Fourth, the implementation principle of Spring AOP

4.1 Dynamic proxy

Spring AOP is based on dynamic proxies, so its support is limited to method-level interception. This means that Spring AOP is mainly used to intercept and manage method calls, and cannot directly intercept class-level operations.

In Spring AOP, there are two ways to implement dynamic proxy: JDK Proxy and CGLIB. By default, Spring AOP will choose which proxy method to use based on whether the target class implements the interface:

  • JDK Proxy: If the target class implements the interface, Spring AOP will use JDK Proxy to generate the proxy class. java.lang.reflect.ProxyJDK Proxy is implemented based on classes in the Java standard library , requiring the target class to implement at least one interface.

  • CGLIB: If the target class does not implement the interface, Spring AOP will use CGLIB to generate the proxy class. CGLIB is a powerful third-party library that can dynamically create subclasses of classes at runtime to implement proxies.

This means that when using Spring AOP, if the target class implements the interface, Spring will use the JDK Proxy to create the proxy. If the target class does not implement the interface, Spring will use CGLIB to create the proxy. In some cases, you may need to pay attention to some subtle effects that CGLIB proxy may bring, such as the final method cannot be intercepted, etc.

4.2 JDK dynamic proxy

JDK Proxy is a dynamic proxy mechanism in the Java standard library. It creates proxy objects based on the interfaces implemented by the target class to insert crosscutting logic before and after method calls. JDK Proxy is mainly used to intercept method calls of classes that implement interfaces.

Implementation of JDK Proxy:

  1. First, InvocationHandlercreate a method call handler by implementing the interface. This handler defines the logic to be executed when intercepting method calls.
  2. Then, use Proxythe class to create the proxy object. ProxyThe method of the class newProxyInstance()accepts a ClassLoader, an interface array and an InvocationHandlerobject as parameters, and then dynamically generates the bytecode of the proxy class to create a proxy instance.

Limitations of JDK Proxy:

  • JDK Proxy requires that the target class must implement at least one interface. It cannot directly intercept method calls of classes that do not implement the interface.
  • The proxy object created by JDK Proxy implements the interface implemented by the target class, so the proxy object can only call the methods defined in the interface.

4.3 CGLIB dynamic proxy

CGLIB is a powerful third-party library for subclassing classes at runtime to implement proxies. Its main feature is that it can dynamically generate a subclass of a class at runtime without requiring the target class to implement an interface, so as to intercept and enhance the method call of the target class.

Features and benefits of CGLIB:

  • CGLIB can proxy classes that do not implement an interface. This makes it suitable for a wider range of scenarios, including those classes without interfaces.
  • CGLIB uses the inheritance mechanism to implement proxies, generate subclasses of the target class, and insert crosscutting logic in the subclasses. Therefore, it can intercept all methods of the target class, whether they are instance methods or static methods.
  • Due to the use of inheritance to generate proxy classes, CGLIB cannot proxy finalmethods declared as . This is a limitation to be aware of.
  • CGLIB's proxies are generally slightly slower than JDK Proxy because it involves creating and loading the bytecode of the proxy classes.

4.4 The difference between JDK Proxy and CGLIB dynamic proxy

When using Spring AOP to create a proxy, you can choose to use JDK Proxy or CGLIB to implement a dynamic proxy. There are some differences between these two proxy methods, the following is a summary of them:

JDK Proxy:

  • Based on the class implementation in the Java standard library java.lang.reflect.Proxy.
  • It is required that the target class must implement at least one interface, because it creates a proxy based on the interface.
  • By implementing the method of the proxy interface, the cross-cutting logic is inserted before and after the method call.
  • Since the interface is used as the basis of the proxy, the generated proxy object can only call the methods defined in the interface.
  • In general, the performance of JDK Proxy is relatively high.

CGLIB:

  • is a third-party library for generating subclasses of classes implementing proxies at runtime.
  • The target class does not need to implement the interface, and the class that does not implement the interface can be proxied.
  • Through the inheritance mechanism, the cross-cutting logic is inserted in the subclass of the target class.
  • Ability to intercept all methods of the target class, including instance methods and static methods.
  • finalCGLIB cannot proxy methods declared as due to the use of inheritance and bytecode generation of proxy classes .
  • Proxies are relatively slow because of the bytecode involved in generating and loading the proxy classes.

how to choose:

  • If the target class implements the interface, JDK Proxy is preferred. It provides higher performance and is suitable for interface-based proxies.
  • If the target class does not implement the interface, or you need to intercept classes that do not implement the interface, you can choose to use CGLIB. It is more flexible and applicable to a wider range of proxy scenarios.

Guess you like

Origin blog.csdn.net/qq_61635026/article/details/132207155