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嵌套事务可以实 现局部回滚,不会影响上⼀个方法中执行的结果