当前位置: 首页 > news >正文

Spring AOP 事务

        

目录

一,引入依赖:

二,切面

1,基本概念

2, 通知类型:

3,@Pointcut

4, 切面优先级:

5 ,自定义优先级@Order

6,切点表达式

7, 自定义注解

总结: AOP有几种创建方式

三, Spring AOP原理

1, 代理模式

(1)静态代理

(2)动态代理

△JDK动态代理

△CGLIB动态代理

JDB和cglib的区别

四, 事务

1, 事务依赖

2, @Transactional详解

(1)rollbackFor:

(2)isolation:

(3)propagation


面向切面编程,对于一类问题的统一处理,拦截器,统一结果返回,统一异常都是AOP思想的一种实现. Spring框架实现了这种思想,提供了拦截器技术的相关接口,AOP对源代码没有侵入

一,引入依赖:

 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>
@Aspect
@Component
@Slf4j
public class TimeAspect {@Around("execution(* com.ABdolphin.blog.controller.*.*(..))")public Object recordTime(ProceedingJoinPoint pip) throws Throwable {long begin=System.currentTimeMillis();Object proceed = pip.proceed();long end=System.currentTimeMillis();log.info(pip.getSignature()+"执行时间: {}ms",end-begin);return proceed;}
}

二,切面

1,基本概念

切面=连接点+切点+通知

连接点:目标方法

切点:通过一个表达式来描述这个切点

通知:具体要做的事情

2, 通知类型:

@Around:环绕通知,此注解标注的通知⽅法在⽬标⽅法前,后都被执⾏

@Before:前置通知,此注解标注的通知⽅法在⽬标⽅法前被执⾏

@After:后置通知,此注解标注的通知⽅法在⽬标⽅法后被执⾏,⽆论是否有异常都会执⾏ @AfterReturning: 返回后通知,此注解标注的通知⽅法在⽬标⽅法后被执⾏,有异常不会执⾏ @AfterThrowing:异常后通知,此注解标注的通知⽅法发⽣异常后执⾏

    @Before("execution(* com.ABdolphin.blog.controller.*.*(..))")public void doBefore(){log.info("AspectDemo 执行 doBefore...");}@After("execution(* com.ABdolphin.blog.controller.*.*(..))")public void doAfter(){log.info("AspectDemo 执行 doAfter...");}@AfterReturning("execution(* com.ABdolphin.blog.controller.*.*(..))")public void AfterReturning(){log.info("AspectDemo 执行 AfterReturning...");}@AfterThrowing("execution(* com.ABdolphin.blog.controller.*.*(..))")public void AfterThrowing(){log.info("AspectDemo 执行 AfterThrowing...");}@Around("execution(* com.ABdolphin.blog.controller.*.*(..))")public Object Around(ProceedingJoinPoint pjp)  {log.info("AspectDemo 执行 Around前...");Object proceed = null;try {proceed = pjp.proceed();} catch (Throwable e) {log.error("around 发生异常");}log.info("AspectDemo 执行 Around后...");return proceed;}

3,@Pointcut

@Pointcut,可以把公共切点表达式提取出来

    #类内部直接调用就可以了@Pointcut("execution(* com.ABdolphin.blog.controller.*.*(..))")public void pt(){}@Before("pt()")public void doBefore(){log.info("AspectDemo 执行 doBefore...");}@After("pt()")public void doAfter(){log.info("AspectDemo 执行 doAfter...");}#类外部,需要将路径描述清楚@Around("com.ABdolphin.blog.common.aspect.AspectDemo.pt()")public Object recordTime(ProceedingJoinPoint pip) throws Throwable {...}

当外部类有使用@Pointcut时,需要将当前类名完整的表述出来

4, 切面优先级:

5 ,自定义优先级@Order

数字越小,优先级越高

6,切点表达式

execution(< 访问修饰符 > < 返回类型 > < 包名 . 类名 . ⽅法 ( ⽅法参数 )> < 异常 >)

切点表达式⽀持通配符表达:

1. * :匹配任意字符,只匹配⼀个元素(返回类型,包,类名,⽅法或者⽅法参数)

a. 包名使⽤ * 表⽰任意包(⼀层包使⽤⼀个*)

b. 类名使⽤ * 表⽰任意类

c. 返回值使⽤ * 表⽰任意返回值类型

d. ⽅法名使⽤ * 表⽰任意⽅法

e. 参数使⽤ * 表⽰⼀个任意类型的参数

2 .. :匹配多个连续的任意符号,可以通配任意层级的包,或任意类型,任意个数的参数

a. 使⽤ .. 配置包名,标识此包以及此包下的所有⼦包

b. 可以使⽤ .. 配置参数,任意个任意类型的参数

其中:访问修饰符,和异常可省略

7, 自定义注解

#注解声明
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface TimeRecord {
}
@Aspect
@Component
@Slf4j
public class TimeAspect {@Around("@annotation(com.ABdolphin.blog.common.aspect.TimeRecord)")public Object recordTime(ProceedingJoinPoint pip) throws Throwable {long begin=System.currentTimeMillis();Object proceed = pip.proceed();long end=System.currentTimeMillis();log.info(pip.getSignature()+"执行时间: {}ms",end-begin);return proceed;}
}

