Spring AOP and transaction description

Table of contents

1. Transaction management

1.1 Transaction description

1.2 Spring transaction management

1.3 Advanced transaction

(1) Transactional attribute description

(2) rollbackFor attribute

(3) propagation attribute

1.4 Summary

2.AOP

2.1 Overview of AOP

2.2 AOP Core Concepts

2.3 Advanced AOP

(1) Notification type

(2) Point-cut expression

(3) Notification order

(4) connection point


1. Transaction management

1.1 Transaction description

A transaction is a collection of operations that is an indivisible unit of work. A transaction will take all operations as a whole and submit or revoke the operation request to the database together. So the set of operations either succeeds or fails at the same time.

There are three main steps in the transaction operation:

* Start a transaction (start a transaction before a set of operations starts): start transaction / begin;
* Commit a transaction (commit a transaction after all the operations are successful): commit;
* Roll back a transaction (if any operation in the middle is abnormal, roll back transaction): rollback;

1.2 Spring transaction management

In the spring framework, we only need to get it done with a simple annotation @Transactional.

Transactional annotations:

@Transactional Function: It is to start the transaction before the execution of the current method starts, and submit the transaction after the method is executed. If an exception occurs during the execution of this method, the transaction will be rolled back.

@Transactional annotation: We generally control transactions in the business layer, because in the business layer, a business function may include multiple data access operations. By controlling transactions at the business layer, we can control multiple data access operations within the scope of a transaction.

@Transactional annotation writing position:

Method: The current method is handed over to spring for transaction management
Class: All methods in the current class are handed over to spring for transaction management
Interface: All methods in all implementation classes under the interface are handed over to spring for transaction management

Generally, we are in the method of adding, deleting, and modifying in the business layer, and there are multiple table additions, deletions, and changes in one method. We need to add this annotation to incorporate the operations of multiple tables into one transaction for management.

You can enable the transaction management log in the application.yml configuration file, so that you can see the log information related to the transaction in the control.

#spring事务管理日志
logging:
  level:
    org.springframework.jdbc.support.JdbcTransactionManager: debug

1.3 Advanced transaction

(1) Transactional attribute description

The spring transaction management annotation @Transactional has controlled the transaction of the business layer method. Next, we will introduce the details of the use of @Transactional transaction management annotations in detail. Here we mainly introduce two common attributes in the @Transactional annotation:

1. Attributes of abnormal rollback: rollbackFor
2. Transaction propagation behavior: propagation

(2) rollbackFor attribute

By default, only a RuntimeException (runtime exception) will roll back the transaction.

If we want to roll back all exceptions, we need to configure the rollbackFor attribute in the @Transactional annotation. The rollbackFor attribute can specify what type of exception occurs to roll back the transaction.

Generally, we specify the value of the rollbackFor attribute as Exception.class, that is, roll back when any exception occurs.

@Transactional(rollbackFor=Exception.class)

(3) propagation attribute

This property is used to configure the propagation behavior of the transaction.

What is the propagation behavior of the transaction?

* is when a transaction method is called by another transaction method, how this transaction method should perform transaction control.

For example: two transaction methods, one A method and one B method. The @Transactional annotation is added to these two methods, which means that both methods have transactions, and the B method is called in the A method.

The so-called transaction propagation behavior means that when method A is running, a transaction will be started first, and method B will be called in method A. Method B itself also has a transaction, so when method B is running, whether to join Come into the transaction of method A, or create a new transaction when method B is running? This involves the propagation behavior of the transaction.

If we want to control the propagation behavior of the transaction, specify an attribute propagation after the @Transactional annotation, and specify the propagation behavior through the propagation attribute. Next, let's introduce common transaction propagation behaviors.

For these transaction propagation behaviors, we only need to pay attention to the following two:

1. REQUIRED (default)
2. REQUIRES_NEW

 By default, we don't need to set this property, just use the default value, and join the transaction if there is a transaction.

