【JavaEE进阶】——第七节.Spring AOP统一功能处理(切面、切点、连接点、通知)

作者简介:大家好,我是未央;

博客首页:未央.303

系列专栏:JavaEE进阶

每日一句:人的一生,可以有所作为的时机只有一次,那就是现在!!!

文章目录

前言

一、 AOP的前置知识

1.1 什么是AOP

1.2 AOP的作用 

二、SpringAOP的实现

2.1 添加SpringAOP框架支持

2.2 定义切面(Aspect)

2.3 定义切点(Pointcut)

2.4 定义通知(Advice)

三、实例展示(计时器)

代码实现 

总结



 

前言

今天我们将介绍有关Spring AOP统一功能处理的内容


一、 AOP的前置知识

1.1 什么是AOP

Aop是一种统一处理某一问题的思想,比如验证用户是否登录

验证用户是否登录实例分析:

在为使用Aop的时候,我们需要验证的每个类(页面)都有调用验证方法,而使用了Aop后,我们只需要在某处把验证规则配置一下,就可以实现对需要验证的类的登录验证,不用每个类在重复调用验证方法了。

Aop由切面、切点、连接点、通知组成;

切面:

切面表示我们要统一处理的功能(类)——比如验证用户是否登录;

切点:

切点则是是否进行Aop拦截的规则(哪些页面不需要进行登录验证,哪些需要,这种规则)

连接点:

连接点则是具体到哪些页面需要进行拦截(哪些类需要调用登录验证方法)

通知:

通知则是验证用户是否登录的那个具体方法实现(代码细节)——》前置通知,后置通知 ;

而AOP是一种思想,而SpringAOP是这个框架对AOP思想的实现(类似IoC和DI) 


1.2 AOP的作用 

AOP由以下四部分组成:

  1. 切面(Aspect):定义AOP业务类型(表示当前AOP是做什么的)。
  2. 连接点(Join Point):有可能调用AOP的地方就叫做一个连接点。
  3. 切点(Pointcut):定义AOP拦截规则。
  4. 通知(Advice)【增强方法】:定义什么时候干什么事(代码的实现细节,前后顺序)。

通知(Advice):

定义的是被拦截的方法具体要执行的业务,比如用户登录权限验证方法就是具体要执行的业务。

通知的分类方法:

  • a) 前置通知:在拦截的目标方法之前执行的通知(事件)
  • b)后置通知:在拦截的目标方法之后执行的通知(事件)
  • c)返回之后通知:在拦截的目标方法返回数据之后通知
  • d)抛出异常之后的通知:在拦截的目标方法抛出异常之后执行的通知
  • e)环绕通知:在拦截方法执行前后都执行的通知。

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


二、SpringAOP的实现

2.1 添加SpringAOP框架支持

在我们的项目的pom.xml中添加支持

这个时候,我们就需要借助 中央仓库了https://mvnrepository.com/ 

注意:
Spring AOP 框架, 在创建新项目的时候,搜索不到。
Spring Boot 项目中,有没有内置 AOP 框架。

这个时候,我们就需要借助 中央仓库了https://mvnrepository.com/ 

 细节拓展: Spring AOP 依赖的版本号标签是可以省略的。
虽然 Spring AOP 没有 作为一个常用框架,导致我们引入框架的时候,需要借助 Maven 中央仓库来引入。
但是!Spring Boot 里面,其实有记录 Spring AOP 的 版本关联信息、
它可根据当前项目的环境,自动引入合适版本的 Spring AOP.


2.2 定义切面(Aspect)

定义方法步骤:

【JavaEE进阶】——第七节.Spring AOP统一功能处理(切面、切点、连接点、通知)_第1张图片


【JavaEE进阶】——第七节.Spring AOP统一功能处理(切面、切点、连接点、通知)_第2张图片


2.3 定义切点(Pointcut)

【JavaEE进阶】——第七节.Spring AOP统一功能处理(切面、切点、连接点、通知)_第3张图片

其中pointcut方法为空方法,其不需要方法体,此方法名就是起到一个“标识”的作用,标识下面的通知方法具体指的是哪个切点。 

切点的语法组成:

【JavaEE进阶】——第七节.Spring AOP统一功能处理(切面、切点、连接点、通知)_第4张图片

 AspectJ支持三种通配符:

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

表达式示例如下:

  • execution(* com.example.demo.UserController. *(..)):匹配UserController类里的所有方法。
  • execution(* com.example.demo.UserController+.*(..)):匹配UserController类的子类包括该类的所有方法
  • execution(* com.example.demo.*.*(..)):匹配com.example.demo包下的所有类的所有方法
  • execution(* com.example.demo..*.*(..)):匹配com.example.demo包下、子孙包下所有类的所有方法
  • execution(* addUser(String,int)):匹配addUser方法,且第一个参数类型是String,第二个参数类型是int

