14. Spring AOP 的组成和实现

目录

1. Spring AOP 简介

2. AOP 的组成

2.1 切面(Aspect)

2.2 连接点(Join Point)

2.3 切点(Pointcut)

2.4 通知(Advice)

3. Spring AOP的实现

3.1 新建项目

3.2 添加 AOP 框架支持 

3.3 定义切面、切点和通知

4. 切点表达式说明

5. 练习:使用 AOP 统计 UserController 每个方法的执行时间


1. Spring AOP 简介

AOP 是一种思想,Spring AOP 是这种思想的具体实现。

OOP:面向对象编程

AOP:面向切面编程

AOP 面向切面编程,就是对某一类事情的集中处理。

比如,我们需要在 CSDN 上进行编辑博客、发布博客、删除博客等操作,这些功能都是需要进行权限校验的,判断是否登录。

开发三阶段

对于公共方法的处理:

  1. (初级阶段)每个方法都去实现
  2. (中级阶段)把同一类功能抽取成公共方法
  3. (高级阶段)采用 AOP 的方式,对代码无侵入实现

除了统⼀的用户登录判断之外,AOP 还可以实现:

  • 统⼀日志记录
  • 统一方法执行时间统计
  • 统一的返回格式设置
  • 统一的异常处理
  • 事务的开启和提交等
统一方法执行时间统计 项目监控:监控项目请求流量、监控接口的响应时间甚至每个方法的响应时间
统一的返回格式设置

httpstatus: HTTP状态码

code: 业务状态码(后端响应成功不代表业务办理成功) 

msg: 业务处理失败返回的信息

data: 

也就是说使用 AOP 可以扩充多个对象的某个能力,所以 AOP 可以说是 OOP(Object Oriented Programming,面向对象编程)的补充和完善

2. AOP 的组成

2.1 切面(Aspect)

切面(Aspect)由切点(Pointcut)和通知(Advice)组成,它既包含了横切逻辑的定义,也包括了连接点的定义。

切面是包含了:通知、切点和切面的类,相当于 AOP 实现的某个功能的集合。

2.2 连接点(Join Point)

应⽤执行过程中能够插入切面的⼀个点,这个点可以是方法调用时、抛出异常时,甚至修改字段 时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。

连接点相当于需要被增强的某个 AOP 功能的所有方法。

2.3 切点(Pointcut)

Pointcut 是匹配 Join Point 的谓词。 Pointcut 的作用就是提供⼀组规则(使用 AspectJ pointcut expression language 来描述)来匹配 Join Point,给满足规则的 Join Point 添加 Advice。

切点相当于保存了众多连接点的一个集合(如果把切点看成一个表,而连接点就是表中⼀条⼀条 的数据)。

2.4 通知(Advice)

切面也是有目标的 ——它必须完成的工作。在 AOP 术语中,切面的工作被称之为通知

Spring 切面类中,可以在方法上使用以下注解,会设置方法为通知方法,在满足条件后会通知本 方法进行调用:

  • 前置通知使用 @Before:通知方法会在目标方法调用之前执行。
  • 后置通知使用 @After:通知方法会在目标方法返回或者抛出异常后调用。
  • 返回之后通知使用 @AfterReturning:通知方法会在目标方法返回后调用。
  • 抛异常后通知使用 @AfterThrowing:通知方法会在目标方法抛出异常后调用。
  • 环绕通知使用户 @Around:通知包裹了被通知的方法,在被通知的方法通知之前和调用之后执行自定义的行为。

切点相当于要增强的方法。 

AOP 整个组成部分的概念如下图所示,以多个页面都要访问用户登录权限为例:

14. Spring AOP 的组成和实现_第1张图片

既然说 AOP 是对一类事情的集中处理,那么我们就需要明确两点:

  1.  一类事情:处理对象的一个范围
  2.  集中处理:处理的内容是什么

我们通过生活中的一个例子来看一下:

比如,我们乘坐高铁需要安检

那么,我们需要处理的内容就是安检;处理的范围就是需要乘坐高铁的人。

此处乘坐高铁需要安检这件事情就是切面,处理的内容安检就是通知,处理的范围乘坐高铁的人就是切点,具体有哪些人就是连接点

切点是一个规则,事情的处理,最终作用在方法上。 

3. Spring AOP的实现

3.1 新建项目

14. Spring AOP 的组成和实现_第2张图片

14. Spring AOP 的组成和实现_第3张图片