But there is a special case, as follows:

**Requirement: **Operation logs need to be recorded when disbanding a department

​ Since the disbandment of a department is a very important and very dangerous operation, it is required in the business to leave traces every time the operation of disbanding a department is performed, that is, to record the operation log. And it is also required that no matter whether the execution is successful or the execution fails, traces need to be left.

First of all, if we do not set the propagation behavior of the transaction, the execution is as follows:

  • A transaction was opened when the delete operation was performed
  • When executing the insert operation, the transaction propagation line set by insert is the default value REQUIRED, which means that if there is a transaction, it will be added, and if there is no transaction, a new transaction will be created
  • At this time: the delete and insert operations use the same transaction, and multiple operations in the same transaction either succeed or fail at the same time, so when the transaction is rolled back when an exception occurs, the delete and insert operations will be rolled back.

This result does not meet our design requirements. The operation of recording the operation log should not be affected by other operations, so we need to set the propagation behavior of the transaction, set it to REQUIRES_NEW, and restart a transaction.

When the delete method runs, a transaction is started. When insert(deptLog) is called, a new transaction is also created. At this time, when the insert method finishes running, the transaction has been committed. Even if the external transaction is abnormal, the internal committed transaction will not be rolled back, because it is two independent transactions.

1.4 Summary

We only need to master two transaction propagation behaviors: REQUIRED and REQUIRES_NEW.

REQUIRED : In most cases, this propagation behavior is sufficient.
REQUIRES_NEW : This propagation behavior can be used when we don't want transactions to affect each other. For example: before placing an order, a log needs to be recorded. Regardless of whether the order is saved successfully or not, it is necessary to ensure that the log record can be recorded successfully.


2.AOP

2.1 Overview of AOP

What is AOP?

* AOP English full name: Aspect Oriented Programming (aspect-oriented programming, aspect-oriented programming), in fact, to put it bluntly, aspect-oriented programming is oriented to specific method programming.

However, some business functions have relatively low execution efficiency and take a long time to execute. We need to optimize these business methods. The first step is to locate the business method that takes a long time to execute, and then optimize the business method.

At this point, we need to count the execution time of each business method in the current project. So how to count the execution time of each business method?

Perhaps the first thing most people think of is to record the start time of this method before each business method runs. After the method finishes running, record the end time of the method running. Taking the end time minus the start time, isn't it the time-consuming execution of this method?

The implementation of the above analysis can solve the demand problem. But for a project, it will contain a lot of business modules, and each business module contains many methods of adding, deleting, modifying and checking. If we want to add record start time, end time, calculation Executing time-consuming code will make the programmer's work very tedious.

However, AOP method-oriented programming can enhance the functions of specific methods without changing these original methods.

The role of AOP: to enhance the existing method without modifying the source code during the running of the program (non-invasive: decoupling)

If we want to fulfill the requirement of counting the time-consuming execution of each business method, we only need to define a template method, and define the public logic code that records the time-consuming execution of the method in the template method, and record it before the method starts running. The start time of this method operation, when the method ends, record the end time of the method operation, and run the original business method in the middle.

At this time, when we call the list business method of department management, the logic of the list method will not be executed directly, but the template method we defined will be executed, and then in the template method:

  • Record method run start time
  • Run the original business method (the original business method at this time is the list method)
  • Record method run end time and calculate method execution time

AOP aspect-oriented programming is the same as OOP object-oriented programming, they are just a programming idea, and dynamic proxy technology is the most mainstream implementation of this idea. Spring's AOP is an advanced technology of the Spring framework, which aims to use the dynamic proxy mechanism at the bottom layer in the process of managing bean objects to program specific methods (function enhancement).

Advantages of AOP:

  • 1. Reduce duplicate code
  • 2. Improve development efficiency
  • 3. Easy maintenance
  • 4. No code intrusion: the original business method has been enhanced or changed without modifying the original business method