2.4 定义通知(Advice)

切点和通知的关系:

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

  • 前置通知使⽤ @Before:通知⽅法会在⽬标⽅法调⽤之前执⾏。
  • 后置通知使⽤ @After:通知⽅法会在⽬标⽅法返回或者抛出异常后调⽤。
  • 返回之后通知使⽤ @AfterReturning:通知⽅法会在⽬标⽅法返回后调⽤。
  • 抛异常后通知使⽤ @AfterThrowing:通知⽅法会在⽬标⽅法抛出异常后调⽤。
  • 环绕通知使⽤ @Around:通知包裹了的⽅法(集合中的连接点),在被通知的⽅法收到通知之前和调⽤之后执⾏⾃定义的⾏为。

实现通知方法:在什么时机执行什么方法。

以前置方法为例,演示一下:


三、实例展示(计时器)

我们可以在 其值方法中 加一行代码,记录 开始时间。
然后,再在 后置方法中 记录 结束时间。
最后,两者相减,不就得到了 拦截到的方法的执行时间了嘛!
这样做,真的对吗? 是不对。

这得看情况。

如果是在单线程的环境下(同一时刻,只有一个线程在访问该方法),使用上述方式,没有问题。

但是!
在多线程的情况下,有多个用户访问 会被拦截下来的方法,每一次访问,都会调用 前置方法。
这会导致, 前置方法记录的开始时间,会不停被刷新(覆盖),最终记录的是 最后一个线程访问的时间。
后置方法,也是同样的情况。



也就是说我们最终相减的情况:
哪一次的开始时间 减去 哪一次 结束时间,我们都是无从获知的!
而且,得出非常多,数量取决访问的线程有多少。

那么,问题来了!
前面我不是说: AOP 可以统⼀⽅法执⾏时间的统计嘛。
但是,遇到问题了、
那么,我们该怎么做呢?

.有的人可能会说:这是线程安全问题,加锁呗!
对不起,不行!这就是全局的问题,你加锁也解决不了问题。
但是!我们不是剩一个 环绕通知吗?
解决的办法,就在这里。

下面,我们就来先了解一下 环绕通知。


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

环绕通知@Around:

形象来说:环绕通知,就是把 整个连接点(方法)包裹起来了,那我们就可以“为所欲为”了。

比如说:
我们执行的方法 是在当前通知里面去执行的,所以,我们就可以针对每一个方法去记录开始时间和结束时间。

因为在每一次在执行目标方法(连接点)和 通知 的时候,它们是在一块的。给人的感觉就像是具有了 事务的原子性;

代码实现 

 LoginAop代码 :

package com.example.demo.aop;
 
import com.sun.corba.se.impl.ior.OldJIDLObjectKeyTemplate;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
 
// 切面表示我们要统一出来的问题——比如验证用户是否登录(LoginAop类)
// 切点则是是否进行Aop拦截的规则(哪些页面不需要进行登录验证,哪些需要,这种规则)
// 连接点则是具体到哪些页面需要进行拦截(登录验证)
// 通知则是验证用户是否登录的那个具体方法实现(代码细节)——》前置通知,后置通知
// 验证当前是否登录 的aop实现类
@Component
@Aspect // 表示当前类是一个切面
public class LoginAop {
    // 定义切点
    @Pointcut("execution(* com.example.demo.controller.UserController.*(..))")
    public void pointcut() { // 标记切点
    }
    // 前置通知
    @Before("pointcut()") // 参数说明这个前置方法是针对与那个切点的
    public void doBefore() {
        System.out.println("前置通知");
    }
    // 后置通知
    @After("pointcut()")
    public void doAfter() {
        System.out.println("后置通知");
    }
    // 环绕通知
    @Around("pointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint) {
        StopWatch stopWatch = new StopWatch();
 
        Object o =  null;
        System.out.println("环绕通知开始执行");
        try {
            stopWatch.start();
            o = joinPoint.proceed();
            stopWatch.stop();
        } catch (Throwable e) {
            e.printStackTrace();
        }
        System.out.println("环绕通知结束执行");
        System.out.println("执行花费的时间: " + stopWatch.getTotalTimeMillis() + "ms");
        return o;
    }
}

总结

【JavaEE进阶】——第七节.Spring AOP统一功能处理(切面、切点、连接点、通知)_第5张图片

 

你可能感兴趣的:(JavaEE进阶,spring,java,JavaEE进阶)