Spring AOP 详解

本文介绍了SpringAOP的基本概念、优点以及在实际开发中的应用,涵盖了Aspect、JoinPoint、Pointcut、Advice等核心概念,并展示了如何在Spring中实现AOP,包括使用JDKProxy和CGLIB动态代理技术。
摘要由CSDN通过智能技术生成

3. Spring AOP

3.1 什么是 Spring AOP?

AOP(Aspect Oriented Programming):⾯向切⾯编程,它是⼀种思想,通过预编译和运行期间动态代理来实现程序功能的统一维护的一种技术,它是对某⼀类事情的集中处理。Spring AOP 是⼀个框架,是⼀种对 AOP 思想的实现,它们的关系和IoC 与 DI 类似。

面向对象编程的缺陷:

面向对象的特点是继承、多态和封装。封装就要求将功能分散到不同的对象中去称为职责分配,让不同的类设计不同的方法。这样代码就分散到一个个的类中去了,好处是降低了代码的复杂程度,使类可重用。在分散代码的同时,也增加了代码的重复性,比如说,我们在两个类中,可能都需要在每个方法中做日志,按面向对象的设计方法,我们就必须在两个类的方法中都加入日志的内容:
在这里插入图片描述
也可以将这段代码写在一个独立的类独立的方法里,然后再在这两个类中调用。但是这样一来,这两个类跟这个独立的类就有耦合了,它的改变会影响这两个类。
在这里插入图片描述

如何理解AOP的好处?

某天小明一觉醒来,发现自己光着腚穿越到三亿年前的非洲大草原,为了填饱肚子他只能天天靠捕猎一些小动物生存下去,考虑到卫生问题,需要弄熟了再吃,他把猎物先放到一边开始接着进行钻木取火,终于火苗着了,小明开始烤肉了,饱餐了一顿。每次钻木取火都很费劲,小明总是累得满头大汗才得到一点点小火星,但是没办法,原始社会只能这样。

<小明每天都要钻木取火>:
在这里插入图片描述
假如是另外一种情况:同样是某一天,小明一觉醒来发现自己穿越到了三亿年前的非洲大草原上,万幸的是他一摸兜里还揣着打火机,但是他还得打猎要不然就得饿肚子,和上次不一样的是,他不用再费劲巴拉的钻木取火了,直接“啪”一点,打火机就能冒出火苗,直接就能点火烤肉了,然后他美美地饱餐了一顿。每次需要用到火时,拿出打火机啪一点就行了。
在这里插入图片描述

类比小明的故事,当我们在做后台系统时,如果没有AOP,所有需要判断用户登录的页面中的方法都要各自实现或调用用户验证的方法,而且用户登录验证都是的相同方法,当功能越多时,要写的登录验证也就越多,代码会更臃肿,修改和维护成本也会升高。
有了AOP之后,只需要在某一处配置一下,所有需要判断用户登录页面中的方法就可以全部实现用户登录验证了,不需要每个方法中都写相同的用户登录验证了。
AOP是OOP的补充,OOP从纵上区分出一个个的类来,而AOP则从横向上向对象中加入特定的代码,使OOP由原来的二维变为三维了,由平面变成立体了。

AOP的用途:

统一日志记录,统一方法执行时间统计,统一的返回格式设置,统一的异常处理,事务的开启和提交等

3.2 AOP相关概念

在这里插入图片描述

3.2.1 Aspect

Aspect 是 包含了 Advice ,Pointcut,和 Aspect 的类,相当于AOP实现的某个功能的集合。

3.2.2 Join Point

程序执行过程中插入 Aspect 的一个点,这个点可以是方法调用时、抛出异常时、甚至修改字段时,Aspect 能够利用这些点插入到程序的正常流程中并添加新的行为。

3.2.3 Pointcut

