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动态代理实现
|
16天前
|
存储 缓存 Java
Spring高手之路23——AOP触发机制与代理逻辑的执行
本篇文章深入解析了Spring AOP代理的触发机制和执行流程,从源码角度详细讲解了Bean如何被AOP代理,包括代理对象的创建、配置与执行逻辑,帮助读者全面掌握Spring AOP的核心技术。
27 3
Spring高手之路23——AOP触发机制与代理逻辑的执行
|
1天前
|
Java Spring
[Spring]aop的配置与使用
本文介绍了AOP(面向切面编程)的基本概念和核心思想。AOP是Spring框架的核心功能之一,通过动态代理在不修改原代码的情况下注入新功能。文章详细解释了连接点、切入点、通知、切面等关键概念,并列举了前置通知、后置通知、最终通知、异常通知和环绕通知五种通知类型。
|
2月前
|
设计模式 Java 测试技术
spring复习04,静态代理动态代理,AOP
这篇文章讲解了Java代理模式的相关知识,包括静态代理和动态代理(JDK动态代理和CGLIB),以及AOP(面向切面编程)的概念和在Spring框架中的应用。文章还提供了详细的示例代码,演示了如何使用Spring AOP进行方法增强和代理对象的创建。
spring复习04,静态代理动态代理,AOP
|
26天前
|
Java 编译器 Spring
Spring AOP 和 AspectJ 的区别
Spring AOP和AspectJ AOP都是面向切面编程(AOP)的实现,但它们在实现方式、灵活性、依赖性、性能和使用场景等方面存在显著区别。‌
44 2
|
1月前
|
Java Spring 容器
Spring IOC、AOP与事务管理底层原理及源码解析
【10月更文挑战第1天】Spring框架以其强大的控制反转(IOC)和面向切面编程(AOP)功能,成为Java企业级开发中的首选框架。本文将深入探讨Spring IOC和AOP的底层原理,并通过源码解析来揭示其实现机制。同时,我们还将探讨Spring事务管理的核心原理,并给出相应的源码示例。
117 9
|
26天前
|
XML Java 数据格式
Spring的IOC和AOP
Spring的IOC和AOP
42 0
|
2月前
|
Java 数据库连接 数据库
Spring基础3——AOP,事务管理
AOP简介、入门案例、工作流程、切入点表达式、环绕通知、通知获取参数或返回值或异常、事务管理
Spring基础3——AOP,事务管理
|
3月前
|
XML Java 数据格式
Spring5入门到实战------11、使用XML方式实现AOP切面编程。具体代码+讲解
这篇文章是Spring5框架的AOP切面编程教程,通过XML配置方式,详细讲解了如何创建被增强类和增强类,如何在Spring配置文件中定义切入点和切面,以及如何将增强逻辑应用到具体方法上。文章通过具体的代码示例和测试结果,展示了使用XML配置实现AOP的过程,并强调了虽然注解开发更为便捷,但掌握XML配置也是非常重要的。
Spring5入门到实战------11、使用XML方式实现AOP切面编程。具体代码+讲解
|
3月前
|
缓存 Java 开发者
Spring高手之路22——AOP切面类的封装与解析
本篇文章深入解析了Spring AOP的工作机制,包括Advisor和TargetSource的构建与作用。通过详尽的源码分析和实际案例,帮助开发者全面理解AOP的核心技术,提升在实际项目中的应用能力。
41 0
Spring高手之路22——AOP切面类的封装与解析