Spring AOP:解锁切面编程的威力与实践

简介: Spring AOP:解锁切面编程的威力与实践


Spring AOP

Spring 的 AOP(面向切面编程)是 Spring 框架中的一个核心特性,它允许开发者在不修改原有代码的情况下,通过添加额外的逻辑来实现横切关注点(cross-cutting concerns)的功能。

在传统的面向对象编程中,应用程序的业务逻辑通常分散在多个对象中,例如数据持久化、日志记录、事务管理等。这些横切关注点会导致代码重复和散乱,使得维护和扩展变得困难。AOP 通过将这些横切关注点从主要业务逻辑中剥离出来,以切面的方式进行统一管理和配置,从而提高代码的可维护性和可重用性。

Spring 的 AOP 基于代理模式实现。它通过动态代理或者字节码生成技术,在运行时为目标对象生成一个代理对象,并将切面逻辑织入到代理对象的方法调用中。当应用程序调用代理对象的方法时,切面逻辑会在目标方法执行前、执行后或抛出异常时被触发执行。

以下是 Spring AOP 的核心概念:

  1. Aspect(切面):切面是横切关注点的模块化组合,它定义了要在目标对象的哪些连接点上执行什么样的切面逻辑。切面可以包含通知(advice)和切点(pointcut)。
  2. Advice(通知):通知是切面在特定连接点上执行的动作。常见的通知类型包括前置通知(在目标方法执行前执行)、后置通知(在目标方法执行后执行)、返回通知(在目标方法返回结果后执行)和异常通知(在目标方法抛出异常时执行)。
  3. Pointcut(切点):切点定义了哪些连接点(目标对象的方法)将被切面的通知所影响。切点可以通过表达式、注解或者 XML 配置来指定。
  4. Joinpoint(连接点):连接点表示在应用程序执行过程中可以插入切面的点,例如方法调用、方法执行、异常处理等。
  5. Weaving(织入):织入是将切面的通知应用到目标对象的过程。Spring AOP 支持编译时织入、类加载时织入和运行时织入三种织入方式。

使用 Spring AOP 可以实现诸如日志记录、事务管理、性能监控等跨越多个对象的共同关注点,从而提高代码的可维护性和模块化程度。你可以通过配置 XML、注解或者 Java Config 的方式来声明和定义切面,然后将其应用到 Spring 容器中的 Bean 上。

Spring AOP 底层实现

JDK 动态代理(通过接口创建代理类)

JDK 动态代理是通过 Java 反射机制来实现的。当目标对象实现了接口时,Spring 会使用 JDK 动态代理来创建代理对象。代理对象会实现与目标对象相同的接口,并将方法的调用委托给目标对象。

JDK 动态代理的主要步骤如下:

  • 定义一个 InvocationHandler 接口的实现类,在 invoke 方法中编写代理逻辑。
  • 使用 Proxy 类的 newProxyInstance 静态方法创建代理对象。需要传入目标对象的类加载器、目标对象实现的接口以及 InvocationHandler 实例。

优点:JDK 动态代理不需要依赖第三方库,能够直接使用 Java 的标准库实现。

缺点:只能为实现了接口的类创建代理对象。

package world.xuewei.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
 * @author 薛伟
 * @since 2023/9/14 20:51
 */