Pointcut 提供了一组规则(这个规则使用 AspectJ pointcut expression language 描述)来匹配Join Point,给满足规则的 Join Point 添加 Advce。Pointcut 相当于保存了众多 Join Point 的一个集合,如果把 Poincut 看成一个表,而 Join Point 就是表中的一条条数据。

3.2.4 Advice

Aspect 的工作被称为 Advice。
Advice 定义了 Aspect 是什么、何时使用,描述了 Aspect 要完成的工作,还决定合适执行这个工作。

在Spring Aspect 类中,可以在方法上使用注解,使其成为 Advice 方法,在满足条件后会通知这个方法执行。

  • 前置通知:@Before,Advice方法会在目标方法调用之前执行。
  • 后置通知:@After,Advice方法会在目标方法返回或抛出异常后调用。
  • 返回之后再通知:@AfterReturning,Advice方法会在目标方法返回后调用。
  • 抛异常后通知:@AfterThrowing,Advice方法会在目标方法抛出异常后调用。
  • 环绕通知:@Aroud,Advice方法包裹了目标方法,Advice方法会在目标方法调用之前和调用之后执行。

3.3 实现Spring AOP

3.1.1 添加 AOP 框架支持

在 pom.xml 中添加如下配置:

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

3.1.2 定义 Aspect 和 Pointcut

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect // 表明此类为⼀个切⾯
@Component
public class UserAspect {
 	// 定义切点,这⾥使⽤ AspectJ 表达式语法
 	@Pointcut("execution(*com.example.demo.controller.UserController.*(..))")
 	public void pointcut(){ } // 不需要有方法体,只是起到一个标识左右,标识下面的 Advice 方法具体指的是哪个Pointcut
 
}

切点表达式:
切点表达式由切点函数组成,其中 execution() 是最常⽤的切点函数,⽤来匹配⽅法,语法为:

execution(<修饰符><返回类型><包.类.⽅法(参数)><异常>)
AspectJ ⽀持的三种通配符:

    • :匹配任意字符,只匹配⼀个元素(包,类,或⽅法,⽅法参数)
  • … :匹配任意字符,可以匹配多个元素 ,在表示类时,必须和 * 联合使⽤
    • :表示按照类型匹配指定类的所有类,必须跟在类名后⾯,如 com.cad.Car+ ,表示继承该类的
      所有⼦类包括本身
      在这里插入图片描述
      表达式示例:

execution(* com.cad.demo.User.*(..)) :匹配 User 类⾥的所有⽅法。
execution(* com.cad.demo.User+.*(..)) :匹配该类的⼦类包括该类的所有⽅法。
execution(* com.cad.*.*(..)) :匹配 com.cad 包下的所有类的所有⽅法。
execution(* com.cad..*.*(..)) :匹配 com.cad 包下、⼦孙包下所有类的所有⽅法。 execution(* >addUser(String, int)) :匹配 addUser ⽅法,且第⼀个参数类型是 String,第⼆个 参数类型是 int

具体实现:

import org.aspectj.lang.ProceedingJoinPoint; 
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class UserAspect {

    // 定义切点⽅法
    @Pointcut("execution(* com.example.demo.controller.UserController.* (..))")
    public void pointcut(){ }

    // 前置通知
    @Before("pointcut()")
    public void doBefore(){ 
        System.out.println("执⾏ Before ⽅法"); 
    }

    // 后置通知
    @After("pointcut()")
    public void doAfter(){ 
        System.out.println("执⾏ After ⽅法"); 
    }

    // return 之前通知 
    @AfterReturning("pointcut()")
    public void doAfterReturning(){ 
        System.out.println("执⾏ AfterReturning ⽅法"); 
    }

    // 抛出异常之前通知
    @AfterThrowing("pointcut()")
    public void doAfterThrowing(){ 
        System.out.println("执⾏ doAfterThrowing ⽅法"); 
    }

    // 添加环绕通知
    @Around("pointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint){ 
        Object obj = null; 
        System.out.println("Around ⽅法开始执⾏"); 
        try {
            // 执⾏拦截⽅法
           obj = joinPoint.proceed();
        } catch (Throwable throwable) { 
            throwable.printStackTrace();
        }
        System.out.println("Around ⽅法结束执⾏"); 
        return obj;
    }
}

