SpringBoot AOP 和 事务

news/2025/1/31 12:26:48 标签: spring boot, 后端, java

SpringBoot 整合 AOP

动态代理技术

JDK 动态代理

  • JDK 动态代理是 Java 自带的一种代理方式。它要求目标类必须有接口,基于这个接口,JDK 在运行时会动态生成一个代理对象。这个代理对象和目标对象就像 “拜把子” 的兄弟,因为它们都实现了相同的接口,能以相同的方式被调用,只是代理对象在调用真正的方法前后可以添加额外的逻辑。
    .

CGLIB 动态代理

  • CGLIB 动态代理是另一种代理技术。它的厉害之处在于不需要目标类有接口,通过让代理类继承目标类来实现代理,就好比代理类 “认” 目标类为 “干爹”。这样,代理类就能在目标类方法调用的前后插入自己的逻辑,从而对目标类的行为进行增强。

Spring AoP

AoP 概述


在这里插入图片描述

AOP 核心概念

  • 切入点:实际被 AOP 控制的方法,需要被增强的方法
  • 通知:封装共享功能的方法就是通知

在这里插入图片描述

AoP 通知类型

环绕就是一部分在目标方法之前,一部分在之后

  • 它的返回值代表的是原始方法执行完毕的返回值

java">try {
	前置通知 @Before
  	目标方法执行
  	返回后通知 @AfterReturning
} catch() {
  	异常后通知 @AfterThrowing
} finally {
	后置通知 @After
}

AoP 通知顺序

在这里插入图片描述

AoP 切点表达式 @execution

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

AoP 切点表达式重用

第一种:创建存储切点的类维护

创建一个存储切点的类
单独维护切点表达式
execution 使用:类全限定符.方法名()

  • 切点维护类
java">@Component
public class MyPointCut {

    @Pointcut("execution(* com.atguigu.service.impl.*.*(..))")
    public void pc(){}
  • 重用类演示类
java">    @Before("com.atguigu.pointcut.MyPointCut.pc()")
    public void start() {
        System.out.println("方法开始了");
    }

    @After("com.atguigu.pointcut.MyPointCut.pc()")
    public void after() {
        System.out.println("方法结束了");
    }

    @AfterThrowing("com.atguigu.pointcut.MyPointCut.pc()")
    public void error() {
        System.out.println("方法报错了");
    }
第二种:当前类中提取表达式

定义一个空方法
注解 @Pointcut()
增强注解中引用切点 直接调用方法名

java">@Aspect
@Component
public class UserServiceAspect {

    // 定义一个空方法,使用@Pointcut注解来定义切点表达式,这里表示匹配UserService接口下的所有方法
    @Pointcut("execution(* com.example.demo.service.UserService.*(..))")
    public void userServicePointcut() {}

    // 在前置通知中复用上面定义的切点表达式,直接写切点方法名即可
    @Before("userServicePointcut()")
    public void beforeAddUser() {
        System.out.println("在执行UserService的方法前执行的逻辑");
    }

AoP 基本使用

底层技术组成

在这里插入图片描述

动态代理(InvocationHandler):

  • JDK原生 的实现方式,需要被代理的目标类必须实现接口。因为这个技术要求代理对象和目标对象实现同样的接口(兄弟两个拜把子模式)。
  • cglib:通过继承被代理的目标类(认干爹模式)实现代理,所以不需要目标类实现接口。
  • AspectJ:SpringAOP借用了AspectJ中的AOP注解。也就是说 @Before 等注解都来自 AspectJ
默认代理方式演示(JDK原生)

在这里插入图片描述

第一步:导入依赖

 <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
第二步:准备接口和实现类

java">public interface Calculator {
    int add(int i, int j);
}
java">@Component
public class CalculatorPureImpl implements Calculator {
    @Override
    public int add(int i, int j) {

        int result = i + j;

        return result;
    }
}
第三步:声明切面类 @Aspect

切面类要加入 IoC 容器
并且要使用 @Aspect 声明为切面类

java">@Aspect
@Component
public class LogAdvice {
    @Pointcut("execution(* com.mangfu.Calculator.*(..))")
    public void Mypointcut(){}

    @Before("Mypointcut()")
    public void testBefore() {
        System.out.println("before");
    }

    @AfterReturning("Mypointcut()")
    public void testAfterReturning() {
        System.out.println("afterReturning");
    }

    @AfterThrowing("Mypointcut()")
    public void testAfterThrowing() {
        System.out.println("afterThrowing");
    }

    @After("Mypointcut()")
    public void testAfter() {
        System.out.println("after");
    }
}

环绕通知

就相当于直接进入切面类执行

