SpringBoot:切面AOP权限校验:实例演示与注解全解

1 理解AOP1.1 什么是AOPAOP(Aspect Oriented Programming) , 面向切面思想 , 是Spring的三大核心思想之一(另外两个:IOC-控制反转、DI-依赖注入) 。
那么AOP为何那么重要呢?在我们的程序中 , 经常存在一些系统性的需求 , 比如权限校验、日志记录、统计等 , 这些代码会散落穿插在各个业务逻辑中 , 非常冗余且不利于维护 。 例如下面这个示意图:
SpringBoot:切面AOP权限校验:实例演示与注解全解文章插图
有多少业务操作 , 就要写多少重复的校验和日志记录代码 , 这显然是无法接受的 。 当然 , 用面向对象的思想 , 我们可以把这些重复的代码抽离出来 , 写成公共方法 , 就是下面这样:
SpringBoot:切面AOP权限校验:实例演示与注解全解文章插图
这样 , 代码冗余和可维护性的问题得到了解决 , 但每个业务方法中依然要依次手动调用这些公共方法 , 也是略显繁琐 。 有没有更好的方式呢?有的 , 那就是AOP , AOP将权限校验、日志记录等非业务代码完全提取出来 , 与业务代码分离 , 并寻找节点切入业务代码中:
SpringBoot:切面AOP权限校验:实例演示与注解全解文章插图
1.2 AOP体系与概念简单地去理解 , 其实AOP要做三类事:

  • 在哪里切入 , 也就是权限校验等非业务操作在哪些业务代码中执行 。
  • 在什么时候切入 , 是业务代码执行前还是执行后 。
  • 切入后做什么事 , 比如做权限校验、日志记录等 。
因此 , AOP的体系可以梳理为下图:
SpringBoot:切面AOP权限校验:实例演示与注解全解文章插图
一些概念详解:
  • Pointcut:切点 , 决定处理如权限校验、日志记录等在何处切入业务代码中(即织入切面) 。 切点分为execution方式和annotation方式 。 前者可以用路径表达式指定哪些类织入切面 , 后者可以指定被哪些注解修饰的代码织入切面 。
  • Advice:处理 , 包括处理时机和处理内容 。 处理内容就是要做什么事 , 比如校验权限和记录日志 。 处理时机就是在什么时机执行处理内容 , 分为前置处理(即业务代码执行前)、后置处理(业务代码执行后)等 。
  • Aspect:切面 , 即Pointcut和Advice 。
  • Joint point:连接点 , 是程序执行的一个点 。 例如 , 一个方法的执行或者一个异常的处理 。 在 Spring AOP 中 , 一个连接点总是代表一个方法执行 。
  • Weaving:织入 , 就是通过动态代理 , 在目标对象方法中执行处理内容的过程 。
网络上有张图 , 我觉得非常传神 , 贴在这里供大家观详:
SpringBoot:切面AOP权限校验:实例演示与注解全解文章插图
2 AOP实例实践出真知 , 接下来我们就撸代码来实现一下AOP 。 完成项目已上传至:
使用 AOP , 首先需要引入 AOP 的依赖 。 参数校验:这么写参数校验(validator)就不会被劝退了~
org.springframework.bootspring-boot-starter-aop2.1 第一个实例接下来 , 我们先看一个极简的例子:所有的get请求被调用前在控制台输出一句"get请求的advice触发了" 。
具体实现如下:
  1. 创建一个AOP切面类 , 只要在类上加个 @Aspect 注解即可 。 @Aspect 注解用来描述一个切面类 , 定义切面类的时候需要打上这个注解 。 @Component 注解将该类交给 Spring 来管理 。 在这个类里实现advice:
package com.mu.demo.advice;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.aspectj.lang.annotation.Pointcut;import org.springframework.stereotype.Component;@Aspect@Componentpublic class LogAdvice {// 定义一个切点:所有被GetMapping注解修饰的方法会织入advice@Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)")private void logAdvicePointcut() {} // Before表示logAdvice将在目标方法执行前执行@Before("logAdvicePointcut()")public void logAdvice(){// 这里只是一个示例 , 你可以写任何处理逻辑System.out.println("get请求的advice触发了");}}
  1. 创建一个接口类 , 内部创建一个get请求:
package com.mu.demo.controller;import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.JSONObject;import org.springframework.web.bind.annotation.*;@RestController@RequestMapping(value = "http://kandian.youth.cn/aop")public class AopController {@GetMapping(value = "http://kandian.youth.cn/getTest")public JSONObject aopTest() {return JSON.parseObject("{\"message\":\"SUCCESS\",\"code\":200}");} @PostMapping(value = "http://kandian.youth.cn/postTest")public JSONObject aopTest2(@RequestParam("id") String id) {return JSON.parseObject("{\"message\":\"SUCCESS\",\"code\":200}");}}