Spring AOP

简介: Spring AOP

一、简介Spring AOP

APO指的是面向切面编程,与OOP(面向对象)类似,是对某一类事情的集中处理。那么Spring AOP就是这个思想的具体实现。

例如对用户的登录权限进行校验,在没有AOP之前就需要在判断用户是否是登录状态的页面都需要实现或者调用验证用户登录的方法,但是在使用AOP之后,就只需要在某一处进行配置,所有判断用户是否是登录状态的页面就全都可以实现用户登录验证了。

二、Spring AOP的相关概念

切面:定义AOP处理的统一功能,这个功能就叫做切面,比如验证用户登录的功能就可以成为一个切面,切面由切点和通知组成。

连接点:触发AOP(拦截方法)的点就可以称之为连接点。

切点:定义AOP的拦截规则。

通知:规定AOP的执行时机和执行方法,通知可以有前置通知、后置通知、抛出异常后通知、返回数据后通知以及环绕通知

通俗点来讲:切面就是设想一个大型活动,那么切点就是活动策划书,通知就是活动的具体执行,连接点就是触发活动策划中的一些事件。

三、Spring AOP的具体实现

实现Spring AOP具体可以分为如下几步:

  1. 在项目中添加Spring AOP框架;
  2. 定义切面;
  3. 定义切点;
  4. 实现通知。

添加Spring AOP框架

Spring Boot框架中并没有Spring AOP框架 ,需要去maven中心库进行引入与自己项目版本相匹配的依赖。

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
    <version>3.0.5</version>
</dependency>

定义切面

定义切面时就需要创建一个类,添加@Aspect注解表示该类是一个切面。

例如定义一个UserAspect类:

@Aspect //表示当前类是一个切面
@Component
public class UserAspect {
    
}

定义切点

切点是切面中的一个方法,需要使用@PointCut注解,里面需要传入一个AspectJ表达式,AspectJ是一个第三方库,但是由于比较好用,SpringAOP也是兼容的。

如下为AspectJ表达式:

在AspectJ表达式中也可以使用如下的通配符:

  • *:可以匹配任意的内容,可以使用在返回值、包名、类名以及方法名。
  • ..:匹配任意字符,常用在方法的参数中,若在类上使用就需要配合*来使用。
  • +:用于匹配指定类及其所有的子类。

例如,在UserAspect切面中定义如下的切点:

    @Pointcut("execution(* com.example.spring_aop.controller.UserController.*(..))")
    public void pointCut(){}

实现通知

在controller包下定义UserController类,并定义sayHi方法:

@RestController
@RequestMapping("/user")
public class UserController {
    @RequestMapping("/sayhi")
    public String  sayHi(){
        return "hi!";
    }
}

前置通知

表示在执行目标方法之前执行该方法,使用@Before注解,其中需要传入的参数是切点的方法名。

@Before("pointCut()")
    public void doBefore(){
        System.out.println("前置方法已经执行");
    }

那么访问UserController的sayhi页面:

并且前置方法也被执行了:

后置通知

在执行目标方法之后执行该方法,使用@After注解,同样需要传入切点的方法名。

@After("pointCut()")
public void doAfter(){
    System.out.println("后置方法已经执行");
}

那么再次访问sayhi页面就会有如下:

 

返回数据后通知

在目标方法返回数据后执行该方法,如果没有返回数据该方法就不会执行,该方法的执行通常是在后置通知之前执行,使用@AfterReturning注解,传入的参数同样也是切点的方法名。

那么再次访问sayhi页面之后就出现如下所示:

抛出异常后通知

在目标方法抛出异常后执行该方法,如果没有抛出异常该方法就不会执行,使用@AfterThrowing注解,传入的参数同样也是切点的方法名。

例如将sayhi方法修改如下:

@RequestMapping("/sayhi")
    public String  sayHi() throws Exception{
        int num = 1/0;
        return "hi!";
    }

那么再次对sayhi进行访问时就会出现如下:

由于在返回数据之前就已经抛出异常了就没有正常返回数据,因此就不会执行返回数据后的通知。

环绕通知

包裹了被通知的方法,在被通知方法通知之前和调用之后使用自定义方法,使用@Around注解,传入的参数同样也是切点的方法名。


该通知相比之前的通知较为复杂,返回值是Object,传入的参数是ProceedingJoinPoint对象,其中方法体中调用该对象的proceed()方法。

@Around("pointCut()")
public Object doAround(ProceedingJoinPoint joinPoint){
        Object result = null;
        System.out.println("环绕通知开始");
        try {
            result = joinPoint.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
        }
        System.out.println("环绕通知结束");
        return result;
    }

对sayhi进行访问时就会出现如下所示:

那么利用环绕通知可以求目标方法的执行时间:

@Around("pointCut()")
    public Object doAround(ProceedingJoinPoint joinPoint){
        Object result = null;
        System.out.println("环绕通知开始");
        try {
            long start = System.currentTimeMillis();
            result = joinPoint.proceed();
            long end = System.currentTimeMillis();
            System.out.println(joinPoint.getSignature().getName()+"方法的执行时间:"+(end-start)+"ms");
        } catch (Throwable e) {
            e.printStackTrace();
        }
        System.out.println("环绕通知结束");
        return result;
    }