  • 接口和实现类
java">public interface Calculator {
    int add(int i, int j);
}
java">@Component
public class CalculatorPureImpl implements Calculator {
    @Override
    public int add(int i, int j) {

        int result = i + j;

        return result;
    }
}
  • 配置类开启 AspectJ
java">@Configuration
@ComponentScan("com.mangfu")
@EnableAspectJAutoProxy
public class MyConfig {
}
  • 切面类
  • ProceedingJoinPoint joinPoint:目标方法对象
  • jointPoint.getArgs(): 获取目标方法运行的参数
  • joinPoint.proceed(args): 执行目标方法
java">@Aspect
@Component
public class LogAdvice {
   @Around("execution(* com.mangfu.Calculator.*(..))")
   public Object around(ProceedingJoinPoint joinPoint) {
       Object[] args = joinPoint.getArgs(); //获取目标方法运行的参数
       Object result = null; //用于接收目标参数的返回值

       try {
           System.out.println("Before");
           result = joinPoint.proceed(args); //调用目标方法
           System.out.println("AfterReturning");
       } catch (Throwable e) {
           System.out.println("Afterthrowing");
           throw new RuntimeException(e);
       } finally {
           System.out.println("After");
       }
       return result;

   }
}
  • 测试类
java">@SpringJUnitConfig(MyConfig.class)
public class MyTest {

    @Autowired
    private Calculator calculator;

    @Test
    public void test() {
        System.out.println(calculator.add(1, 2));
    }
}
JoinPoint 详解

在这里插入图片描述
在这里插入图片描述

CGLib 动态代理生效情况

在目标类没有实现任何接口的情况下,Spring会自动使用cglib技术实现代理。

  • 如果目标类有接口, 选择使用 jdk 动态代理:实现目标接口
  • 如果目标类没有接口, 选择 cglib 动态代理:继承目标对象
  • 如果有接口, 就用接口接值
  • 如果没有接口, 就用类进行接值
  • 没实现接口的实体类和切面类
java">@Component
public class Calculator {
    public int add(int i, int j) {

        int result = i + j;

        return result;
    }
}
java">@Aspect
@Component
public class testBefore {
    @Before("execution(* com.mangfu2.Calculator.add(..))" )
    public void before(){
        System.out.println("test sucessful");

    }
}
  • 测试类
java">@SpringJUnitConfig(MyConfig.class)
public class MyTest {

   //建议用接口取。防止后期取不到代理类对象
    //正常:aop - aop底层选择代理 - 选择jdk代理 - 根据接口生成代理类 - 代理对象和目标对象 是拜把子 兄弟关系。不是同一个
    
    //这里实现类没有实现接口所以用 CGLib 动态代理
    //aop - ioc 容器中真正存储的是代理对象不是目标对象
    @Autowired
    private Calculator calculator;

    @Test
    public void test(){
        calculator.add(1,2);
    }
    
}

Spring AoP 对获取 Bean 的影响

JDK 原生代理

声明一个接口,其仅有一个实现类,同时创建切面类对该接口的实现类应用通知:

  • 按接口类型获取 bean 可正常获取。
  • 按类获取 bean 则无法获取,原因在于应用切面后,实际存放在 IOC 容器中的是代理类对象,目标类本身并未放入 IOC 容器,所以依据目标类类型无法从 IOC 容器中找到相应对象

在这里插入图片描述
在这里插入图片描述

CGLib 代理

声明一个类,创建一个切面类,对上面的类应用通知

  • 根据类获取 bean,能获取到

在这里插入图片描述
在这里插入图片描述

Spring TX 声明式事务

声明式事务概念

  • 定义:通过注解或 XML 配置的方式控制事务的提交和回滚,具体事务实现由第三方框架负责,开发者只需添加相应配置,无需直接进行事务操作。
  • 优点:可将事务控制和业务逻辑分离,提升代码的可读性与可维护性。

请添加图片描述
在这里插入图片描述

  • spring-tx: 包含声明式事务实现的基本规范(事务管理器规范接口和事务增强等等)
  • spring-jdbc: 包含DataSource方式事务管理器实现类DataSourceTransactionManager
  • spring-orm: 包含其他持久层框架的事务管理器实现类例如:Hibernate/Jpa等
    .
    spring-tx 就是 Spring 官方提供的事务接口,各大持久层框架需要自己实现这个接口,而 spring-jdbc 就是 jdbc, jdbcTemplate, mybatis 的实现类 DataSourceTranscationManagerspring-orm 就是 Hibernate/Jpa 等持久层框架的实现类

声明式事务基本使用

SpringBoot 项目会自动配置一个 DataSourceTransactionManager,所以我们只需在方法(或者类)加上 @Transactional 注解,就自动纳入 Spring 的事务管理了