总结: AOP有几种创建方式

1. 基于注解 @Aspect (参考上述课件内容)

2. 基于⾃定义注解(参考⾃定义注解 @annotation 部分的内容)

3. 基于SpringAPI(通过xml配置的⽅式,⾃从SpringBoot⼴泛使⽤之后,这种⽅法⼏乎看不到了)

4. 基于代理来实现(更加久远的⼀种实现⽅式,写法笨重,不建议使⽤)

三, Spring AOP原理

是基于动态代理实现的

1, 代理模式

定义:为其他对象提供⼀种代理以控制对这个对象的访问.它的作⽤就是通过提供⼀个代理类,让我们在调⽤⽬标⽅法的时候,不再是直接对⽬标⽅法进⾏调⽤,⽽是通过代理类间接调用

1. Subject: 业务接⼝类.可以是抽象类或者接⼝(不⼀定有)

2. RealSubject: 业务实现类. 具体的业务执⾏,也就是被代理对象.

3. Proxy: 代理类.RealSubject的代理.

代理模式分为静态代理和动态代理

(1)静态代理

• 静态代理:由程序员创建代理类或特定⼯具⾃动⽣成源代码再对其编译,在程序运⾏前代理类的 .class ⽂件就已经存在了.

#接口
public interface HouseSubject {void rent();void sale();
}
#真实的代理对象
public class RealHouseSubject implements HouseSubject{@Overridepublic void rent() {System.out.println("我是房东,我要租房");}@Overridepublic void sale() {System.out.println("我是房东,我买租房");}
}
#代理方法
public class HouseProxy implements HouseSubject{private RealHouseSubject subject;public HouseProxy(RealHouseSubject subject) {this.subject = subject;}@Overridepublic void rent() {System.out.println("我是中介,我帮房东开始代理");subject.rent();System.out.println("我是中介,我帮房东结束代理");}@Overridepublic void sale() {System.out.println("我是中介,我帮房东开始代理");subject.sale();System.out.println("我是中介,我帮房东结束代理");}
}
public class Main {public static void main(String[] args) {//静态代理HouseProxy houseProxy=new HouseProxy(new RealHouseSubject());houseProxy.rent();houseProxy.sale();}

由于代码都写死了,对目标对象的 每个方法的增强都是手动完成的,非常不灵活. 所以日常开发几乎看不到静态代理的场景

(2)动态代理

• 动态代理:在程序运⾏时,运⽤反射机制动态创建⽽成.一般的实现方式分类两种,①JDK动态代理②CGLIB动态代理

△JDK动态代理

1. 定义⼀个接⼝及其实现类

2. ⾃定义 HouseSubject 和 RealHouseSubject  . InvocationHandler 并重写 invoke ⽅法,在 invoke ⽅法中我们会调⽤⽬标⽅法(被代理类的⽅法)并⾃定义⼀些处理逻辑

3. 通过 Proxy.newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h) ⽅法创建代理对象

public class JDKInvocation implements InvocationHandler {#代理的对象private  Object target;#初始化(初始化为一个HouseSubject 对象)public JDKInvocation(HouseSubject target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("开始代理");Object invoke = method.invoke(target, args);System.out.println("结束代理");return invoke;}
}
public class Main {public static void main(String[] args) {//JDKHouseSubject target=new RealHouseSubject();HouseSubject proxy= (HouseSubject) Proxy.newProxyInstance(target.getClass().getClassLoader(),new Class[]{HouseSubject.class},new JDKInvocation(target));proxy.rent();proxy.sale();}
△CGLIB动态代理

1. 定义⼀个类(被代理类) MethodInterceptor 并重写

2. ⾃定义法,和JDK动态代理中的 intercept ⽅法, invoke ⽅法类似

3. 通过Enhancer类的create()创建代理类

想要使用Cglib需要引入一个依赖

//cglib<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version></dependency>
public class CglibMethodInvocation implements MethodInterceptor {private  Object target;public CglibMethodInvocation(Object target) {this.target = target;}@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("开始代理");Object invoke = method.invoke(target, args);System.out.println("结束代理");return invoke;}
}
public class Main {public static void main(String[] args) {//cglib//如果引用的是Cglib的包的话,,需要添加vm 依赖//--add-opens java.base/java.lang=ALL-UNNAMEDHouseSubject target=new RealHouseSubject();HouseSubject proxy= (HouseSubject) Enhancer.create(target.getClass(),new CglibMethodInvocation(target));proxy.rent();}

如果引用的是Cglib的包的话, 需要添加vm 依赖

--add-opens java.base/java.lang=ALL-UNNAMED

JDB和cglib的区别

spring.aop.proxy-target-class=false

proxyTargetClass目标对象代理方式
false                实现了接口

jdk代理        

false

只实现了类cglib
true实现了接口    

cglib

true只实现了类cglib

Spring Framework默认是false. Spring Boot默认是true, 即全部是cglib进行代理

JDK只能代理接口,不能代理类. cglib这既可以代理类又可以代理接口

四, 事务

1, 事务依赖

