Spring AOP 入门、实现原理及实践案例

前言

  Spring 是目前 Java 领域最为流行的开发框架之一,它提供了很多方便快捷的功能,其中之一就是 AOP(Aspect Oriented Programming),即面向切面编程。本文将详细介绍 Spring AOP 的实现原理、核心概念以及在实际应用中的使用案例。

1. AOP 的基本概念

  1.1 切入点(Pointcut)

  切入点是一个表达式,用于描述哪些方法将被拦截执行,通常使用正则表达式或通配符来匹配方法名或类名。Spring AOP 支持两种类型的切入点:静态切入点和动态切入点。静态切入点在创建时就已经确定,而动态切入点则需要在运行时根据实际情况进行计算。

  1.2 通知(Advice)

  通知是指 AOP 在拦截到被选定的方法后,所执行的代码块。Spring AOP 中支持五种不同类型的通知:

  • 前置通知(Before Advice):在目标方法执行之前执行。
  • 后置通知(After Returning Advice):在目标方法正常完成后执行。
  • 异常通知(After Throwing Advice):在目标方法抛出异常时执行。
  • 最终通知(After Finally Advice):在目标方法完成之后执行,无论是否发生异常。
  • 环绕通知(Around Advice):覆盖目标方法的执行,需要手动调用目标方法。

  1.3 切面(Aspect)

  切面是将切入点和通知组合在一起的实体对象,用于描述哪些方法应该在何时被拦截,并指定要执行的通知代码块。

2. Spring AOP 的实现原理

  Spring AOP 的实现原理是基于 JDK 动态代理或 CGLIB 字节码技术。在目标对象上创建一个动态代理或子类,拦截所需要的方法并执行对应的通知,在执行完毕后再将控制权转交给目标对象。这样就可以在不修改目标对象代码的情况下,实现对目标方法的增强。

  具体来说,Spring AOP 拦截方法的实现分为以下几个步骤:

  1. 定义切面,配置切入点和通知。
  2. 根据目标对象类型创建代理对象。
  3. 拦截符合切入点要求的方法,并执行所配置的通知代码块。

3. Spring AOP 实践案例

  为了更好地理解 Spring AOP 的实现原理,下面以一个简单的实践案例为例:使用 AOP 打印每个方法执行的日志,日志内容包括方法执行、执行结果、异常信息等方面。

  3.1 定义切入点和通知

  首先,需要定义一个切入点,这里为了方便,直接匹配所有 public 方法:

@Pointcut("execution(public * *(..))")
public void logPointCut() {
    
    }

  然后,定义一个环绕通知,在目标方法执行前后打印日志:

@Around("logPointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
    
    
    long startTime = System.currentTimeMillis();

    Object result = joinPoint.proceed();

    long endTime = System.currentTimeMillis();
    long duration = endTime - startTime;

    String methodName = joinPoint.getSignature().getName();
    String className = joinPoint.getTarget().getClass().getSimpleName();
    String args = Arrays.toString(joinPoint.getArgs());

    if (result instanceof Serializable) {
    
    
        log.info("{}#{}({}, {}) : {} , 耗时 {} ms", className, methodName, args, result, duration);
    } else {
    
    
        log.info("{}#{}({}, [not serializable object]) , 耗时 {} ms", className, methodName, args, duration);
    }

    return result;
}

  上述代码中,@Around 注解表示该方法是一个环绕通知,并且会拦截名为 logPointCut() 的切入点所匹配到的所有方法。ProceedingJoinPoint 类型表示需要拦截的连接点,通过调用其 proceed() 方法可以继续执行原本要执行的目标方法,返回值类型为 Object,即目标方法的执行结果。

  3.2 配置 AOP

  接下来需要在 Spring 配置文件中配置 AOP,将切面和切入点以及通知绑定在一起:

<!-- 定义切面 -->
<bean id="logAspect" class="com.example.LogAspect"/>

<!-- 配置 AOP -->
<aop:config>
    <aop:aspect ref="logAspect">
        <!-- 声明切入点 -->
        <aop:pointcut id="logPointCut" expression="execution(public * *(..))"/>

        <!-- 声明通知 -->
        <aop:around method="around" pointcut-ref="logPointCut"/>
    </aop:aspect>
</aop:config>

  上述代码中,<bean> 标签定义了一个名为 logAspect 的 JavaBean 对象,并指定其类为 com.example.LogAspect,即定义了切面对象。而 <aop:config> 标签则表示开始 AOP 配置,其中 <aop:aspect> 定义了一个切面,其 ref 属性指向 logAspect,即引用前面定义的切面对象;<aop:pointcut> 声明了一个切入点,其 expression 属性使用与前面定义的一致;<aop:around> 则声明了一个环绕通知,将其方法名指定为 around,并将其与前面定义的切入点绑定在一起。

  3.4 SpringBoot版本的AOP案例

注意:在 Spring Boot 中使用 AOP 需要在启动类的配置中添加 @EnableAspectJAutoProxy 注解来开启自动代理功能,以便能够使用 AOP 切面。

@Aspect
@Component
public class LogAspect {
    
    

    @Pointcut("execution(public * *(..))")
    public void logPointCut() {
    
    }

    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
    
    
        long startTime = System.currentTimeMillis();

        Object result = joinPoint.proceed();

        long endTime = System.currentTimeMillis();
        long duration = endTime - startTime;

        String methodName = joinPoint.getSignature().getName();
        String className = joinPoint.getTarget().getClass().getSimpleName();
        String args = Arrays.toString(joinPoint.getArgs());

        if (result instanceof Serializable) {
    
    
            log.info("{}#{}({}, {}) : {} , 耗时 {} ms", className, methodName, args, result, duration);
        } else {
    
    
            log.info("{}#{}({}, [not serializable object]) , 耗时 {} ms", className, methodName, args, duration);
        }

        return result;
    }
}

  3.5 测试应用

  最后,在业务代码中添加一些方法并测试:

@Service
public class MyService {
    
    
    public void sayHello(String name) {
    
    
        System.out.println("Hello, " + name + "!");
    }
    
    public int divide(int a, int b) {
    
    
        return a / b;
    }
}

  上述代码定义了一个名为 MyService 的服务类,其中包含了两个简单的方法,分别是打印问候语和除法运算。现在,只需要在 Spring 容器中注入该服务类,并调用其方法即可,AOP 将会在控制台打印出对应的日志信息:

MyService myService = context.getBean(MyService.class);
myService.sayHello("World");
myService.divide(10, 2);
myService.divide(10, 0);

  其中第一行代码是从 Spring 容器中获取该服务类的实例对象,然后依次调用 sayHellodivide 方法,可以看到控制台输出了类似下面的日志信息:

com.example.MyService#sayHello([World], null) : null , 耗时 0 ms
com.example.MyService#divide([10, 2], 5) : 2 , 耗时 0 ms
com.example.MyService#divide([10, 0], java.lang.ArithmeticException: / by zero) , 耗时 1 ms

4. 总结

  Spring AOP 是一种很方便实用的面向切面编程技术,通过代理或字节码技术实现对目标方法的拦截和通知,从而实现对目标方法的增强。在实际应用中,通常使用切入点、通知和切面来描述 AOP 的核心概念,并通过配置文件将它们组合在一起来实现具体功能。

猜你喜欢

转载自blog.csdn.net/java_cpp_/article/details/130888148
今日推荐