14. Spring AOP 的组成和实现_第4张图片

3.2 添加 AOP 框架支持 

在 pom.xml 中添加如下配置:


    org.springframework.boot
    spring-boot-starter-aop

3.3 定义切面、切点和通知

 我们先定义 UserController 类:

@RequestMapping("/user")
@RestController
public class UserController {
    // 获取用户信息
    @RequestMapping("/getInfo")
    public String getInfo(){
        return "get info...";
    }
    // 注册
    @RequestMapping("/reg")
    public String reg(){
        return "reg...";
    }
    // Login
    @RequestMapping("/login")
    public String login(){
        return "login...";
    }
}

运行后,成功访问: 

14. Spring AOP 的组成和实现_第5张图片

14. Spring AOP 的组成和实现_第6张图片

14. Spring AOP 的组成和实现_第7张图片

接下来,我们在 UserController 类中定义切面和切点: 

@Slf4j
@RequestMapping("/user")
@RestController
public class UserController {
    // 获取用户信息
    @RequestMapping("/getInfo")
    public String getInfo(){
        log.info("get info...");
        return "get info...";
    }
    // 注册
    @RequestMapping("/reg")
    public String reg(){
        log.info("reg...");
        return "reg...";
    }
    // Login
    @RequestMapping("/login")
    public String login(){
        log.info("login...");
        return "login...";
    }
}

在 LoginAspect 类中使用 @Before 注解(通知方法会在目标方法调用之前执行): 

@Slf4j
@Component
@Aspect
public class LoginAspect {
    @Pointcut("execution(* com.example.demo.controller.UserController.* (..))")
    public void pointcut(){}

    @Before("pointcut()")
    public void doBefore(){
        log.info("do berore...");
    }
}

14. Spring AOP 的组成和实现_第8张图片

我们接着新建一个 TestController 类:

@Slf4j
@RequestMapping("/test")
@RestController
public class TestController {
    @RequestMapping("/hi")
    public String hi(){
        log.info("hi~");
        return "hi~";
    }
}

可以看到运行的结果中,并没有在控制台打印 @Before 中的内容: 

14. Spring AOP 的组成和实现_第9张图片

那么为什么没有执行呢?

14. Spring AOP 的组成和实现_第10张图片