具体案例:

3.3 Spring AOP 的实现原理:

在这里插入图片描述

Spring AOP 是构建在动态代理基础上,因此 Spring 对 AOP 的⽀持局限于⽅法级别的拦截。
Spring AOP ⽀持 JDK Proxy 和 CGLIB ⽅式实现动态代理。
默认情况下,实现了接⼝的类,使 ⽤ AOP 会基于 JDK ⽣成代理类,没有实现接⼝的类,会基于 CGLIB ⽣成代理类。

3.3.1 代理的⽣成时机:织⼊(Weaving)

织⼊是把Aspect应⽤到⽬标对象并创建新的代理对象的过程,Aspect在指定的 Join Point 被织⼊到⽬标对 象中。

在⽬标对象的⽣命周期⾥有多个点可以进⾏织⼊:

  • 编译期:切⾯在⽬标类编译时被织⼊。这种⽅式需要特殊的编译器。AspectJ的织⼊编译器就 是以这种⽅式织 Aspect 的
  • 类加载期:切⾯在⽬标类加载到JVM时被织⼊。这种⽅式需要特殊的类加载器 (ClassLoader),它可以在⽬标类被引⼊应⽤之前增强该⽬标类的字节码。AspectJ5的加载 时织⼊(load-time weaving. LTW)就⽀持以这种⽅式织⼊Aspect。
  • 运⾏期:Aspect在应⽤运⾏的某⼀时刻被织⼊。⼀般情况下,在织⼊ Aspect 时,AOP容器会为⽬标对象动态创建⼀个代理对象。SpringAOP就是以这种⽅式织⼊Aspect 的。
3.3.1.1 动态代理

此种实现在设计模式上称为动态代理模式,在实现的技术⼿段上,都是在 class 代码运⾏期,动态的织⼊字节码。
Spring 框架中的AOP,主要基于两种⽅式:JDK 及 CGLIB 的⽅式。这两种⽅式的代 理⽬标都是被代理类中的⽅法,在运⾏期动态的织⼊字节码⽣成代理类。
CGLIB是Java中的动态代理框架,主要作⽤就是根据⽬标类和⽅法,动态⽣成代理类。
Java中的动态代理框架,⼏乎都是依赖字节码框架(如 ASM,Javassist 等)实现的。
字节码框架是直接操作 class 字节码的框架。可以加载已有的class字节码⽂件信息,修改部
分信息,或动态⽣成⼀个 class。

3.3.1.1.1 JDK 动态代理实现

JDK 实现时,先通过实现 InvocationHandler 接⼝创建⽅法调⽤处理器,再通过 Proxy 来创建代理类。

import org.example.demo.service.AliPayService; 
import org.example.demo.service.PayService;

import java.lang.reflect.InvocationHandler; 
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

//动态代理:使⽤JDK提供的api(InvocationHandler、Proxy实现),此种⽅式实现,要求被
代理类必须实现接⼝
public class PayServiceJDKInvocationHandler implements InvocationHandler {

    //⽬标对象即就是被代理对象 
    private Object target;

    public PayServiceJDKInvocationHandler( Object target) { 
        this.target = target;
    }

    //proxy代理对象
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throw s Throwable {
        //1.安全检查
        System.out.println("安全检查");
        //2.记录⽇志
        System.out.println("记录⽇志");
        //3.时间统计开始
        System.out.println("记录开始时间");

        //通过反射调⽤被代理类的⽅法
        Object retVal = method.invoke(target, args);

        //4.时间统计结束 
        System.out.println("记录结束时间"); 
        return retVal;
    }

