# Aop应用


<!--more-->

# Spring AOP应用

## 一、aop概念

![img](https://img.zhaojq.top/20260727184929413.png "img")

![img](https://img.zhaojq.top/20260727184929626.png "img")

| 名词             | 解释                                                         |
| ---------------- | ------------------------------------------------------------ |
| Joinpoint 连接点 | 它指的是那些可以⽤于把增强代码加⼊到业务主线中的点，那么由上图中我们可以看出，这些点指的就是⽅法。在⽅法执⾏的前后通过动态代理技术加⼊增强的代码。在Spring框架AOP思想的技术实现中，也只⽀持⽅法类型的连接点。 |
| Pointcut 切入点  | 它指的是那些已经把增强代码加⼊到业务主线进来之后的连接点。由上图中，我们看出表现层 transfer⽅法就只是连接点，因为判断访问权限的功能并没有对其增强。 |
| Advice 通知/增加 | 它指的是切⾯类中⽤于提供增强功能的⽅法。并且不同的⽅法增强的时机是不⼀样的。⽐如，开启事务肯定要在业务⽅法执⾏之前执⾏；提交事务要在业务⽅法正常执⾏之后执⾏，⽽回滚事务要在业务⽅法执⾏产⽣异常之后执⾏等等。那么这些就是通知的类型。其分类有：前置通知 后置通知 异常通知 最终通知 环绕通知。 |
| Target 目标对象  | 它指的是代理的⽬标对象。即被代理对象。                       |
| Proxy 代理       | 它指的是⼀个类被AOP织⼊增强后，产⽣的代理类。即代理对象。    |
| Weaving 织入     | 它指的是把增强应⽤到⽬标对象来创建新的代理对象的过程。spring采⽤动态代理织⼊，⽽AspectJ采⽤编译期织⼊和类装载期织⼊。 |
| Aspect 切面      | 它指定是增强的代码所关注的⽅⾯，把这些相关的增强代码定义到⼀个类中，这个类就是切⾯类。例如，事务切⾯，它⾥⾯定义的⽅法就是和事务相关的，像开启事务，提交事务，回滚事务等等，不会定义其他与事务⽆关的⽅法。我们前⾯的案例中 TrasnactionManager就是⼀个切⾯。 |

> 连接点：⽅法开始时、结束时、正常运⾏完毕时、⽅法异常时等这些特殊的时机点，我们称之为连接点，项⽬中每个⽅法都有连接点，连接点是⼀种候选点
>
> 切⼊点：指定AOP思想想要影响的具体⽅法是哪些，描述感兴趣的⽅法
>
> Advice增强：
>
> - 第⼀个层次：指的是横切逻辑
>
> - 第⼆个层次：⽅位点（在某⼀些连接点上加⼊横切逻辑，那么这些连接点就叫做⽅位点，描述的是具体的特殊时机）
>
> Aspect切⾯：切⾯概念是对上述概念的⼀个综合
>
> Aspect切⾯= 切⼊点+增强= 切⼊点（锁定⽅法） + ⽅位点（锁定⽅法中的特殊时机）+ 横切逻辑
>
> **众多的概念，⽬的就是为了锁定要在哪个地⽅插⼊什么横切逻辑代码**

## 二、代理的选择

spring根据代理对象实现接口情况选择，当代理对象：

- 未实现接口：cglib代理

- 实现接口：jdk代理

  >Tips: 可通过配置强制使用cglib

## 三、aop配置方式

- xml配置
- xml+注解组合
- 纯注解 

## 四、Spring 中 aop实现

### 1.XML模式

- 开启aop支持

  ```xml
  <!--开启spring对注解aop的⽀持-->
  <aop:aspectj-autoproxy/>
  ```

- 添加约束头

  ```xml-dtd
  xmlns:aop="http://www.springframework.org/schema/aop"
  
  http://www.springframework.org/schema/aop
  https://www.springframework.org/schema/aop/spring-aop.xsd
  ```

- 配置步骤(由于xml现在已经不主流，此处仅做简单示例，不加demo代码了)

  ```xml
  <!--把通知bean交给spring来管理-->
  <bean id="logUtil" class="com.example.demo.utils.LogUtil"></bean>
  
  <!--开始aop的配置-->
  <aop:config>
    <!--配置切⾯-->
    <aop:aspect id="logAdvice" ref="logUtil">
      
    	<!--配置前置通知-->
    	<aop:before method="printLog" pointcut="execution(public *
               com.lagou.service.impl.TransferServiceImpl
                        .updateAccountByCardNo(com.lagou.pojo.Account))">
      </aop:before>
    </aop:aspect>
  </aop:config>
  ```

- 细节

  1） 关于切入点表达式

  ```text
  全限定⽅法名 访问修饰符 返回值 包名.包名.包名.类名.⽅法名(参数列表)
  
  1.全匹配方式
  public void com.demo.service.impl.TransferServiceImpl.updateAccountByCardNo(com.demo.pojo.Account)
  
  2.访问修饰符可以省略
  void com.demo.service.impl.TransferServiceImpl.updateAccountByCardNo(com.demo.pojo.Account)
  
  3.返回值可以使⽤*，表示任意返回值
  * com.demo.service.impl.TransferServiceImpl.updateAccountByCardNo(com.demo.pojo.Account)
  
  4.包名可以使⽤.表示任意包，但是有⼏级包，必须写⼏个
  * ....TransferServiceImpl.updateAccountByCardNo(com.demo.pojo.Account)
  
  5.包名可以使⽤..表示当前包及其⼦包
  * ..TransferServiceImpl.updateAccountByCardNo(com.demo.pojo.Account)
  
  6.类名和⽅法名，都可以使⽤.表示任意类，任意⽅法
  * ...updateAccountByCardNo(com.demo.pojo.Account)
  
  7.参数列表，可以使⽤具体类型
  	基本类型直接写类型名称 如： int
  	引⽤类型必须写全限定类名，如：java.lang.String
  	
  8.参数列表可以使⽤*，表示任意参数类型，但是必须有参数
  * *..*.*(*)
  
  9.参数列表可以使⽤..，表示有⽆参数均可。有参数可以是任意类型
  * *..*.*(..)
  
  10.全通配⽅式：
  * *..*.*(..)
  ```

  2）改变代理的方式 

  ```xml
  <!--使⽤aop:config标签配置-->
  <aop:config proxy-target-class="true">
  
  <!--使⽤aop:aspectj-autoproxy标签配置-->
  <!--此标签是基于XML和注解组合配置AOP时的必备标签，表示Spring开启注解配置AOP的⽀持-->
  <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectjautoproxy>
    
  ```

  3）五种通知类型

  ```text
  1.前置通知:aop:before标签,前置通知可以获取切⼊点⽅法的参数，并对其进⾏增强,属性：
  	  method：⽤于指定前置通知的⽅法名称
  		pointcut：⽤于指定切⼊点表达式
  		pointcut-ref：⽤于指定切⼊点表达式的引⽤
  		
  2.正常执⾏时通知:aop:after-returning标签
  3.异常通知：op:after-throwing标签，异常通知不仅可以获取切⼊点⽅法执⾏的参数，也可以获取切⼊点⽅法执⾏产	 ⽣的异常信息
  4.最终通知：aop:after
    最终通知的执⾏时机是在切⼊点⽅法（业务核⼼⽅法）执⾏完成之后，切⼊点⽅法返回之前执⾏。
    换句话说，⽆论切⼊点⽅法执⾏是否产⽣异常，它都会在返回之前执⾏
  5.环绕通知：aop:around
  	环绕通知，它是有别于前⾯四种通知类型外的特殊通知。前⾯四种通知（前置，后置，异常和最终）
  	它们都是指定何时增强的通知类型。⽽环绕通知，它是Spring框架为我们提供的⼀种可以通过编码的
  	⽅式，控制增强代码何时执⾏的通知类型。它⾥⾯借助的ProceedingJoinPoint接⼝及其实现类，
  	实现⼿动触发切⼊点⽅法的调⽤
  ```

### 2.组合模式

- 开启支持-很有必要

  ```xml
  <!--开启spring对注解aop的⽀持-->
  <aop:aspectj-autoproxy/>
  ```

- 示例代码

  ```java
  /**
   * @author zhaojianqiang
   */
  @Component
  @Aspect
  public class LogUtils {
  
  
      @Pointcut("execution(* com.example.demo.service.impl.TransferServiceImpl.*(..))")
      public void pt1() {
  
      }
  
  
      /**
       * 业务逻辑开始之前执行
       */
      @Before("pt1()")
      public void beforeMethod(JoinPoint joinPoint) {
          Object[] args = joinPoint.getArgs();
          for (int i = 0; i < args.length; i++) {
              Object arg = args[i];
              System.out.println(arg);
          }
          System.out.println("业务逻辑开始执行之前执行.......");
      }
  
  
      /**
       * 业务逻辑结束时执行（无论异常与否）
       */
      @After("pt1()")
      public void afterMethod() {
          System.out.println("业务逻辑结束时执行，无论异常与否都执行.......");
      }
  
  
      /**
       * 异常时时执行
       */
      @AfterThrowing("pt1()")
      public void exceptionMethod() {
          System.out.println("异常时执行.......");
      }
  
  
      /**
       * 业务逻辑正常时执行
       */
      @AfterReturning(value = "pt1()", returning = "retVal")
      public void successMethod(Object retVal) {
          System.out.println("业务逻辑正常时执行.......");
      }
  
  
      /**
       * 环绕通知
       */
      /*@Around("pt1()")*/
      public Object arroundMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
          Object result = null;
          try {
              System.out.println("环绕通知中的 beforeMethod....");
              Object[] args = proceedingJoinPoint.getArgs();
              result = proceedingJoinPoint.proceed(args);
              System.out.println("环绕通知中的 afterMethod....");
          } catch (Exception e) {
              System.out.println("环绕通知中的 exceptionMethod....");
          } finally {
              System.out.println("环绕通知中的 successMethod....");
          }
  
          return result;
      }
  
  }
  
  ```

  

### 3.注解模式

在使⽤注解驱动开发aop时，我们要明确的就是，是注解替换掉配置⽂件中的下⾯这⾏配置：

```xml
<!--开启spring对注解aop的⽀持-->
<aop:aspectj-autoproxy/>
```

```java
@Configuration
@ComponentScan("com.example.demo")
@EnableAspectJAutoProxy //开启spring对注解AOP的⽀持
public class SpringConfiguration {
}
```



## 五、声明式事务的支持

### 1. 事务回顾

#### 1.1 事务概念

事务指逻辑上的⼀组操作，组成这组操作的各个单元，要么全部成功，要么全部不成功。从⽽确保了数

据的准确与安全。

#### 1.2 事务四大特性

- 原⼦性（Atomicity）

  原⼦性是指事务是⼀个不可分割的⼯作单位，事务中的操作要么都发⽣，要么都不发⽣。从操作的⻆度来描述，事务中的各个操作要么都成功要么都失败

- ⼀致性（Consistency） 

  事务必须使数据库从⼀个⼀致性状态变换到另外⼀个⼀致性状态。例如转账前A有1000，B有1000。转账后A+B也得是2000。⼀致性是从数据的⻆度来说的，（1000，1000） （900，1100），不应该出现（900，1000）

- 隔离性（Isolation） 

  事务的隔离性是多个⽤户并发访问数据库时，数据库为每⼀个⽤户开启的事务，每个事务不能被其他事务的操作数据所⼲扰，多个并发事务之间要相互隔离。

  ⽐如：事务1给员⼯涨⼯资2000，但是事务1尚未被提交，员⼯发起事务2查询⼯资，发现⼯资涨了2000块钱,读到了事务1尚未提交的数据（脏读）

- 持久性（Durability）

  持久性是指⼀个事务⼀旦被提交，它对数据库中数据的改变就是永久性的，接下来即使数据库发⽣故障也不应该对其有任何影响。

#### 1.3 事务隔离级别

不考虑隔离级别，会出现以下情况：（以下情况全是错误的），也即为隔离级别在解决事务并发问题

- 脏读：⼀个线程中的事务读到了另外⼀个线程中未提交的数据。

- 不可重复读：⼀个线程中的事务读到了另外⼀个线程中已经提交的update的数据（前后内容不⼀样）

  > 场景：
  >
  > 员⼯A发起事务1，查询⼯资，⼯资为1w，此时事务1尚未关闭
  >
  > 财务⼈员发起了事务2，给员⼯A张了2000块钱，并且提交了事务
  >
  > 员⼯A通过事务1再次发起查询请求，发现⼯资为1.2w，原来读出来1w读不到了，叫做不可重复读

- 虚读（幻读）：⼀个线程中的事务读到了另外⼀个线程中已经提交的insert或者delete的数据（前后条数不⼀样）

  > 场景：
  >
  > 事务1查询所有⼯资为1w的员⼯的总数，查询出来了10个⼈，此时事务尚未关闭
  >
  > 事务2财务⼈员发起，新来员⼯，⼯资1w，向表中插⼊了2条数据，并且提交了事务
  >
  > 事务1再次查询⼯资为1w的员⼯个数，发现有12个⼈，⻅了⻤了

数据库共定义了四种隔离级别：

- Serializable（串⾏化）：可避免脏读、不可重复读、虚读情况的发⽣。（串⾏化） 最⾼

- Repeatable read（可重复读）：可避免脏读、不可重复读情况的发⽣。(幻读有可能发⽣) 第⼆

  该机制下会对要update的⾏进⾏加锁

- Read committed（读已提交）：可避免脏读情况发⽣。不可重复读和幻读⼀定会发⽣。 第三

- Read uncommitted（读未提交）：最低级别，以上情况均⽆法保证。(读未提交) 最低

> 注意：级别依次升⾼，效率依次降低
>
> **MySQL的默认隔离级别是：REPEATABLE READ**
>
> 查询当前使⽤的隔离级别： **select @@tx_isolation;**
>
> 设置MySQL事务的隔离级别： **set session transaction isolation level xxx;** （设置的是当前mysql连接会话的，并不是永久改变的）

#### 1.4 事务传播行为

事务往往在service层进⾏控制，如果出现service层⽅法A调⽤了另外⼀个service层⽅法B，A和B⽅法本身都已经被添加了事务控制，那么A调⽤B的时候，就需要进⾏事务的⼀些协商，这就叫做事务的传播⾏为。

A调⽤B，我们站在B的⻆度来观察来定义事务的传播⾏为

| PROPAGATION_REQUIRED      | 如果当前没有事务，就新建⼀个事务，如果已经存在⼀个事务中，加⼊到这个事务中。这是最常⻅的选择。 |
| ------------------------- | ------------------------------------------------------------ |
| PROPAGATION_SUPPORTS      | ⽀持当前事务，如果当前没有事务，就以⾮事务⽅式执⾏。         |
| PROPAGATION_MANDATORY     | 使⽤当前的事务，如果当前没有事务，就抛出异常。               |
| PROPAGATION_REQUIRES_NEW  | 新建事务，如果当前存在事务，把当前事务挂起。                 |
| PROPAGATION_NOT_SUPPORTED | 以⾮事务⽅式执⾏操作，如果当前存在事务，就把当前事务挂起。   |
| PROPAGATION_NEVER         | 以⾮事务⽅式执⾏，如果当前存在事务，则抛出异常。             |
| PROPAGATION_NESTED        | 如果当前存在事务，则在嵌套事务内执⾏。如果当前没有事务，则执⾏与PROPAGATION_REQUIRED类似的操作。 |



### 2. Spring中事务API

- mybatis: sqlSession.commit(); 
- hibernate: session.commit();

**PlatformTransactionManager**

```java
package org.springframework.transaction;

import org.springframework.lang.Nullable;

public interface PlatformTransactionManager {

	TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
			throws TransactionException;

	void commit(TransactionStatus status) throws TransactionException;

	void rollback(TransactionStatus status) throws TransactionException;

}

```

>此接⼝是Spring的事务管理器核⼼接⼝。Spring本身并不⽀持事务实现，只是负责提供标准，应⽤底层
>
>⽀持什么样的事务，需要提供具体实现类。
>
>此处也是**策略模式**的具体应⽤。在Spring框架中，也为我们内置了⼀些具体策略
>
>如：DataSourceTransactionManager（mybatis) , HibernateTransactionManager(hibernate)
>
>DataSourceTransactionManager 归根结底是横切逻辑代码，声明式事务要做的就是使⽤Aop（动态代
>
>理）来将事务控制逻辑织⼊到业务代码



### 3. Spring声明式事务配置

#### 3.1 纯xml

此种方式目前很少使用，我们不做代码示例

- 依赖

  ```xml
  <dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>5.1.12.RELEASE</version>
  </dependency>
  <dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.9.4</version>
  </dependency>
  <dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-jdbc</artifactId>
  <version>5.1.12.RELEASE</version>
  ```

- xml配置

  ```xml
  <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
          <property name="dataSource" ref="dataSource"></property>
      </bean>
      <tx:advice id="txAdvice" transaction-manager="transactionManager">
          <!--定制事务细节，传播⾏为、隔离级别等-->
          <tx:attributes>
              <!--⼀般性配置-->
              <tx:method name="*" read-only="false" propagation="REQUIRED" isolation="DEFAULT" timeout="-1"/>
              <!--针对查询的覆盖性配置-->
              <tx:method name="query*" read-only="true" propagation="SUPPORTS"/>
          </tx:attributes>
      </tx:advice>
      <aop:config>
          <!--advice-ref指向增强=横切逻辑+⽅位-->
          <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.example.demo.service.impl.TransferServiceImpl.*(*))"/>
      </aop:config>
  ```

  

#### 3.2 xml+注解

- xml配置

  ```xml
  <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
          <property name="dataSource" ref="dataSource"></property>
      </bean>
      <!--开启spring对注解事务的⽀持-->
      <tx:annotation-driven transaction-manager="transactionManager"/>
  ```

- 在接口、类或者方法上添加@Transactional注解

  ```java
  //注解参数可自定义
  @Transactional(readOnly = true,propagation = Propagation.SUPPORTS, rollbackFor = Exception.class)
  ```

#### 3.3 纯注解

Spring基于注解驱动开发的事务控制配置，只需要把 xml 配置部分改为注解实现。只是需要⼀个注解替换掉xml配置⽂件中的<tx:annotation-driven transactionmanager="transactionManager"/> 配置。

在注解类上配置如下即可：

```java
@EnableTransactionManagement//开启spring注解事务的⽀持
public class SpringConfiguration {
}
```






















