SpringBoot 事务&AOP入门

文章介绍了SpringBoot中的事务管理,包括@Transational注解的使用,异常回滚和传播行为。接着讲解了AOP的概念,核心概念如连接点、切入点、通知,并展示了不同类型的通知(@Before,@After,@Around等)及其应用场景。还提到了如何通过@Pointcut抽取公共切入点表达式以及通知的执行顺序和切入点表达式的使用。
摘要由CSDN通过智能技术生成

1.SpringBoot事务管理

1.1 事务

事务是一个不可分割的整体,要么同时成功,要么同时失败

1.2事务的操作:

  1. 开启事务:start transaction / begin
  2. 提交事务:commit
  3. 回滚事务:rollback

1.3 SpringBoot中的事务

在SprintBoot项目中开启事务只需要在类、接口、方法上添加@Transactional注解就可以开启事务

@Transactional注解在方法上的作用
在方法执行前开启事务,方法执行完成后提交事务,如果中途出现运行时异常则回滚事务

1.3.1 异常回滚的属性:rollbackFor

当我们在方法上加了@Transactional注解进行事务管理,出现运行时异常时会回滚事务,从而保证事务操作前后数据是一致的,但是当出现编译时异常时,事务会正常提交,假如我们想要所有的异常都会回滚,就需要配置 @Transactional 注解中的 rollbackFor 属性

// 默认只会回滚运行时异常 而rollbackFor可以指定出现那种异常时回滚事务
@Transactional(rollbackFor = Exception.class)

1.3.2 异常传播行为:propagation

首先我们先说一下什么是事务的传播行为,事务的传播行为就是,当一个事务方法被另一个事务方法调用时,被调用的方法是如何进行事务控制的。

可能听起来比较抽象,那我们举个例子,例如,A方法有事务,B方法也有一个事务,A方法调用了B方法,那么此时B方法是使用A方法的事务,还是自己创建一个呢,这个就是事务的传播行为。

传播行为的分类

属性值描述
REQUIRED默认值 有则加入,没有则创建
REQUIRES_NEW无论有没有,都会自己创建新事务
SUPPORTS有则加入,没有就在没有事务的状态中运行
NOT_SUPPORTED如果有事务,则将事务挂起
MANDATORY必须有事务,没有就抛异常
NEVER必须没有事务,有则抛异常

:------------------------站在事务B方法的角度 (前两个开发中常用)------------------------:

propagation值的使用

// 有则加入,没有则创建
@Transactional(propagation = Propagation.NESTED)
// 无论有没有,都会创建一个新事务
@Transactional(propagation = Propagation.REQUIRES_NEW)

2.SpringBoot AOP入门

2.1 AOP概述

AOP(Aspect Oriented Programming):面向切面编程,大白话就是面向特定的方法编程。

那么面向特定方法编程是什么,在说白一点,如果我们需要测试一些方法进行性能测试,需要拿到每个
方法执行的耗时,应该怎么实现呢,在每个方法前后加上一个获得当前时间然后取差就可以完成,但是
一个项目中有成千上万个方法,先不说工作量,光是这些重复的动作就已经很恶心了。

而AOP就是在不改变原来方法的前提下,对方法进行增强。

2.2 AOP核心概述

下面说一下AOP的几个专业的核心概念

  1. 连接点:JoinPoint 可以被AOP控制的方法
  2. 切入点:PoinCut已经被控制AOP的方法
  3. 通知:Advice重复的逻辑,共性内容
  4. 切面: Aspect切入点+通知
  5. 目标对象:Target被增强方法所在的对象

2.3 通知类型

@Before:前置通知,此注解标注的通知方法在目标方法前被执行
@After:后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行
@Around:坏绕通知,此注解标注的通知方法在目标方法前、后都会被执行
@AfterReturning:返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行
@AfterThrowing:异常后通知,此注解标注的通知方法只有发生异常后才执行

//前置通知
@Before("切入点表达式")
//环绕通知
@Around("切入点表达式")
//后置通知
@After("切入点表达式")
//返回后通知(程序在正常执行的情况下,会执行的后置通知)
@AfterReturning("切入点表达式")
//异常通知(程序在出现异常的情况下,执行的后置通知)
@AfterThrowing("切入点表达式")

代码演示

@Slf4j
@Component
@Aspect
public class MyAspect1 {
    //前置通知
    @Before("execution(* com.itheima.service.*.*(..))")
    public void before(JoinPoint joinPoint){
        log.info("前置通知执行了...");

    }

    //环绕通知
    @Around("execution(* com.itheima.service.*.*(..))")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        log.info("坏绕后通知执行了...");

        //调用目标对象的原始方法执行
        Object result = proceedingJoinPoint.proceed();
        
        //原始方法如果执行时有异常,环绕通知中的后置代码不会在执行了      
        log.info("坏绕前通知执行了 ...");
        return result;
    }

    //后置通知
    @After("execution(* com.itheima.service.*.*(..))")
    public void after(JoinPoint joinPoint){
        log.info("后置通知执行了...");
    }

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

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

注意事项
@Around环绕通知需要自己调用ProceedingJoinPoint.proceed()来让原始方法执行,其他通知不需要考虑目标方法执行
@Around坏绕通知方法的返回值,必须指定为Object来接收原始方法的返回值,否则原始方法执行完毕,是获取不到返回值的