    public static void main(String[] args) {

        PayService target=  new AliPayService(); 
        //⽅法调⽤处理器
        InvocationHandler handler =
            new PayServiceJDKInvocationHandler(target); 
        //创建⼀个代理类:通过被代理类、被代理实现的接⼝、⽅法调⽤处理器来创建 
        PayService proxy = (PayService) Proxy.newProxyInstance(
                        target.getClass().getClassLoader(), 
                new Class[]{PayService.class}, 
                handler
        );
        proxy.pay();
    }
}
3.3.1.1.2 CGLIB 动态代理实现
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor; 
import org.springframework.cglib.proxy.MethodProxy;
import org.example.demo.service.AliPayService;
import org.example.demo.service.PayService;

import java.lang.reflect.Method;

public class PayServiceCGLIBInterceptor implements MethodInterceptor {

    //被代理对象
    private Object target;

    public PayServiceCGLIBInterceptor(Object target){ 
        this.target = target;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] args, Method Proxy methodProxy) throws Throwable {
        //1.安全检查
        System.out.println("安全检查");
        //2.记录⽇志
        System.out.println("记录⽇志");
        //3.时间统计开始
        System.out.println("记录开始时间");

        //通过cglib的代理⽅法调⽤
        Object retVal = methodProxy.invoke(target, args);

        //4.时间统计结束 
        System.out.println("记录结束时间"); 
        return retVal;
    }

    public static void main(String[] args) {
        PayService target=  new AliPayService();
        PayService proxy= (PayService) Enhancer.create(target.getClass(),n ew PayServiceCGLIBInterceptor(target));
        proxy.pay();
    }
}
3.3.1.1.3 JDK 和 CGLIB 实现的区别
  1. JDK 实现,要求被代理类必须实现接⼝,之后是通过 InvocationHandler 及 Proxy,在运⾏
    时动态的在内存中⽣成了代理类对象,该代理对象是通过实现同样的接⼝实现(类似静态代 理接⼝实现的⽅式),只是该代理类是在运⾏期时,动态的织⼊统⼀的业务逻辑字节码来完 成。
  2. CGLIB 实现,被代理类可以不实现接⼝,是通过继承被代理类,在运⾏时动态的⽣成代理类
    对象。
  • 10
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Spring AOP(面向切面编程)是Spring框架中的一个模块,用于提供横切关注点(Cross-Cutting Concerns)的支持。横切关注点是与应用程序的核心业务逻辑无关的功能,例如日志记录、性能统计、事务管理等。 在Spring AOP中,通过定义切面(Aspect)来捕获横切关注点,并将其应用到目标对象的方法中。切面由切点(Pointcut)和通知(Advice)组成。切点定义了在何处应用通知,通知则定义了在切点处执行的操作。 Spring AOP支持以下几种类型的通知: 1. 前置通知(Before Advice):在目标方法执行之前执行的通知。 2. 后置通知(After Advice):在目标方法执行之后执行的通知,不管方法是否抛出异常。 3. 返回通知(After Returning Advice):在目标方法成功执行并返回结果后执行的通知。 4. 异常通知(After Throwing Advice):在目标方法抛出异常后执行的通知。 5. 环绕通知(Around Advice):围绕目标方法执行的通知,可以在方法调用前后执行自定义操作。 除了通知,Spring AOP还支持引入(Introduction)和切点表达式(Pointcut Expression)等功能。引入允许为目标对象添加新的接口和实现,而切点表达式则允许开发人员定义切点的匹配规则。 要在Spring应用程序中使用AOP,需要进行以下步骤: 1. 引入Spring AOP的依赖。 2. 配置AOP代理。 3. 定义切面和通知。 4. 配置切点和通知之间的关系。 总之,Spring AOP提供了一种便捷的方式来处理横切关注点,使得开发人员可以将关注点与核心业务逻辑分离,提高代码的可维护性和可重用性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值