第一步:加入依赖


  • jdbc, jdbcTemplate, mybatis 这三个持久层框架情况
<!-- 第三方实现 spring 事务接口的类的依赖-->
<!-- spring-jdbc -->
  <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>6.0.6</version>
  </dependency>
  • 如果是 Hibernate 等其他持久层框架就用 spring-jdbc 换成 spring-orm 依赖

第二步:给需要事务的地方加上 @Transactional 注解


@Treanscational 加在类上就是类里所有方法开启事务。加在方法上就单独那个方法有事务

  • Dao层
java">@Repository
public class StudentDao {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    public void updateNameById(String name,Integer id){
        String sql = "update students set name = ? where id = ? ;";
        int rows = jdbcTemplate.update(sql, name, id);
    }

    public void updateAgeById(Integer age,Integer id){
        String sql = "update students set age = ? where id = ? ;";
        jdbcTemplate.update(sql,age,id);
    }
}
  • Service 层
java">@Service
public class StudentService {
    
    @Autowired
    private StudentDao studentDao;

    /**
     * 添加事务:
     *      @Transctional
     *      位置: 方法 | 类上
     *      方法: 当前方法有事务
     *      类上: 泪下所有方法都有事务
     */
     //这里有自动事务了。所以可以报错可以自动回滚
    @Transactional
    public void changeInfo(){
        studentDao.updateAgeById(88,1);
        int i = 1/0;
        System.out.println("-----------");
        studentDao.updateNameById("test1",1);
    }
}
  • 测试类
java">@SpringJUnitConfig(JavaConfig.class)
public class TxTest {

    @Autowired                               
    private StudentService studentService;

    @Test
    public void  testTx(){
        studentService.changeInfo();
    }
}

事务的属性

事务属性:只读


只读模式可以提升查询事务的效率!,一般情况都是通过类添加注解添加事务,类下的所有方法都有事务,而查询方法一般不用添加事务。这个时候可以再次添加事务注解,设置为只读,提高效率查询效率

  • @Transactional(readOnly = ...)
java">@Transactional
@Service
public class StudentService {

    @Autowired
    private StudentDao studentDao;


	 //查询 没有必要添加事务。这里设置为只读提高效率
    @Transactional(readOnly = true)
    public void getStudentInfo() {
       
        //获取学生信息 查询数据库 不修改

    }

}

事务属性:超时


事务在执行过程中,有可能因为遇到某些问题,导致程序卡住,从而长时间占用数据库资源。而长时间占用资源,大概率是因为程序运行出现了问题(可能是Java程序或MySQL数据库或网络连接等等)。此时这个很可能出问题的程序应该被回滚,撤销它已做的操作,事务结束,把资源让出来,让其他正常程序可以执行。概括来说就是一句话:超时回滚,释放资源。

  • @Transactional(timeout = ...)
java">@Transactional(timeout = 3)
@Service
public class StudentService {

    @Autowired
    private StudentDao studentDao;
    public void changeInfo() {
        studentDao.updateAgeById(88,1);
        System.out.println("-----------");
        try {
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        studentDao.updateNameById("test2",1);
    }

	//因为这个注解会覆盖掉类上注解。所以要再设置一遍
    @Transactional(readOnly = true, timeout = 3)
    public void getStudentInfo() {
     
    }

}

事务属性:回滚


默认情况下,发生运行时 (RuntimeException) 异常事务才回滚,所以我们可以指定 Exception 异常来控制所有异常都回滚

  • roollbackFor = 回滚的异常范围:设置的异常都回滚
  • noRollbackFor = 不回滚的异常范围:控制某个异常不回滚
java">//所有异常都回滚,除了 FileNotFoundException 
@Transactional(rollbackFor = Exception.class, noRollbackFor = FileNotFoundException.class)
    public void changeInfo() throws FileNotFoundException {
        studentDao.updateAgeById(99,1);

        new FileInputStream("xxxx");

        studentDao.updateNameById("test2",1);
    }

事务属性:事务隔离级别


事务并发可能引发的问题

  • 脏读:一个事务读取另一个事务未提交的数据
  • 不可重复读:一个事务就是读取了另一个事务提交的修改数据
  • 幻读: 一个事务读取了另一个事务提交的插入数据

事务隔离级别

