八、AOP

63 阅读5分钟

一,AOP基础

1.1 AOP概述

image-20231104233159774

image-20231104233636043

1.2 AOP核心概念

image-20231105103004208

1.3 AOP快速入门

  1. 导依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    		
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.20</version>
    </dependency>
    
  2. 制作连接点方法(原始操作,Service接口与实现类)

    @Service
    public class aopServiceImpl implements aopService {
    
        private aopDao dao;
    
        @Autowired
        public void setDao(aopDao dao){
            this.dao=dao;
        }
    
        @Override
        public void save() {
            dao.save();
            System.out.println("Service....save.....");
        }
    
        @Override
        public void update() {
            dao.update();
            System.out.println("Service....update....");
        }
    }
    
    
  3. 制作共性功能(通知类与通知)

    public class MyAdvice {
    
        public void before(){
            System.out.println("before");
        }
    
    }
    
  4. 定义切入点

    public class MyAdvice {
        @Pointcut("execution(* com.aopDemo.service.impl.aopServiceImpl.save())")
        public void pt(){};
    }
    
  5. 绑定切入点与通知关系(切面)

    public class MyAdvice {
        
        @Pointcut("execution(* com.aopDemo.service.aopService.save())")
        public void pt(){};
        //这种pt()是简写,实际上我们可以把pt抽取到一个pt类然后通过类名.方法名()来调用
        @Before("pt()")
        public void before(){
            System.out.println("before");
        }
    
    }
    
  6. 定义通知类受Spring容器管理,并定义当前类为切面类

    @Component
    @Aspect
    public class MyAdvice {
    
        @Pointcut("execution(* com.aopDemo.service.aopService.save())")
        public void pt(){};
        @Before("pt()")
        public void before(){
            System.out.println("before");
        }
    
    }
    
  7. 开启Spring对AOP注解驱动支持

    @Configuration
    @ComponentScan(basePackages ="com.aopDemo")
    @EnableAspectJAutoProxy//开启spring对aop注解驱动的支持
    public class SpringConfig {
    }
    

1.4 AOP执行流程

  1. Spring容器启动

  2. 读取所有切面配置中的切入点

  3. 初始化bean,判定bean对应的类中的方法是否匹配到任意切入点

    • 匹配失败,创建对象

    • 匹配成功,创建原始对象(目标对象)的代理对象

  4. 获取bean执行方法

    • 获取bean,调用方法并执行,完成操作

    • 获取的bean是代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作)

目标对象(Target):原始功能去掉共性功能对应的类产生的对象,这种对象是无法直接完成最终工作的

**代理(**Proxy):目标对象无法直接完成工作,需要对其进行功能回填,通过原始对象的代理对象实现


AOP在执行的时候,对目标对象创建对应的代理对象,最终注入的是代理对象。

image-20231105105015329

二,AOP进阶

2.1 切入点表达式

image-20231105114722421

2.3.1 execution

image-20231105114811736

注意:虽然:包名.类名可省略,一般不会省略。


通配符:可以使用通配符描述切入点,快速描述。

  • *:单个独立的任意符合,可以独立出现,也可以作为前缀或后缀的匹配符出现。

    execution (public * com.itheima.*.UserService.find*(*))
    

    匹配com.itheima包下的任意包中的UserService类或接口中所有find开头的,带有一个参数的方法。

  • ..:多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写。

    execution (public User com..UserService.findById(..))
    

    匹配com包下的任意包中的UserService类或接口中所有名为findById的方法。

  • +:专用与匹配子类类型

    execution(* *..*Service+.*(..))
    

举例:

  • 完整写法:

    image-20231105114929063

  • 省略修饰符的写法

    image-20231105115147667

  • 全部省略的写法

    image-20231105115222126

    这种写法可能会匹配到多个方法,不建议使用。

  • 基于接口的写法

    image-20231105115312595

  • 使用通配符的写法

    image-20231105115505532