public class JdkProxyDemo {
    public static void main(String[] args) {
        // 创建目标对象(如果是 JDK 8 之前的版本,匿名内部类访问外面的变量需要声明 final)
        UserService userService = new UserServiceImpl();
        Class<? extends UserService> userServiceClass = userService.getClass();
        // 使用 JDK 动态代理生成代理对象
        UserService proxyObj = (UserService) Proxy.newProxyInstance(userServiceClass.getClassLoader(), userServiceClass.getInterfaces(), new InvocationHandler() {
            /**
             * 编写额外逻辑
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 在此处编写前置操作
                System.out.println("Before");
                Object result = null;
                try {
                    result = method.invoke(userService, args);
                } catch (Exception e) {
                    // 在此处编写异常操作
                    System.out.println("Exception");
                }
                // 在此处编写后置操作
                System.out.println("After");
                // 可以在此处编写新的返回值并返回
                return result;
            }
        });
        proxyObj.login();
    }
}

ClassLoader 用于创建代理类的 Class 对象,从而创建代理对象。对象是由类创建而来,先有类才有对象,而类的加载是由类加载器完成,动态创建的代理类本身是没有绑定类加载器的,所以需要借用一个外界的。可以借用任何类的类加载器。

CGLIB 动态代理(通过继承创建代理类)

当目标对象没有实现任何接口时,Spring 会使用 CGLIB 动态代理来创建代理对象。CGLIB 是一个强大的字节码生成库,它通过继承目标对象来创建代理对象,并重写目标对象的方法。

CGLIB 动态代理的主要步骤如下:

  • 定义一个 MethodInterceptor 接口的实现类,通过实现 intercept 方法编写代理逻辑。
  • 使用 Enhancer 类创建代理对象。需要设置目标对象的类和 MethodInterceptor 实例。

优点:CGLIB 动态代理可以为没有实现接口的类创建代理对象。

缺点:CGLIB 动态代理需要依赖 CGLIB 库,生成的代理对象继承了目标对象,对 final 方法和 private 方法无法进行代理。

package world.xuewei.proxy;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.InvocationHandler;
import java.lang.reflect.Method;
/**
 * @author 薛伟
 * @since 2023/9/14 20:51
 */
public class CglibProxyDemo {
    public static void main(String[] args) {
        // 创建目标对象
        UserService userService = new UserServiceImpl();
        // 使用 CGlib 动态创建代理对象
        UserService proxyObj = (UserService) Enhancer.create(userService.getClass(), new InvocationHandler() {
            @Override
            public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                // 在此处编写前置操作
                System.out.println("Before");
                Object result = null;
                try {
                    result = method.invoke(userService, args);
                } catch (Exception e) {
                    // 在此处编写异常操作
                    System.out.println("Exception");
                }
                // 在此处编写后置操作
                System.out.println("After");
                // 可以在此处编写新的返回值并返回
                return result;
            }
        });
        proxyObj.login();
    }
}

关于 Cglib 动态代理的类加载器(ClassLoader),Cglib 也是需要类加载器的,只不过指定方式和 JDK 动态代理略有不同,首先我们也可以为 Enhancer 类的对象指定类加载器。

  1. 如果为 Enhancer 对象指定了类加载器,那么就使用这个加载器。
  2. Enhancer 的父类中定义了一个 getDefaultClassLoader 的抽象方法,如果有实现,那么就用这个加载器。
  3. 获取 Enhancer 类的类加载器。
  4. 如果上面的都没有获取到,最后会尝试获取当前线程绑定的类加载器,否则就会抛出:IllegalStateException("Cannot determine classloader") 异常
public ClassLoader getClassLoader() {
      ClassLoader t = this.classLoader;
      if (t == null) {
          t = this.getDefaultClassLoader();
      }
      if (t == null) {
          t = this.getClass().getClassLoader();
      }
      if (t == null) {
          t = Thread.currentThread().getContextClassLoader();
      }
      if (t == null) {
          throw new IllegalStateException("Cannot determine classloader");
      } else {
          return t;
      }
  }

强制使用 Cglib 动态代理

<aop:config proxy-target-class="true">
    <!-- ... -->
</aop:config>

Spring 如何加工创建代理对象

创建代理对象的核心逻辑在 AbstractAutoProxyCreator 类中。前面我们已经学习过了后置处理器,而 Spring 加工创建代理对象也正是应用了 BeanPostProcessor 接口。

核心方法为此类的 createProxy 方法。

protected Object createProxy(Class<?> beanClass, @Nullable String beanName, @Nullable Object[] specificInterceptors, TargetSource targetSource) {
    if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
        AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory)this.beanFactory, beanName, beanClass);
    }
    ProxyFactory proxyFactory = new ProxyFactory();
    proxyFactory.copyFrom(this);
    if (!proxyFactory.isProxyTargetClass()) {
        if (this.shouldProxyTargetClass(beanClass, beanName)) {
            proxyFactory.setProxyTargetClass(true);
        } else {
            this.evaluateProxyInterfaces(beanClass, proxyFactory);
        }
    }
    Advisor[] advisors = this.buildAdvisors(beanName, specificInterceptors);
    proxyFactory.addAdvisors(advisors);
    proxyFactory.setTargetSource(targetSource);
    this.customizeProxyFactory(proxyFactory);
    proxyFactory.setFrozen(this.freezeProxy);
    if (this.advisorsPreFiltered()) {
        proxyFactory.setPreFiltered(true);
    }
    return proxyFactory.getProxy(this.getProxyClassLoader());
}

创建代理的方法,大概总结下执行的几个关键步骤:

  1. 创建代理工厂对象
  2. 代理工厂属性初始化及一些判断
  3. 关于通知的一些操作
  4. 调用工厂创建代理对象

通过代理工厂创建代理对象的核心逻辑如下(DefaultAopProxyFactory):

public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    if (!config.isOptimize() && !config.isProxyTargetClass() && !this.hasNoUserSuppliedProxyInterfaces(config)) {
        return new JdkDynamicAopProxy(config);
    } else {
        Class<?> targetClass = config.getTargetClass();
        if (targetClass == null) {
            throw new AopConfigException("TargetSource cannot determine target class: Either an interface or a target is required for proxy creation.");
        } else {
            return (AopProxy)(!targetClass.isInterface() && !Proxy.isProxyClass(targetClass) ? new ObjenesisCglibAopProxy(config) : new JdkDynamicAopProxy(config));
        }
    }
}