Common application scenarios:

  • Record the operation log of the system
  • access control
  • Transaction management: The bottom layer of the Spring transaction management we explained above is actually implemented through AOP. As long as the @Transactional annotation is added, the AOP program will automatically start the transaction before the original method runs, and submit or rollback transaction

2.2 AOP Core Concepts

1. Connection point: JoinPoint, a method that can be controlled by AOP (implying relevant information when the method is executed)

A join point refers to a method that can be controlled by aop. For example: all business methods in the entry program are methods that can be controlled by aop.

2. Notification: Advice, refers to those repeated logics, that is, common functions (finally embodied as a method)

​ In the introductory program, it is necessary to count the time-consuming execution of each business method. At this time, we need to record the start time of this method before the operation of these business methods starts. When each business method finishes running, come again. Record the end time of this method run.

​ But in AOP aspect-oriented programming, we only need to extract this part of the repeated code logic and define it separately. The extracted part of the repeated logic is the common function.

3. Pointcut: PointCut, matching the condition of the join point, the notification will only be applied when the pointcut method is executed

​ In the notification, which methods should the common functions we define be applied to? At this point, the concept of pointcut is involved. A pointcut refers to a condition that matches a join point. Advice will only be applied when the pointcut method is run.

​ In the development of aop, we usually use a pointcut expression to describe the pointcut (details will be explained later).

4. Aspect: Aspect, describing the corresponding relationship between notification and entry point (notification + entry point)

​ When advice and pointcuts are combined, they form an aspect. Through the aspect, it is possible to describe which original method the current aop program needs to target, and when to perform what kind of operation.

5. Target object: Target, the object to which the notification is applied

​ The target object refers to the object to which the notification applies, and we call it the target object.

2.3 Advanced AOP

(1) Notification type

Advice types of AOP in Spring:

@Around: Around the notification, the notification method annotated by this annotation is executed before and after the target method.
@Before: Pre-notification, the notification method annotated by this annotation is executed before the target method.
@After : post-notification, the notification method annotated by this annotation is executed after the target method, regardless of whether there is an exception or not.
@AfterReturning : Notification after return, the notification method annotated by this annotation is executed after the target method, and will not be executed if there is an exception.
@AfterThrowing : Notification after exception, the notification method annotated by this annotation is executed after an exception occurs.

The execution result of the program when no exception occurs: 

Execution results when an exception occurs in the program:

Things to keep in mind when using notifications:

@Around advice needs to call ProceedingJoinPoint.proceed() to let the original method execute, and other advice does not need to consider the target method execution.
The return value of the @Around notification method must be specified as Object to receive the return value of the original method, otherwise the return value cannot be obtained after the original method is executed.

(2) Point-cut expression

1. Extraction

//前置通知
@Before("execution(* com.itheima.service.*.*(..))")

//环绕通知
@Around("execution(* com.itheima.service.*.*(..))")

//后置通知
@After("execution(* com.itheima.service.*.*(..))")

//返回后通知(程序在正常执行的情况下,会执行的后置通知)
@AfterReturning("execution(* com.itheima.service.*.*(..))")

//异常通知(程序在出现异常的情况下,执行的后置通知)
@AfterThrowing("execution(* com.itheima.service.*.*(..))")

 Pointcut expressions are specified in each annotation, and these pointcut expressions are exactly the same. At this time, there are a lot of repetitive pointcut expressions in our code. If the pointcut expressions need to be changed at this time, we need to change all the pointcut expressions one by one, which becomes very cumbersome. .

Spring provides the @PointCut annotation. The function of this annotation is to extract the common pointcut expression, and only need to refer to the pointcut expression when it needs to be used.

@Slf4j
@Component
@Aspect
public class MyAspect1 {

    //切入点方法(公共的切入点表达式)
    @Pointcut("execution(* com.itheima.service.*.*(..))")
    private void pt(){

    }