书写建议:

  • 所有代码按照标准规范开发,否则以下技巧全部失效
  • 描述切入点通常描述接口,而不描述实现类
  • 访问控制修饰符针对接口开发均采用public描述(可省略访问控制修饰符描述)
  • 返回值类型对于增删改类使用精准类型加速匹配,对于查询类使用*****通配快速描述
  • 包名书写尽量不使用..匹配,效率过低,常用*做单个包描述匹配,或精准匹配
  • 接口名/类名书写名称与模块相关的采用*匹配,例如UserService书写成*Service,绑定业务层接口名
  • 方法名书写以动词进行精准匹配,名词采用*匹配,例如getByld书写成getBy*,selectAll书写成selectAl
  • 参数规则较为复杂,根据业务方法灵活调整
  • 通常不使用异常作为匹配规则

2.3.2 @annotation

image-20231105115752745

使用步骤:

  1. 自定义注解

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Log {
        String value() default "";
    }
    
  2. 切面类上

    @Component
    @Aspect
    public class MyAdvice {
        @Pointcut("@annotation(com.aopDemo.annoation.Log)")
        public void pt2(){};
        @Before("pt2()")
        public void before(){
            System.out.println("before");
        }
    
    }
    
  3. 连接点类上

    @Service
    public class aopServiceImpl implements aopService {
    
        private aopDao dao;
    
        @Autowired
        public void setDao(aopDao dao){
            this.dao=dao;
        }
    
        @Log
        @Override
        public void save() {
            dao.save();
            System.out.println("Service....save.....");
        }
    }
    
    

    这个注解只能加到实现类上,加到接口是无效的。

2.2 通知类型

image-20231105110956594


重点记住一下环绕通知,它与其他的通知有所区别:

  • 当用环绕通知绑定的方法有返回值的时候,我们在通知里面也需要返回这个值,如下图。

image-20240701211851625

@Around注意事项

  • 环绕通知必须依赖形参ProceedingJoinPoint才能实现对原始方法的调用,进而实现原始方法调用前后同时添加通知
  • 通知中如果未使用ProceedingJoinPoint对原始方法进行调用将跳过原始方法的执行
  • 对原始方法的调用可以不接收返回值,通知方法设置成void即可,如果接收返回值,必须设定为Object类型
  • 原始方法的返回值如果是void类型,通知方法的返回值类型可以设置成void,也可以设置成Object
  • 由于无法预知原始方法运行后是否会抛出异常,因此环绕通知方法必须抛出Throwable对象

@PointCut注解

  • 该注解的作用是将公共的切点表达式抽取出来,需要用到时引用该切点表达式即可。
    //抽取重复的切入点表达式
	@Pointcut("execution(* com.normaling.controller.*.*(..))")
    private void pt(){}
	//private的目的是为了限制只能在当前切面类中使用,如果是public则外部切面类也可复用
	//复用
    @Around("pt()")
    public Object testAops(ProceedingJoinPoint pjp) throws Throwable{
        long begin = System.currentTimeMillis();
        Object proceed = pjp.proceed();
        long end = System.currentTimeMillis();
        System.out.println(pjp.getSignature() + "耗时:" + (end - begin) + "毫秒");

        return proceed;
    }

2.3 通知顺序

当多个切面的切入点匹配到同一个目标方法,目标方法运行时,多个通知方法都会被执行,此时会存在通知的执行顺序。

image-20231105114622645

2.4 AOP通知获取数据

2.4.1 获取切入点方法的参数

  • JoinPoint:适用于前置、后置、返回后、抛出异常后通知
  • ProceedJointPoint:适用于环绕通知

image-20240701214058839

小技巧:

  • 对于@Around里面的获取到的args,我们可以手动进行更改,然后再将数值传递过去

    Object[] args=pjp.getArgs();
    args[0]=1;
    Object ret=pjp.proceed(args)
    

2.4.2 获取切入点方法返回值

image-20240701214112908

注意点:

  • 针对@AfterReturning注解,当我们返回值和JointPoint参数都要用的时候,JointPoint必须是第一个

    @AfterReturning(value="pt()",returning="ret")
    public void afterReturning(JointPoint jp,String ret){
    
    }
    

2.4.3 获取切入点方法运行异常信息

image-20240701214132119

2.5 连接点

image-20231105120002602

  • ProceedingJoinPoint

    image-20231105120021901

  • JoinPoint

    image-20231105120040060