//环绕通知
@Around("execution(* com.itheima.service.*.*(..))")
public Object around(ProceedingJoinPoint proceedingJoinPoint){
    log.info("环绕前通知...");
     //调用目标对象的原始方法执行
    Object result = proceedingJoinPoint.proceed();        
//原始方法如果执行时有异常,环绕通知中的后置代码不会在执行了    
    log.info("环绕后置通知...");
    return result;
}

在上述代码中切入点表达式一摸一样,并且需要修改时需要一个一个修改,较为繁琐,我们可以将这些切入点表达式进行抽取
而Spring为我们提供了一个@PointCut注解,这个注解的作用就是将公共切入点表达式抽取出来,使用时引用就可以,但是如果切入点方法使用了private修饰,就只能在当前切面类中引用,如果其他切面类也想引用,就需要把private修改为public,而引用时需要使用全类名引用
格式:全类名.方法名()

// 本类
@Slf4j
@Component
@Aspect
public class MyAspect1 {
	// 切入点方法(公共的切入点表达式)
	@Pointcut("execution(* com.itheima.service.*.*(..))")
	    private void pt(){
	}
	// 环绕通知(引用切入点)
	@Around("pt()")
	public Object around(ProceedingJoinPoint proceedingJoinPoint){
	    log.info("环绕前通知...");
	     //调用目标对象的原始方法执行
	    Object result = proceedingJoinPoint.proceed();        
	// 原始方法如果执行时有异常,环绕通知中的后置代码不会在执行了    
	    log.info("环绕后置通知...");
	    return result;
	}
}
// 其他类
@Slf4j
@Component
@Aspect
public class MyAspect2 {
    //引用MyAspect1切面类中的切入点表达式
    @Before("com.itheima.aspect.MyAspect1.pt()")
	public Object around(ProceedingJoinPoint proceedingJoinPoint){
	    log.info("环绕前通知...");
	     //调用目标对象的原始方法执行
	    Object result = proceedingJoinPoint.proceed();        
	// 原始方法如果执行时有异常,环绕通知中的后置代码不会在执行了    
	    log.info("环绕后置通知...");
	    return result;
	}
}

2.4 通知顺序

当我们定义了多个切面类,而多个切入点都匹配到了同一个目标方法上,此时当目标方法执行时,那么那个切面类当中的通知方法会先执行呢(多个通知,对应的切入点一样)
顺序:默认是按照切面类 类名的自然排序决定先后顺序

而目标方法前的通知方法:字母靠前先执行
而目标方法后的通知方法:字母靠后先执行

控制通知的执行顺序有两种方式:

  1. 修改切面类的类名(繁琐,不推荐)
  2. 使用Spring提供的@Order注解
@Order(1)  //切面类的执行顺序(前置通知:数字越小先执行; 后置通知:数字越小越后执行)

2.5 切入点表达式

切入点表达式: 描述切入点方法的一种表达式
形式:

  1. execution(……):根据方法的签名来匹配
  2. @annotation(……) :根据注解匹配

2.5.1 execution

(1) 语法

// ?号部分可以省略
execution(访问修饰符?  返回值  包名.类名.?方法名(方法参数) throws 异常?)
// 示例
@Before("execution(void com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))")

(2) 通配符:
*:可以通配单个层级或者单个参数
. .:可以通配多个连续的层级或者多个参数

(3) 切入点表达式的语法规则

  1. 方法的访问修饰符可以省略
  2. 返回值可以使用*号代替(任意返回值类型)
  3. 包名可以使用*号代替,代表任意包(一层包使用一个*
  4. 使用..配置包名,标识此包以及此包下的所有子包
  5. 类名可以使用*号代替,标识任意类
  6. 方法名可以使用*号代替,表示任意方法
  7. 可以使用 * 配置参数,一个任意类型的参数
  8. 可以使用.. 配置参数,任意个任意类型的参数

示例:

省略方法的修饰符号

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

使用*代替返回值类型

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

使用*代替包名(一层包使用一个*

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

使用..省略包名

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

使用*代替类名

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

使用*代替方法名

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

使用 * 代替参数

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

使用..省略参数

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

注意事项
根据业务需要,可以使用 且(&&)、或(||)、非(!) 来组合比较复杂的切入点表达式。

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

切入点表达式的书写建议

所有业务方法名在命名时尽量规范,方便切入点表达式快速匹配。如:查询类方法都是 find 开头,更新类方法都是update开头

2.5.2 @annotation

在匹配多个无规则的目标方法是,execution就闲的不是很方便了,而我们借助@annotation就可以简化切入点表达式的书写
实现步骤:

  1. 编写自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLog {
}
  1. 在业务类要做为连接点的方法上添加自定义注解
// 业务类中的目标方法
@MyLog //自定义注解(表示:当前方法属于目标方法)
public void test() {
}
// 切面类中的通知方法
 @Before("@annotation(com.itheima.anno.MyLog)")
    public void before(){
        log.info("annotation注解测试 ...");
    }

总结
execution切入点表达式

根据我们所指定的方法的描述信息来匹配切入点方法,这种方式也是最为常用的一种方式
如果我们要匹配的切入点方法的方法名不规则,或者有一些比较特殊的需求,通过execution切入点表达式描述比较繁琐

annotation 切入点表达式

基于注解的方式来匹配切入点方法。这种方式虽然多一步操作,我们需要自定义一个注解,但是相对来比较灵活。我们需要匹配哪个方法,就在方法上加上对应的注解就可以了
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值