[Spring] Introduction to Spring AOP and Analysis of Implementation Principles


1 A first look at Aop

1.1 What is AOP?

AOP (Aspect-Oriented Programming) is a programming paradigm that provides a way to modularize cross-cutting concerns in a program. Cross-cutting concerns can be logs, transactions, security, etc. They do not belong to business logic, but they must be tightly coupled with business logic. In AOP, we call these cross-cutting concerns "aspects", which are independent of business logic modules, but can be woven into business logic at different stages of program execution. Using AOP can improve code reusability, reduce coupling between modules, simplify code maintainability, etc.

1.2 Composition of AOP

AOP consists of aspects, pointcuts, joinpoints, and advice.

1.2.1 Aspect

An aspect is a class that includes notifications, pointcuts, and aspects, which is equivalent to a collection of certain functions implemented by AOP. Popular understanding, in the program is a class that deals with a specific problem in a certain aspect. It contains many methods, which are pointcuts and notifications.

1.2.2 Join Points

A point where an aspect can be inserted during application execution. This point can be when a method is called, when an exception is thrown, or even when a field is modified. Aspect code can use these points to be inserted into the normal flow of the application and add new behaviors. Join points can be understood as all points that may trigger AOP rules. In a narrow sense, it can be understood as a method that requires function enhancement.

1.2.3 Pointcut

A pointcut is a collection of join points. It defines on which join points a particular advice applies. By using a pointcut expression, a specific join point can be selected based on its characteristics, such as method signature or class name. That is, pointcuts are rules (configurations) for active interception.
Specifically: the function of Pointcut is to provide a set of rules (described in AspectJ pointcut expression language) to match Join Points, and add Advice to Join Points that satisfy the rules.

1.2.4 Advice

In AOP terminology, the work of aspects is called advice. Advice is an action performed by an aspect on a join point. It defines when (such as before or after a method call) and how (such as logging or performance monitoring) the behavior of an aspect is applied. That is, the specific action triggered by the intercepted request in the program.

1.3 AOP usage scenarios

Looking back at the author's previous article, in the blog system based on the separation of front and back ends implemented by Servlet, except for a few functions such as login that do not require user login verification, almost all other front-end controllers (Controller) called by the page Both need to verify the user login status first. However, when the system has more and more functions, more and more login verifications need to be written. Once some functions need to be changed, this processing method will affect the whole body due to its high coupling. And these methods are the same. For this kind of functions that are unified and used in many places, AOP can be considered for unified processing.
For example, the original blog system needs to verify the login status before the author deletes, publishes, and browses the blog. If the user is not logged in, the request is redirected to the login interface. After using AOP, before the user calls the Server service, uniform verification is performed. If the verification passes, the service will be normal, otherwise, it will be "intercepted".
AOP login interception
In addition to unified login judgment, using AOP can also achieve:

  • Unified logging
  • Unified method execution time statistics
  • Unified return format settings
  • Unified exception handling
  • Transaction opening and committing, etc.

2 Getting Started with Spring AOP

Above, we have a basic understanding of AOP. Next, our goal is to try to use Spring AOP to realize the functions of AOP. The completed goals are as follows:

Intercept all the methods in StudentController, that is, execute the corresponding notification event every time any method in StudentController is called.

The implementation steps of Spring AOP are as follows:

  1. Add Spring AOP framework support.
  2. Define facets and pointscuts.
    (1) Create an aspect class
    (2) Configure interception rules
  3. Define notifications.

2.1 Add Spring AOP framework support

First, create a Spring Boot project
Spring Boot project
and add Spring AOP dependency configuration in pom.xml:

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

2.2 Defining facets and points

Use @Aspectannotations to indicate that the current class is an aspect, and in the pointcut, we need to define the rules of interception, the specific implementation is as follows:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect // 表明此类为一个切面
@Component // 随着框架的启动而启动
public class StudentAspect {
    
    
    // 定义切点, 这里使用 Aspect 表达式语法
    @Pointcut("execution(* com.hxh.demo.controller.StudentController.*(..))")
    public void pointcut(){
    
     }
}

In the above implementation code, pointcut is an empty method, which only serves as an "identification", that is, to identify which cut-off point the following notification method refers to, and there can be multiple cut-off points.

