Aop基础

AOP方法

AOP提取所有的重复方法,可以把共性的逻辑全部抽取在AOP方法里面,从而减少重复的代码。下面就统计代码逻辑执行的时间来实现一个AOP类。

创建一个AOP类

为了创建一个AOP类,需要做到下面几点

  • 加入Maven坐标

  • @Componment注解

    注解此类交给IOC容器管理

  • 加上@Aspect注解

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Component
@Slf4j
@Aspect
public class TimeAspect {

    /**
     * 统计一个方法运行所需要的时间
     * 1.指定ProceedingJoinPoint对象
     * 2.抛出异常
     * 3.返回被记录方法的返回值
     * 4.编写aop影响的表达式
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    // 第一个*表示所有的返回值
    // 中间的表示 com.itheima.service包下的所有类和接口下的所有方法
    // 最后的(..)表示方法的参数可以是任意值
    public Object time(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        long end = System.currentTimeMillis();
        long time = end - start;
        log.info(joinPoint.getSignature() + "方法占用的时间{}ms", time);
        return result;
    }
}

抽取公共逻辑

要想让方法被Aspect类中的方法逻辑掌控,就需要声明注解以及execution注解来确定这个方法的控制范围。

在创建一个AOP类后,我们可以在这个类里面编写需要执行的一些公共逻辑

  • 指定注解

    Spring中AOP的通知类型:

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

    在表达式指定的范围内的指定方法都会执行注解注释的方法。

对于上面的计算方法耗时的方式就是遵守这几个原则的:

  • 创建好AOP

    • @Aspect注释
    • @Component
  • 将需要公共执行的逻辑封装到方法内

    1
    2
    3
    4
    5
    6
    7
    8
    
    public Object time(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        long end = System.currentTimeMillis();
        long time = end - start;
        log.info(joinPoint.getSignature() + "方法占用的时间{}ms", time);
        return result;
    }
    
  • 声明该方法的作用时间和影响的方法

    1
    
    @Around("execution(* com.itheima.service.*.*(..))");
    

    这里的表达式有下面这两个格式:

    execution主要根据方法的返回值、包名、类名、方法名、方法参数等信息来匹配,语法为:

    1
    
    execution(访问修饰符?  返回值  包名.类名.?方法名(方法参数) throws 异常?)
    

    其中带?的表示可以省略的部分

    • 访问修饰符:可省略(比如: public、protected)

    • 包名.类名: 可省略

    • throws 异常:可省略(注意是方法上声明抛出的异常,不是实际抛出的异常)

    示例:

    1
    
    @Before("execution(void com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))")
    

    可以使用通配符描述切入点

    • * :单个独立的任意符号,可以通配任意返回值、包名、类名、方法名、任意类型的一个参数,也可以通配包、类、方法名的一部分

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

    注意,如果只是使用一个'.',那么不会匹配到该包下的子包,如果需要匹配子包的话要使用..

抽取公共注解

我们可以将这个类完善一下,如下面的代码所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package com.itheima.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component
@Slf4j
@Aspect
public class TimeAspect {

    /**
     * 统计一个方法运行所需要的时间
     * 1.指定ProceedingJoinPoint对象
     * 2.抛出异常
     * 3.返回被记录方法的返回值
     * 4.编写aop影响的表达式
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    // 第一个*表示所有的返回值
    // 中间的表示 com.itheima.service包下的所有类和接口下的所有方法
    // 最后的(..)表示方法的参数可以是任意值
    @Around("execution(* com.itheima.service.*.*(..))")
    public Object time(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        long end = System.currentTimeMillis();
        long time = end - start;
        log.info(joinPoint.getSignature() + "方法占用的时间{}ms", time);
        return result;
    }
    
    @Before("execution(* com.itheima.service.*.*(..))")
    public void before(JoinPoint joinPoint) {
        log.info("Before Aspect");
    }
    
    @After("execution(* com.itheima.service.*.*(..))")
    public void after(JoinPoint joinPoint) {
        log.info("After Aspect");
    }
    
    @AfterReturning("execution(* com.itheima.service.*.*(..))")
    public void afterReturning(JoinPoint joinPoint) {
        log.info("AfterReturning Aspect");
    }
    
    @AfterThrowing("execution(* com.itheima.service.*.*(..))")
    public void afterThrowing(JoinPoint joinPoint) {
        log.info("AfterThrowing Aspect");
    }
}

这里包含了所有的注解,在正常情况下,如果不发生异常,那么@AfterThrowing是不会执行的。

可以看到,这里面有大量的重复的execution表达式,如果后续改变了切入点表达式的话,就需要我们一个一个的去进行修改。

这里也提供了一个可以抽取公共表达式的方法

@PointCut

使用这个注解可以将一个表达式封装为一个新的方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package com.itheima.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component
@Slf4j
@Aspect
public class TimeAspect {


    @Pointcut("execution(* com.itheima.service.*.*(..))")
    public void pointCut() {}

    /**
     * 统计一个方法运行所需要的时间
     * 1.指定ProceedingJoinPoint对象
     * 2.抛出异常
     * 3.返回被记录方法的返回值
     * 4.编写aop影响的表达式
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    // 第一个*表示所有的返回值
    // 中间的表示 com.itheima.service包下的所有类和接口下的所有方法
    // 最后的(..)表示方法的参数可以是任意值
    @Around("pointCut()")
    public Object time(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        long end = System.currentTimeMillis();
        long time = end - start;
        log.info(joinPoint.getSignature() + "方法占用的时间{}ms", time);
        return result;
    }

    @Before("pointCut()")
    public void before(JoinPoint joinPoint) {
        log.info("Before Aspect");
    }

    @After("pointCut()")
    public void after(JoinPoint joinPoint) {
        log.info("After Aspect");
    }

    @AfterReturning("pointCut()")
    public void afterReturning(JoinPoint joinPoint) {
        log.info("AfterReturning Aspect");
    }

    @AfterThrowing("pointCut()")
    public void afterThrowing(JoinPoint joinPoint) {
        log.info("AfterThrowing Aspect");
    }
}

这样,我们只需要修改一次execution表达式就可以。

annotation注解

使用execution注解可以方便的将一群行为相同的方法抽取出来,但是当进行几个特别的方法且方法之间没有什么共同联系的时候抽取就显得比较麻烦。此时我们就可以使用自定义注解的方式去修饰我们需要抽取的方法。

  • 自定义注释

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    package com.itheima.annotation;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyLog {
    }
    
  • 将需要抽取的类加上注释

    例如,我们需要将listdelete方法的逻辑抽取在一起。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    @MyLog
    public List<Emp> list(String name, Short gender, LocalDate begin, LocalDate end);
    
    /**
     * 批量删除
     * @param ids
     */
    @MyLog
    void delete(List<Integer> ids);
    

    加上自定义注释。

  • 切面类消息中使用@annotation注释

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    @Before("@annotation(com.itheima.annotation.MyLog)")
    public void before(JoinPoint joinPoint) {
        log.info("Before Aspect");
    }
    
    @After("@annotation(com.itheima.annotation.MyLog)")
    public void after(JoinPoint joinPoint) {
        log.info("After Aspect");
    }
    

    @annotation()里面可以传递我们自定义的注解,从而影响到被注解注解的方法。

连接点

在Spring中用JoinPoint抽象了连接点,用它可以获得方法执行时的相关信息,如目标类名、方法名、方法参数等。

  • 对于@Around通知,获取连接点信息只能使用ProceedingJoinPoint类型

  • 对于其他四种通知,获取连接点信息只能使用JoinPoint,它是ProceedingJoinPoint的父类型

通过连接点的方式可以获得方法的一些字段属性。

总结

这就是AOP的所有涉及到的知识~

Licensed under CC BY-NC-SA 4.0
花有重开日,人无再少年
使用 Hugo 构建
主题 StackJimmy 设计