运行结果:

但是这样写只适合单线程的情况,可以在环绕方法中定义一个StopWath对象, 调用其start方法以及stop方法来计算目标方法的运行时间。

@Around("pointCut()")
    public Object doAround(ProceedingJoinPoint joinPoint){
        Object result = null;
        System.out.println("环绕通知开始");
        StopWatch stopWatch = new StopWatch();
        try {
            stopWatch.start();
            result = joinPoint.proceed();
            stopWatch.stop();
            System.out.println(joinPoint.getSignature().getName()+"方法的执行时间:"+stopWatch.getTotalTimeMillis()+"ms");
        } catch (Throwable e) {
            e.printStackTrace();
        }
        System.out.println("环绕通知结束");
        return result;
    }

四、Spring AOP的实现原理

不引入AOP,就是前端和后端直接进行交互,但是在引入AOP之后,AOP就是中间的代理商,进行动态代理,Spring对AOP仅支持方法级别的拦截,AOP在运行期开始代理

Spring AOP ⽀持 JDK Proxy 和 CGLIB ⽅式实现动态代理:

JDK Proxy(JDK动态代理);

GGLIB Proxy:默认情况下使用GGLIB Proxy进行动态代理,GGLIB Proxy通过继承代理对象来实现动态代理,所以GGLIB Proxy不能代理被final修饰的类,就需要使用JDK Proxy进行代理。

目录
相关文章
|
3天前
|
Java
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
这篇文章是Spring5框架的实战教程,深入讲解了AOP的基本概念、如何利用动态代理实现AOP,特别是通过JDK动态代理机制在不修改源代码的情况下为业务逻辑添加新功能,降低代码耦合度,并通过具体代码示例演示了JDK动态代理的实现过程。
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
|
1月前
|
Java Spring
在Spring Boot中使用AOP实现日志切面
在Spring Boot中使用AOP实现日志切面
|
3天前
|
XML Java 数据格式
Spring5入门到实战------11、使用XML方式实现AOP切面编程。具体代码+讲解
这篇文章是Spring5框架的AOP切面编程教程,通过XML配置方式,详细讲解了如何创建被增强类和增强类,如何在Spring配置文件中定义切入点和切面,以及如何将增强逻辑应用到具体方法上。文章通过具体的代码示例和测试结果,展示了使用XML配置实现AOP的过程,并强调了虽然注解开发更为便捷,但掌握XML配置也是非常重要的。
Spring5入门到实战------11、使用XML方式实现AOP切面编程。具体代码+讲解
|
6天前
|
安全 Java 开发者
Java 新手入门:Spring 两大利器IoC 和 AOP,小白也能轻松理解!
Java 新手入门:Spring 两大利器IoC 和 AOP,小白也能轻松理解!
14 1
|
6天前
|
Java Spring
Spring的AOP组件详解
该文章主要介绍了Spring AOP(面向切面编程)组件的实现原理,包括Spring AOP的基础概念、动态代理模式、AOP组件的实现以及Spring选择JDK动态代理或CGLIB动态代理的依据。
Spring的AOP组件详解
|
18天前
|
Java API Spring
Spring Boot 中的 AOP 处理
对 Spring Boot 中的切面 AOP 做了详细的讲解,主要介绍了 Spring Boot 中 AOP 的引入,常用注解的使用,参数的使用,以及常用 api 的介绍。AOP 在实际项目中很有用,对切面方法执行前后都可以根据具体的业务,做相应的预处理或者增强处理,同时也可以用作异常捕获处理,可以根据具体业务场景,合理去使用 AOP。
|
27天前
|
Java Spring 容器
Spring问题之Spring AOP是如何实现面向切面编程的
Spring问题之Spring AOP是如何实现面向切面编程的
|
23天前
|
缓存 安全 Java
Spring高手之路21——深入剖析Spring AOP代理对象的创建
本文详细介绍了Spring AOP代理对象的创建过程,分为三个核心步骤:判断是否增强、匹配增强器和创建代理对象。通过源码分析和时序图展示,深入剖析了Spring AOP的工作原理,帮助读者全面理解Spring AOP代理对象的生成机制及其实现细节。
16 0
Spring高手之路21——深入剖析Spring AOP代理对象的创建
|
3天前
|
XML Java 数据库
Spring5入门到实战------10、操作术语解释--Aspectj注解开发实例。AOP切面编程的实际应用
这篇文章是Spring5框架的实战教程,详细解释了AOP的关键术语,包括连接点、切入点、通知、切面,并展示了如何使用AspectJ注解来开发AOP实例,包括切入点表达式的编写、增强方法的配置、代理对象的创建和优先级设置,以及如何通过注解方式实现完全的AOP配置。
|
1月前
|
Java Spring
在Spring Boot中使用AOP实现日志切面
在Spring Boot中使用AOP实现日志切面