通过一些条件判断(目标类的父级是否为接口并且满足一些条件,optimize 为 false、proxyTargetClass 为 false)来决定使用 JDK 动态代理还是 Cglib 动态代理。

总结一下,Spring 使用动态代理技术来创建代理对象,并通过 BeanPostProcessor 来进行扩展和定制。在创建代理对象时,Spring 会根据目标对象的实现情况选择使用 JDK 动态代理或 Cglib 动态代理。它可以自动识别切面对象并为其创建代理对象,在代理对象创建完成后还可以通过 BeanPostProcessor 对代理对象进行进一步的定制和拓展。

总结



相关文章
|
3月前
Micronaut AOP与代理机制:实现应用功能增强,无需侵入式编程的秘诀
AOP(面向切面编程)能够帮助我们在不修改现有代码的前提下,为应用程序添加新的功能或行为。Micronaut框架中的AOP模块通过动态代理机制实现了这一目标。AOP将横切关注点(如日志记录、事务管理等)从业务逻辑中分离出来,提高模块化程度。在Micronaut中,带有特定注解的类会在启动时生成代理对象,在运行时拦截方法调用并执行额外逻辑。例如,可以通过创建切面类并在目标类上添加注解来记录方法调用信息,从而在不侵入原有代码的情况下增强应用功能,提高代码的可维护性和可扩展性。
64 1
|
26天前
|
安全 Java 编译器
什么是AOP面向切面编程?怎么简单理解?
本文介绍了面向切面编程(AOP)的基本概念和原理,解释了如何通过分离横切关注点(如日志、事务管理等)来增强代码的模块化和可维护性。AOP的核心概念包括切面、连接点、切入点、通知和织入。文章还提供了一个使用Spring AOP的简单示例,展示了如何定义和应用切面。
67 1
什么是AOP面向切面编程?怎么简单理解?
|
1月前
|
XML Java 开发者
论面向方面的编程技术及其应用(AOP)
【11月更文挑战第2天】随着软件系统的规模和复杂度不断增加,传统的面向过程编程和面向对象编程(OOP)在应对横切关注点(如日志记录、事务管理、安全性检查等)时显得力不从心。面向方面的编程(Aspect-Oriented Programming,简称AOP)作为一种新的编程范式,通过将横切关注点与业务逻辑分离,提高了代码的可维护性、可重用性和可读性。本文首先概述了AOP的基本概念和技术原理,然后结合一个实际项目,详细阐述了在项目实践中使用AOP技术开发的具体步骤,最后分析了使用AOP的原因、开发过程中存在的问题及所使用的技术带来的实际应用效果。
57 5
|
1月前
|
数据采集 Java 数据安全/隐私保护
Spring Boot 3.3中的优雅实践:全局数据绑定与预处理
【10月更文挑战第22天】 在Spring Boot应用中,`@ControllerAdvice`是一个强大的工具,它允许我们在单个位置处理多个控制器的跨切面关注点,如全局数据绑定和预处理。这种方式可以大大减少重复代码,提高开发效率。本文将探讨如何在Spring Boot 3.3中使用`@ControllerAdvice`来实现全局数据绑定与预处理。
61 2
|
2月前
|
Java 数据库连接 Spring
【2021Spring编程实战笔记】Spring开发分享~(下)
【2021Spring编程实战笔记】Spring开发分享~(下)
29 1
|
2月前
|
Java 容器
AOP面向切面编程
AOP面向切面编程
43 0
|
3月前
Micronaut AOP与代理机制:实现应用功能增强,无需侵入式编程的秘诀
【9月更文挑战第9天】AOP(面向切面编程)通过分离横切关注点提高模块化程度,如日志记录、事务管理等。Micronaut AOP基于动态代理机制,在应用启动时为带有特定注解的类生成代理对象,实现在运行时拦截方法调用并执行额外逻辑。通过简单示例展示了如何在不修改 `CalculatorService` 类的情况下记录 `add` 方法的参数和结果,仅需添加 `@Loggable` 注解即可。这不仅提高了代码的可维护性和可扩展性,还降低了引入新错误的风险。
48 13
|
3月前
|
Java 应用服务中间件 开发者
深入探索并实践Spring Boot框架
深入探索并实践Spring Boot框架
51 2
|
2月前
|
XML Java 数据库连接
【2020Spring编程实战笔记】Spring开发分享~(上)
【2020Spring编程实战笔记】Spring开发分享~
53 0
|
4月前
|
缓存 Java 开发者
Spring高手之路22——AOP切面类的封装与解析
本篇文章深入解析了Spring AOP的工作机制,包括Advisor和TargetSource的构建与作用。通过详尽的源码分析和实际案例,帮助开发者全面理解AOP的核心技术,提升在实际项目中的应用能力。
48 0
Spring高手之路22——AOP切面类的封装与解析