    //前置通知(引用切入点)
    @Before("pt()")
    public void before(JoinPoint joinPoint){
        log.info("before ...");

    }

    //环绕通知
    @Around("pt()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        log.info("around before ...");

        //调用目标对象的原始方法执行
        Object result = proceedingJoinPoint.proceed();
        //原始方法在执行时:发生异常
        //后续代码不在执行

        log.info("around after ...");
        return result;
    }

    //后置通知
    @After("pt()")
    public void after(JoinPoint joinPoint){
        log.info("after ...");
    }

    //返回后通知(程序在正常执行的情况下,会执行的后置通知)
    @AfterReturning("pt()")
    public void afterReturning(JoinPoint joinPoint){
        log.info("afterReturning ...");
    }

    //异常通知(程序在出现异常的情况下,执行的后置通知)
    @AfterThrowing("pt()")
    public void afterThrowing(JoinPoint joinPoint){
        log.info("afterThrowing ...");
    }
}

It should be noted that when the pointcut method is modified with private, the expression can only be referenced in the current aspect class. When the pointcut expression in the current class is also referenced in other external aspect classes, it is necessary to change private to public, and when quoting, the specific syntax is:

Full class name. method name (), the specific form is as follows:

@Slf4j
@Component
@Aspect
public class MyAspect2 {
    //引用MyAspect1切面类中的切入点表达式
    @Before("com.itheima.aspect.MyAspect1.pt()")
    public void before(){
        log.info("MyAspect2 -> before ...");
    }
}

2. Writing pointcut expressions

The pointcut expression is an expression describing the pointcut method, which is mainly used to determine which methods in the project need to be added to the notification.

Common forms:

1.execution(...): Match according to the signature of the method

execution is mainly matched according to the return value of the method, package name, class name, method name, method parameters and other information. The syntax is:

execution(访问修饰符?  返回值  包名.类名.?方法名(方法参数) throws 异常?)

Among them, the part with `?` indicates the part that can be omitted

Access modifier: can be omitted (for example: public, protected)
package name. Class name: can be omitted
throws exception: can be omitted (note that the exception thrown by the method declaration is not the actual exception thrown)

Example:

@Before("execution(void com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))")

Pointcuts can be described using wildcards

"*": A single independent arbitrary symbol, which can wildcard any return value, package name, class name, method name, a parameter of any type, or a part of a package, class, or method name.
"..": multiple consecutive arbitrary symbols, which can wildly match packages of any level, or parameters of any type and number.

Syntax rules for pointcut expressions:

1. The access modifier of the method can be omitted
2. The return value can be replaced by `*` (any type of return value)
3. The package name can be replaced by `*`, representing any package (a layer of package uses a `*` )
4. Use `..` to configure the package name to identify this package and all subpackages under this package.
5. The class name can be replaced by `*` to identify any class.
6. The method name can be replaced by `*`, Indicates any method
7. Can use `*` configuration parameter, a parameter of any type
8. Can use `..` configuration parameter, any number of parameters of any type

Example pointcut expressions:

Omit method modifiers

execution(void com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))

Use `*` instead of return type

execution(* com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))

Use `*` instead of the package name (a layer of packages use a `*`)

execution(* com.itheima.*.*.DeptServiceImpl.delete(java.lang.Integer))

Use `..` to omit the package name

execution(* com..DeptServiceImpl.delete(java.lang.Integer))    

 Use `*` instead of class names

execution(* com..*.*(java.lang.Integer))   

Use `*` instead of method names

execution(* com..*.*(java.lang.Integer))   

Use `*` instead of parameters

execution(* com.itheima.service.impl.DeptServiceImpl.delete(*))

Use `..` to omit arguments

execution(* com..*.*(..))

Precautions:

According to business needs, you can use and (&&), or (||), not (!) to combine more complex pointcut expressions.

execution(* com.itheima.service.DeptService.list(..)) || execution(* com.itheima.service.DeptService.delete(..))

