学习一件新知识,我们首先要先了解它的概念;AOP(Aspect Oriented Programming):面向切面编程,它是⼀种思想,它是对某⼀类事情的集中处理。比如用户登录权限的校验,没有 AOP 之前,我们在所有需要判断用户登录的页面都进行调用用户验证的方法,而有了 AOP 之后,我们只需在一处配置好 AOP 规则,需要判断用户登录页面(还要接口)全部可以实现自主的登录验证,不再需要每次调用验证登录的方法,不但解决了每次调用验证方法的问题,还降低了代码的耦合性。
通俗的说,AOP就像一张网,会对某一个过程进行筛选,通过筛选的才能进行规定的操作,不通过的则会按照不通过的规则操作。
当然 AOP 是一种思想,而 Spring AOP 是一个框架,提供了 这个思想的实现方式。
还举上方那个例子,我们在处理用户登录验证时,每次验证都需要调用处理方法,随着我们项目的功能越来越多,而这些方法又相同,这么多的方法就会在代码修改和维护上使成本变高,对于这个问题,我们的处理方案就是:对功能统一的,且使用的地方较多的功能,就考虑使用 AOP 来统一处理。
AOP 可以实现的统一处理如下:
也就是说使⽤ AOP 可以扩充多个对象的某个能⼒,所以 AOP 可以说是 OOP(Object Oriented Programming,⾯向对象编程)的补充和完善。
Spring AOP 学习主要分为以下 3 个部分:
切⾯(Aspect)由切点(Pointcut)和通知(Advice)组成,它既包含了横切逻辑的定义,也包括了连接点的定义。通俗的说切面就是一个处理某个方面具体问题的类,类里包含了很多方法,这些方法就是通知、切点。
应⽤执行过程中能够插⼊切面的⼀个点,这个点可以是⽅法调⽤时,抛出异常时,甚⾄修改字段时。切⾯代码可以利⽤这些点插⼊到应⽤的正常流程之中,并添加新的行为。
切点 的作⽤就是提供⼀组规则(使⽤ AspectJ pointcut expression language 来描述)来匹配连接点,给满足规则的连接点添加通知。
切⾯也是有⽬标的 ——它必须完成的⼯作。在 AOP 术语中,切⾯的⼯作被称之为通知。
通知:程序被拦截请求触发后的具体动作。
Spring 切面类中,可以在方法上使用一下注解,去设置方法为通知方法,在满足条件后会通知本方法进行调用:
AOP 整个组成部分的概念如下图所示,以多个⻚⾯都要访问⽤户登录权限为例:
没有使用AOP前,我们不调用登录验证方法,他就不会对用户登录进行验证,使用AOP后,当用户要使用某个需要登陆才有的功能时,AOP 就是根据配置好的文件自动验证是否登陆。
使用Spring AOP 来实现⼀下 AOP 的功能,完成的⽬标是拦截所有 UserController 里面的⽅法,每次调⽤ UserController 中任意⼀个⽅法时,都执⾏相应的通知事件。
Spring AOP 的实现步骤如下:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aopartifactId>
dependency>
@Slf4j
@Component
@Aspect
public class MYAspect {
@Pointcut("execution(* com.example.demo.controller.*.*(..))")
public void put() {
}
@Before("put()")
public void before() {
log.error("方法被调用了");
}
}
execution(* com.zzq.service.*.*(..)) // 表示com.zzq.service 包下任意类,方法名任意,参数列表任意,返回值类型任意
execution(* com.zzq.service.*..*(..)) // 表示 com.zzq.service 包及其子包下任意类,方法名任意,参数列表任意,返回值类型任意
execution(* com.zzq.service.*.*()) // 表示 com.zzq.service 包下任意类,方法名任意,要求方法不能有参数,返回值类型任意
execution(* com.zzq.service.*.delete*(..)) // 表示 com.zzq.service 包下任意类,要求方法不能有参数,返回值类型任意,方法名要求以 delete 开头
@Before("put()")
public void before() {
log.error("方法被调用了");
}
当我们调用 com.example.demo.controller 中的方法时,就会先执行通知里面的东西,再执行方法,如下图(这里为了能看的更清楚,我们用了日子打印信息)
@Slf4j
@Component
@Aspect
public class MYAspect {
@Pointcut("@annotation(com.example.demo.aspect.InvokeLog)")
public void put() {
}
@Before("put()")
public void before() {
log.error("方法被调用了");
}
}
@Pointcut("@annotation(com.example.demo.aspect.InvokeLog)")
public void put() {
}
@Before("put()")
public void before() {
log.error("方法被调用了");
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface InvokeLog {
}
那个方法需要增强时,我们就在这个方法上面加上这个注解,如下图:
加过注解的方法,就会得到增强,也就是执行通知里面的代码,如下图:
我们是在通知的参数列表当中添加一个参数 JoinPoint joinPoint
这个参数可以帮我们拿到被增强方法相关的数据
@Before("put()")
public void before(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs(); // 获取参数列表中的参数
Object target = joinPoint.getTarget(); // 获取代理对象
MethodSignature signature = (MethodSignature) joinPoint.getSignature(); // 获取被增强的方法签名封装的对象
Method method = signature.getMethod(); // 获取被增强的方法
log.error("方法被调用了");
}