我们再来看一下其他注解,@After(通知方法会在目标方法返回或者抛出异常后调用

@Slf4j
@Component
@Aspect
public class LoginAspect {
    @Pointcut("execution(* com.example.demo.controller.UserController.* (..))")
    public void pointcut(){}

    @Before("pointcut()")
    public void doBefore(){
        log.info("do berore...");
    }
    @After("pointcut()")
    public void doAfter(){
        log.info("do after...");
    }
}

运行结果如下:

14. Spring AOP 的组成和实现_第11张图片

@AfterReturning(通知方法会在目标方法返回后调用)

@Slf4j
@Component
@Aspect
public class LoginAspect {
    @Pointcut("execution(* com.example.demo.controller.UserController.* (..))")
    public void pointcut(){}

    @Before("pointcut()")
    public void doBefore(){
        log.info("do berore...");
    }
    @After("pointcut()")
    public void doAfter(){
        log.info("do after...");
    }
    @AfterReturning("pointcut()")
    public void doAfterReturning(){
        log.info("do after returning...");
    }
}

运行以上代码后: 

可以看到 :@AfterReturning 在 @After 之前被调用。

@AfterThrowing(通知方法会在目标方法抛出异常后调用)

我们首先在 UserController 类中加入异常:

@Slf4j
@RequestMapping("/user")
@RestController
public class UserController {
    // 获取用户信息
    @RequestMapping("/getInfo")
    public String getInfo(){
        log.info("get info...");
        return "get info...";
    }
    // 注册
    @RequestMapping("/reg")
    public String reg(){
        log.info("reg...");
        int a = 10/0;
        return "reg...";
    }
    // Login
    @RequestMapping("/login")
    public String login(){
        log.info("login...");
        return "login...";
    }
}

添加 @AfterThrowing 注解:

@Slf4j
@Component
@Aspect
public class LoginAspect {
    @Pointcut("execution(* com.example.demo.controller.UserController.* (..))")
    public void pointcut(){}

    @Before("pointcut()")
    public void doBefore(){
        log.info("do berore...");
    }
    @After("pointcut()")
    public void doAfter(){
        log.info("do after...");
    }
    @AfterReturning("pointcut()")
    public void doAfterReturning(){
        log.info("do after returning...");
    }
    @AfterThrowing("pointcut()")
    public void doAfterThrowing(){
        log.info("do after throwing...");
    }
}

运行后可以看到: 

14. Spring AOP 的组成和实现_第12张图片

当正常返回时,执行 @AfterReturning 注解,当出现异常时,不会执行 @AfterReturning 注解;

当出现异常时,才会执行 @AfterThrowing 注解,当正常返回时,不会执行 @AfterThrowing 注解。

@Around(通知包裹了被通知的方法,在被通知的方法通知之前和调用之后执行自定义的行为):

添加  @Around 注解:

@Slf4j
@Component
@Aspect
public class LoginAspect {
    @Pointcut("execution(* com.example.demo.controller.UserController.* (..))")
    public void pointcut(){}

    @Before("pointcut()")
    public void doBefore(){
        log.info("do berore...");
    }
    @After("pointcut()")
    public void doAfter(){
        log.info("do after...");
    }
    @AfterReturning("pointcut()")
    public void doAfterReturning(){
        log.info("do after returning...");
    }
    @AfterThrowing("pointcut()")
    public void doAfterThrowing(){
        log.info("do after throwing...");
    }
    @Around("pointcut()")
    public void doAround(ProceedingJoinPoint joinPoint){
        log.info("环绕通知执行之前...");
        try {
            joinPoint.proceed(); // 调用目标方法
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
        log.info("环绕通知执行之后...");
    }
}

运行后界面显示如下:

14. Spring AOP 的组成和实现_第13张图片

可以看到此时界面中不再有返回值,因此修改代码如下:

@Around("pointcut()")
public Object doAround(ProceedingJoinPoint joinPoint){
    Object oj = null;
    log.info("环绕通知执行之前...");
    try {
        oj = joinPoint.proceed(); // 调用目标方法
    } catch (Throwable e) {
        throw new RuntimeException(e);
    }
    log.info("环绕通知执行之后...");
    return oj;
}

此时可以看到成功返回并打印了值: 

14. Spring AOP 的组成和实现_第14张图片

我们再来看一下这段代码:

14. Spring AOP 的组成和实现_第15张图片

4. 切点表达式说明

AspectJ 支持三种通配符

  • * :匹配任意字符,只匹配一个元素(包,类,或方法,方法参数)
  • .. :匹配任意字符,可以匹配多个元素 ,在表示类时,必须和 * 联合使用。
  • + :表示按照类型匹配指定类的所有类,必须跟在类名后面,如 com.cad.Car+ ,表示继承该类的 所有子类包括本身

切点表达式由切点函数组成,其中 execution() 是最常用的切点函数,用来匹配方法,语法为: 

execution(<修饰符><返回类型><包.类.方法(参数)><异常>)

14. Spring AOP 的组成和实现_第16张图片

5. 练习:使用 AOP 统计 UserController 每个方法的执行时间

@Slf4j
@Component
@Aspect
public class LoginAspect {
    @Pointcut("execution(* com.example.demo.controller.UserController.* (..))")
    public void pointcut(){}

    @Before("pointcut()")
    public void doBefore(){
        log.info("do berore...");
    }
    @After("pointcut()")
    public void doAfter(){
        log.info("do after...");
    }
    @AfterReturning("pointcut()")
    public void doAfterReturning(){
        log.info("do after returning...");
    }
    @AfterThrowing("pointcut()")
    public void doAfterThrowing(){
        log.info("do after throwing...");
    }
    @Around("pointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint){
        Object oj = null;
        log.info("环绕通知执行之前...");
        try {
            oj = joinPoint.proceed(); // 调用目标方法
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
        log.info("环绕通知执行之后...");
        return oj;
    }

    /**
     *
     * @param joinPoint 使用 AOP 统计 UserController 每个方法的执行时间
     * @return
     */
    @Around("pointcut()")
    public Object doAroundCount(ProceedingJoinPoint joinPoint){
        Object oj = null;
        long start = System.currentTimeMillis();
        try {
            oj = joinPoint.proceed(); // 调用目标方法
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
        log.info(joinPoint.getSignature().toString()+"耗时:"+(System.currentTimeMillis()-start));
        return oj;
    }
}

可以看到不同的方法直接在 url 中进行更改重新运行界面即可获得:

14. Spring AOP 的组成和实现_第17张图片

 

你可能感兴趣的:(java,ee,spring,java,后端)