 <dependency><groupId>org.springframework</groupId><artifactId>spring-tx</artifactId></dependency>
@Slf4j
@RequestMapping("/trans")
@RestController
public class Test {@Autowiredprivate UserService userService;@Transactional@RequestMapping("/registry")public String registry(String name,String password) {//⽤⼾注册userService.registryUser(name, password);log.info("⽤⼾数据插⼊成功 ");//强制程序抛出异常int a = 10 / 0;return "注册成功";}
}

整个方法中没有异常,提交.如果有异常,就进行回滚,此时上述代码抛出异常(运行时异常,和error异常),则插入的数据也会进行回滚

@Transactional 只作用public生效.它既是类注解也是方法注解,作为类注解,它对类下的所有public方法生效

当异常被catch住了,该方法也将执行成功,不会进行回滚,如果throw出去,则会进行回滚

手动回滚事务

TransactionAspectSupport.currentTransactionStatus()

        userService.registryUser(name, password);log.info("⽤⼾数据插⼊成功 ");//强制程序抛出异常try {int a = 10 / 0;}catch (Exception e){System.out.println("发生异常");TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();}return "注册成功";

2, @Transactional详解

(1)rollbackFor:

发生非运行时异常是不会进行回滚的,只有运行时异常和error才会进行回滚

@Transactional(rollbackFor = Exception.class)

当将写成如上代码,就会对所有的异常进行回滚

(2)isolation:

事务隔离级别

· 读未提交:第一个线程还在修改数据,但是没有提交,第二个线程就进行了读操作,就会读到线程一还没有提交的数据,即脏数据,存在的问题为脏读

· 读已提交:读到的是一个线程已经提交后的数据,但是在一个事务中,不同的时间读同一个数据,可能会读到不同的结果,这种现象叫做不可重复读

· 可重复读:事务不会读到其他事务已修改的数据,即使其他事务已经提交,因此,也就可以确保多次查询同一个数据,读到的结果是一致的. 事实上已经插入数据,但是该事务并读不到,因此称之为幻读(实现方法mvcc)

· 串行化:事务的最高隔离级别,他会强制将事务进行排序,但执行效率过低,真正使用的场景不多

(3)propagation

事务的传播机制

A调用B, 下述配置是添加至B上的

①Propagation.REQUIRED: 如果A有事务,则B加入A的事务,如果A没事务,则B创建一个事务(默认值)

②Propagation.SUPPORTS: 若A有事务,则B加入,如果A无事务,则B按照非事务的方式运行

③Propagation.MANDATORY: 若A有事务,则B加入,如果A无事务,则B抛出异常

④Propagation.REQUIRES_NEW: 无论A有没有事务,B都会创建新的事务

⑤Propagation.NOT_SUPPORTED: 无论A有没有事务,B都会以非事务的方式执行

⑥Propagation.NEVER: 以非事务的方式运行,如果存在事务,则抛出异常

⑦Propagation.NESTED: 如果当前存在事务,则创建⼀个事务作为当前事务的嵌套事务来运行. 如果当前没有事务,则该取值等价于 Propagation.REQUIRED

NESTED和REQUIRED区别: 如果事务⼀部分执行成功,REQUIRED加⼊事务会导致整个事务全部回滚.NESTED嵌套事务可以实 现局部回滚,不会影响上⼀个方法中执行的结果

相关文章:

  • 【Linux专栏】zip 多个文件不带路径
  • 2025年渗透测试面试题总结-拷打题库09(题目+回答)
  • Windows1909,21H2哪个版本更稳定
  • 小刚说C语言刷题——1039 求三个数的最大数
  • 1️⃣4️⃣three.js_Stats性能监视器
  • 机器学习中,什么叫监督学习?什么叫非监督学习?
  • 25.解决中医知识问答删除历史对话功能后端处理请求时抛出异常
  • 【大数据、数据开发与数据分析面试题汇总(含答案)】
  • OpenCV训练题
  • 【Redis】Redis 特性
  • L1-1、Prompt 是什么?为什么它能“控制 AI”?
  • 爱普生TG-5006CG成为提升5G RedCap时钟同步精度的理想选择
  • ECA 注意力机制:让你的卷积神经网络更上一层楼
  • 【Pandas】pandas DataFrame sub
  • FreeRTOS互斥信号量解决优先级翻转实战教程
  • 大模型中超参数TopK是什么
  • 批量创建同名文件夹并整理文件至对应文件夹
  • ElementUI中checkbox v-model绑定值为布尔、字符串或数字类型
  • Docker如何更换镜像源提高拉取速度
  • NLP高频面试题(四十八)大语言模型中的思维链(CoT)技术详解
  • GDP增长4.1%,一季度广东经济数据出炉
  • 广发基金刘格崧一季报:首次买入广东宏大、分众传媒,减仓亿纬锂能
  • 文化中国行|1500年水镇枫泾有座丁聪美术馆
  • 由“环滁皆山”到“环滁皆景”,滁州如何勾勒“文旅复兴”
  • 守护体面的保洁员,何时能获得体面?|离题
  • 陕西省药监局:未检出巨子生物“可复美”存在“表皮生长因子”