Suggestions for writing pointcut expressions:

All business method names should be named as standard as possible to facilitate quick matching of pointcut expressions. For example: query methods start with find, and update methods start with update.

The description pointcut method is usually based on the interface description instead of directly describing the implementation class to enhance scalability.

Under the premise of meeting business needs, try to narrow the matching range of entry points. For example: try not to use .. for package name matching, use * to match a single package.

2. @annotation(……) : match according to the annotation 

If we want to match multiple irregular methods, such as: list() and delete(). At this time, it is not very convenient for us to describe based on the entry point expression of execution. In the past, we combined the two entry point expressions to complete the requirements, which is relatively cumbersome.

We can use another pointcut expression annotation to describe this type of pointcut, so as to simplify the writing of pointcut expressions.

Implementation steps:

1. Write a custom annotation
  
2. Add a custom annotation to the method to be used as a connection point in the business class

Example:

Custom annotations:

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

Implementation class: 

@Slf4j
@Service
public class DeptServiceImpl implements DeptService {
    @Autowired
    private DeptMapper deptMapper;

    @Override
    @MyLog //自定义注解(表示:当前方法属于目标方法)
    public List<Dept> list() {
        List<Dept> deptList = deptMapper.list();
        //模拟异常
        //int num = 10/0;
        return deptList;
    }

    @Override
    @MyLog  //自定义注解(表示:当前方法属于目标方法)
    public void delete(Integer id) {
        //1. 删除部门
        deptMapper.delete(id);
    }


    @Override
    public void save(Dept dept) {
        dept.setCreateTime(LocalDateTime.now());
        dept.setUpdateTime(LocalDateTime.now());
        deptMapper.save(dept);
    }

    @Override
    public Dept getById(Integer id) {
        return deptMapper.getById(id);
    }

    @Override
    public void update(Dept dept) {
        dept.setUpdateTime(LocalDateTime.now());
        deptMapper.update(dept);
    }
}

Section class: 

@Slf4j
@Component
@Aspect
public class MyAspect6 {
    //针对list方法、delete方法进行前置通知和后置通知

    //前置通知
    @Before("@annotation(com.itheima.anno.MyLog)")
    public void before(){
        log.info("MyAspect6 -> before ...");
    }

    //后置通知
    @After("@annotation(com.itheima.anno.MyLog)")
    public void after(){
        log.info("MyAspect6 -> after ...");
    }
}

 3. Summary

The execution entry point expression
  matches the entry point method according to the description information of the method we specify, which is also the most commonly used method.
  If the method name of the pointcut method we want to match is irregular, or there are some special requirements, it is cumbersome to describe it through the execution pointcut expression.
annotation pointcut expressions
  match pointcut methods based on annotations. Although this method takes one more step, we need to customize an annotation, but it is relatively flexible. Which method we need to match, just add the corresponding annotation to the method.

(3) Notification order

When in project development, we define multiple aspect classes, and multiple entry points in multiple aspect classes match the same target method. At this time, when the target method is running, the notification methods in the multiple aspect classes will run.

At this point we have a question, which of the multiple notification methods runs first and which runs later? 

By running the program, it can be seen that in different aspect classes, the class names of the aspect classes are sorted alphabetically by default:

* The notification method before the target method: the one with the highest letter rank is executed first
* The notification method after the target method: the one with the top letter rank is executed first

If we want to control the execution order of notifications, there are two ways:

1. Modify the class name of the aspect class (this method is very cumbersome and inconvenient to manage)
2. Use the @Order annotation provided by Spring

Use the @Order annotation to control the execution order of notifications: pre-notification: the smaller the number, execute first; post-notification: the smaller the number, the later the execution.

@Slf4j
@Component
@Aspect
@Order(2)  //切面类的执行顺序(前置通知:数字越小先执行; 后置通知:数字越小越后执行)
public class MyAspect2 {
    //前置通知
    @Before("execution(* com.itheima.service.*.*(..))")
    public void before(){
        log.info("MyAspect2 -> before ...");
    }