  • 读未提交(Read Uncommitted):事务可以读取未被提交的数据,容易产生脏读、不可重复读和幻读等问题。实现简单但不太安全,一般不用。
  • 读已提交(Read Committed):事务只能读取已经提交的数据,可以避免脏读问题,但可能引发不可重复读和幻读。
  • 可重复读(Repeatable Read):在一个事务中,相同的查询将返回相同的结果集,不管其他事务对数据做了什么修改。可以避免脏读和不可重复读,但仍有幻读的问题。
  • 串行化(Serializable):最高的隔离级别,完全禁止了并发,只允许一个事务执行完毕之后才能执行另一个事务。可以避免以上所有问题,但效率较低,不适用于高并发场景。
    .

不同的隔离级别适用于不同的场景,需要根据实际业务需求进行选择和调整。

建议设置第二个隔离级别

isolation = Isolation.事务的隔离级别

  • READ_UNCOMMITTED 读未提交
  • READ_COMMITTED 读已提交
  • REPEATABLE_READ 可重复读
  • SERIALIZABLE 串行化
java">//设置事务隔离级别为可串行化 
@Transactional(isolation = Isolation.SERIALIZABLE)
    public void changeInfo() throws FileNotFoundException {
        studentDao.updateAgeById(99,1);

        new FileInputStream("xxxx");

        studentDao.updateNameById("test2",1);
    }

事务属性:事务传播行为


  • propagation = 传播规则【默认是 Propagation.REQUIRED】
    我们一般使用默认就行
名称【传播规则】含义
REQUIRED如果父方法有事务,就加入,如果没有就新建自己独立!父方法有事务报错,子方法会回滚
REQUIRES_NEW不管父方法是否有事务,我都新建事务,都是独立的!即使父方法有有事务报错。两个子方法也不会回滚

在这里插入图片描述

就是用父方法 topService() 调用 子方法changeAge()changeName()。这两个子方法是否会进行回滚


http://www.niftyadmin.cn/n/5838659.html

相关文章

arm-linux-gnueabihf安装

Linaro Releases windows下打开wsl2中的ubuntu&#xff0c;资源管理器中输入&#xff1a; \\wsl$gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz 复制到/home/ark01/tool 在 Ubuntu 中创建目录&#xff1a; /usr/local/arm&#xff0c;命令如下&#xff1a; …

如何构建树状的思维棱镜认知框架

在思维与知识管理中&#xff0c;“树状思维棱镜”通常指一种层级式、可多维度展开和不断深入&#xff08;下钻&#xff09;的认知框架。它不仅仅是普通的树状结构&#xff08;如传统思维导图&#xff09;&#xff0c;更强调“棱镜”所体现的多视角、多维度切换与综合分析的能力…

简易计算器(c++ 实现)

前言 本文将用 c 实现一个终端计算器&#xff1a; 能进行加减乘除、取余乘方运算读取命令行输入&#xff0c;输出计算结果当输入表达式存在语法错误时&#xff0c;报告错误&#xff0c;但程序应能继续运行当输出 ‘q’ 时&#xff0c;退出计算器 【简单演示】 【源码位置】…

宇宙大爆炸是什么意思

根据宇宙大爆炸学说&#xff0c;宇宙间的一切都在彼此远离&#xff0c;而且距离越远&#xff0c;远离的速度越快。我们只能在地球上观察这种现象&#xff0c;而我们观察到的速度符合如下公式&#xff0c;其中 为哈勃常数&#xff0c; 为距离&#xff0c; 为速度&#xff08;…

分享|通过Self-Instruct框架将语言模型与自生成指令对齐

结论 在大型 “指令调整” 语言模型依赖的人类编写指令数据存在数量、多样性和创造性局限&#xff0c; 从而阻碍模型通用性的背景下&#xff0c; Self - Instruct 框架&#xff0c; 通过 自动生成 并 筛选指令数据 微调预训练语言模型&#xff0c; 有效提升了其指令遵循能…

【memgpt】letta 课程6: 多agent编排

Lab 6: Multi-Agent Orchestration 多代理协作 letta 是作为一个服务存在的,app通过restful api 通信 多智能体之间如何协调与沟通? 相互发送消息共享内存块,让代理同步到不同的服务的内存块

智慧“城市大脑”之城市安全运行方案

智慧“城市大脑”背景 随着城市化进程加速&#xff0c;城市安全运行成为重要议题。智慧“城市大脑”方案应运而生&#xff0c;依托先进物联网技术&#xff0c;旨在提升城市安全管理水平。 建设思路及原则 方案遵循“城市大脑”理念&#xff0c;打造“感”“传”“知”“用”…

【RocketMQ 存储】- broker 端存储单条消息的逻辑

文章目录 1. 前言2. DefaultMessageStore#asyncPutMessage 添加单条消息2.1 DefaultMessageStore#checkStoreStatus 检查存储服务的状态2.2 DefaultMessageStore#checkMessage 校验消息长度是否合法2.3 CommitLog#asyncPutMessage 核心存储逻辑2.4 MappedFile#appendMessage2.5…