Pointcut expressions are composed of pointcut functions, among which execution()is the most commonly used pointcut function, used to match methods, the syntax is:

execution(<modifier><return type><package.class.method(parameter)><exception>)
modifiers and exceptions can be omitted

*Examples of common pointcut expressions:

  • Matches all methods of a specific class:
    execution(* com.example.MyClass.*(..)) : matches all methods in class com.example.MyClass.
  • Match all methods under a specific package:
    execution(* com.example.*.*(..)) : Match all methods under the com.example package and its subpackages.
  • Match methods annotated by specific annotations:
    execution(@com.example.MyAnnotation * *(..)) : Match all methods annotated by com.example.MyAnnotation annotation.
  • Matches methods with specific method names:
    execution(* com.example.MyClass.myMethod(..)) : Matches a method named myMethod in class com.example.MyClass.
  • Matches methods of specific method parameter types:
    execution(* com.example.MyClass.myMethod(String, int)) : Matches the myMethod method in class com.example.MyClass that has one String parameter and one int parameter.
  • Matches methods of a specific return type:
    execution(String com.example.MyClass.myMethod(..)) : Matches the method myMethod in class com.example.MyClass whose return type is String.

StudentController.java

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/student")
public class StudentController {
    
    

    @RequestMapping("/hi")
    public String sayHi(String name) {
    
    
        System.out.println("执行了 sayHi 方法~");
        return "Hi," + name;
    }

    @RequestMapping("/hello")
    public String sayHello() {
    
    
        System.out.println("执行了 sayHello 方法~");
        return "Hello, hxh";
    }
}

2.3 Define related notifications

The notification defines the specific business to be executed by the intercepted method.
In the Spring aspect class, 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 conditions are met:

  • Use pre-advice @Before: the notification method will be executed before the target method is called.
  • Use post-notification @After: the notification method will be called after the target method returns or throws an exception.
  • Use notification after return @AfterReturning: The notification method will be called after the target method returns.
  • Notification after exception is thrown @AfterThrowing: the notification method will be called after the target method throws an exception.
  • Surrounding notification usage @Around: the notification wraps the notified method, and executes custom behaviors before and after the notified method is called.

The specific implementation is as follows:

Pre-notification and post-notification (abnormal notification and post-return notification are just different annotations, the method is the same, so I won’t go into details here~)

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect // 表明此类为一个切面
@Component // 随着框架的启动而启动
public class StudentAspect {
    
    
    // 定义切点, 这里使用 Aspect 表达式语法
    @Pointcut("execution(* com.hxh.demo.controller.StudentController.*(..))")
    public void pointcut(){
    
     }

    // 前置通知
    @Before("pointcut()")
    public void beforeAdvice() {
    
    
        System.out.println("执行了前置通知~");
    }

    // 后置通知
    @After("pointcut()")
    public void afterAdvice() {
    
    
        System.out.println("执行了后置通知~");
    }
}

achieve results

The specific implementation of the surround notification
The surround notification has an Object return value, and the result of the execution process needs to be returned to the framework, and the framework gets the object and continues to execute.

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect // 表明此类为一个切面
@Component // 随着框架的启动而启动
public class StudentAspect {
    
    
    // 定义切点, 这里使用 Aspect 表达式语法
    @Pointcut("execution(* com.hxh.demo.controller.StudentController.*(..))")
    public void pointcut(){
    
     }

    // 环绕通知
    @Around("pointcut()")
    public Object aroundAdvice(ProceedingJoinPoint joinPoint) {
    
    
        System.out.println("进入环绕通知~");
        Object obj = null;
        // 执行目标方法
        try {
    
    
            obj = joinPoint.proceed();
        } catch (Throwable e) {
    
    
            e.printStackTrace();
        }
        System.out.println("退出环绕通知~");
        return obj;
    }
    
}

achieve results


3 Spring AOP implementation principle

Spring AOP weaves AOP code into the program at runtime through dynamic proxy. There are two ways to implement it: JDK Proxyand CGLIB. Therefore, Spring's support for AOP is limited to method-level interception.

  • CGLIB is a dynamic proxy framework in Java. Its main function is to dynamically generate proxy classes according to target classes and methods.
  • Almost all dynamic proxy frameworks in Java are implemented relying on bytecode frameworks (such as ASM, Javassist, etc.).
  • A bytecode framework is a framework for directly manipulating class bytecodes. You can load existing class bytecode file information, modify some information, or dynamically generate a class.