    //后置通知 
    @After("execution(* com.itheima.service.*.*(..))")
    public void after(){
        log.info("MyAspect2 -> after ...");
    }
}

Summarize:

The order of execution of the notification is mainly known to two points:

1. Among different aspect classes, the execution order of notifications by default is related to the alphabetical order of the class names of the aspect classes.
2. You can add the @Order annotation to the aspect class to control the execution order of different aspect class notifications.

(4) connection point

When we explained the core concepts of AOP, we mentioned what is a connection point. A connection point can be simply understood as a method that can be controlled by AOP.

All methods in our target object are methods that can be controlled by AOP. In Spring AOP, the connection point refers specifically to the execution of the method.

In Spring, JoinPoint is used to abstract the connection point, and it can be used to obtain relevant information when the method is executed, such as the target class name, method name, method parameters, etc.

For @Around advice, only the ProceedingJoinPoint type can be used to obtain join point information.
For the other four notifications, only JoinPoint can be used to obtain join point information, which is the parent type of ProceedingJoinPoint.

@Slf4j
@Component
@Aspect
public class MyAspect7 {

    @Pointcut("@annotation(com.itheima.anno.MyLog)")
    private void pt(){}

    //前置通知
    @Before("pt()")
    public void before(JoinPoint joinPoint){
        log.info(joinPoint.getSignature().getName() + " MyAspect7 -> before ...");
    }

    //后置通知
    @Before("pt()")
    public void after(JoinPoint joinPoint){
        log.info(joinPoint.getSignature().getName() + " MyAspect7 -> after ...");
    }

    //环绕通知
    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        //获取目标类名
        String name = pjp.getTarget().getClass().getName();
        log.info("目标类名:{}",name);

        //目标方法名
        String methodName = pjp.getSignature().getName();
        log.info("目标方法名:{}",methodName);

        //获取方法执行时需要的参数
        Object[] args = pjp.getArgs();
        log.info("目标方法参数:{}", Arrays.toString(args));

        //执行原始方法
        Object returnValue = pjp.proceed();

        return returnValue;
    }
}

(5) Implementation steps of AOP

import dependencies

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

Create an AOP notification class and hand it over to the spring container for management. In the program, define the cut point and compose the cut surface through the weaving operation.

Based on the pointcut defined by annotation, annotations need to be defined and added to the method to be enhanced.

package com.itheima.aspect;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * @Author linaibo
 * @Date 2023/4/5 8:18
 * @PackageName:com.itheima.aspect
 * @ClassName: TestAspect
 * @Version 1.0
 */
@Component
@Aspect
@Slf4j
public class TestAspect {

    @Pointcut("execution(* com.itheima.controller.OrderController.message(..))")
    // @Pointcut("@annotation(com.itheima.ano.Myaop)")
    private void pc() {
    }

    @Before("pc()")
    public void before(JoinPoint joinPoint) {
        System.out.println(joinPoint.getSignature().getName());
        log.info("方法执行前");
    }

    @AfterReturning("pc()")
    public void afterReturn(JoinPoint joinPoint) {
        log.info("方法之后后");
    }

    @Around("pc()")
    public Object round(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println(joinPoint.getSignature().getName());
        log.info("环绕方法执行前");
        Object result = joinPoint.proceed();
        log.info("环绕方法执行后");
        return result;
    }

    @After("pc()")
    public void after(JoinPoint joinPoint) {
        log.info("最后方法执行");
    }

    @AfterThrowing("pc()")
    public void afterThrowing(JoinPoint joinPoint) {
        log.info("抛出异常时执行");
    }
}

Spring will generate a proxy object for the request to call according to the above processing.

Guess you like

Origin blog.csdn.net/m0_72167535/article/details/129968039