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{
    @Override
    public void rent() {
        System.out.println("我是房东,我要租房");
    }

    @Override
    public void sale() {
        System.out.println("我是房东,我买租房");
    }
}

#代理方法
public class HouseProxy implements HouseSubject{
    private RealHouseSubject subject;

    public HouseProxy(RealHouseSubject subject) {
        this.subject = subject;
    }

    @Override
    public void rent() {
        System.out.println("我是中介,我帮房东开始代理");
        subject.rent();
        System.out.println("我是中介,我帮房东结束代理");

    }

    @Override
    public 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;
    }

    @Override
    public 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) {
        //JDK
        HouseSubject 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;
    }
    @Override
    public 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-UNNAMED
        HouseSubject 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 {
    @Autowired
    private 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嵌套事务可以实 现局部回滚,不会影响上⼀个方法中执行的结果

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值