3.1 What is a dynamic proxy?

Dynamic Proxy (Dynamic Proxy) is a design pattern that allows proxy objects to be created at runtime and method calls to be forwarded to the actual object. Dynamic proxies can be used to implement the functionality of cross-cutting concerns (such as logging, performance monitoring, transaction management, etc.) without modifying the code of the original object.
In Java, dynamic proxies are usually implemented using java.lang.reflect.Proxyclasses and interfaces.java.lang.reflect.InvocationHandler

Here are the general steps to use a dynamic proxy:

  1. Create a class that implements the InvocationHandler interface that will act as the invocation handler for the proxy object. In the invoke method of the InvocationHandler interface, you can define the logic to be executed before and after the method invocation.

  2. Use the newProxyInstance method of the Proxy class to create a proxy object. The method accepts three parameters: class loader, array of proxy interfaces, and call handler. It will return a proxy object implementing the specified interface.

  3. Use the proxy object to call the method. When a method of the proxy object is called, the invoke method of the call handler is actually called and the method call is forwarded to the actual object.

dynamic proxy

3.2 JDK dynamic proxy implementation

First create a method invocation handler by implementing the InvocationHandler interface, and then create a proxy class through Proxy.

// 动态代理:使⽤JDK提供的api(InvocationHandler、Proxy实现),此种⽅式实现,要求被代理类必须实现接⼝
public class PayServiceJDKInvocationHandler implements InvocationHandler {
    
    

    // ⽬标对象即就是被代理对象
    private Object target;

    public PayServiceJDKInvocationHandler(Object target) {
    
    
        this.target = target;
    }

    // proxy代理对象
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
        // 1.安全检查
        System.out.println("安全检查");
        // 2.记录⽇志
        System.out.println("记录⽇志");
        // 3.时间统计开始
        System.out.println("记录开始时间");
        // 通过反射调⽤被代理类的⽅法
        Object retVal = method.invoke(target, args);
        //4.时间统计结束
        System.out.println("记录结束时间");
        return retVal;
    }

    public static void main(String[] args) {
    
    
        PayService target= new AliPayService();
        // ⽅法调⽤处理器
        InvocationHandler handler =
                new PayServiceJDKInvocationHandler(target);
        // 创建⼀个代理类:通过被代理类、被代理实现的接⼝、⽅法调⽤处理器来创建
        PayService proxy = (PayService) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                new Class[]{
    
    PayService.class},
                handler
        );
        proxy.pay();
    }
}

3.3 CGLIB dynamic proxy implementation

public class PayServiceCGLIBInterceptor implements MethodInterceptor {
    
    
    // 被代理对象
    private Object target;

    public PayServiceCGLIBInterceptor(Object target) {
    
    
        this.target = target;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    
    
        // 1.安全检查
        System.out.println("安全检查");
        // 2.记录⽇志
        System.out.println("记录⽇志");
        // 3.时间统计开始
        System.out.println("记录开始时间");
        // 通过cglib的代理⽅法调⽤
        Object retVal = methodProxy.invoke(target, args);
        // 4.时间统计结束
        System.out.println("记录结束时间");
        return retVal;
    }

    public static void main(String[] args) {
    
    
        PayService target= new AliPayService();
        PayService proxy= (PayService) Enhancer.create(target.getClass(),new PayServiceCGLIBInterceptor(target));
        proxy.pay();
    }
}

3.4 The difference between the two methods

  • JDK implementation requires that the proxied class must implement the interface, and then through InvocationHandler and Proxy, the proxy class object is dynamically generated in memory at runtime, and the proxy object is realized by implementing the same interface (similar to static proxy Interface implementation), but the proxy class is dynamically woven into the unified business logic bytecode at runtime to complete.
  • CGLIB implementation, the proxy class does not need to implement the interface, and the proxy class object is dynamically generated at runtime by inheriting the proxy class.

write at the end

This article is included in the JavaEE programming road点击订阅专栏 and is being updated continuously.
 The above is the whole content of this article! Creation is not easy, if you have any questions, welcome to private message, thank you for your support!

insert image description here

Guess you like

Origin blog.csdn.net/m0_60353039/article/details/131745079