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 对代理对象进行进一步的定制和拓展。

总结



相关文章
|
1月前
|
XML 安全 Java
使用 Spring 的 @Aspect 和 @Pointcut 注解简化面向方面的编程 (AOP)
面向方面编程(AOP)通过分离横切关注点,如日志、安全和事务,提升代码模块化与可维护性。Spring 提供了对 AOP 的强大支持,核心注解 `@Aspect` 和 `@Pointcut` 使得定义切面与切入点变得简洁直观。`@Aspect` 标记切面类,集中处理通用逻辑;`@Pointcut` 则通过表达式定义通知的应用位置,提高代码可读性与复用性。二者结合,使开发者能清晰划分业务逻辑与辅助功能,简化维护并提升系统灵活性。Spring AOP 借助代理机制实现运行时织入,与 Spring 容器无缝集成,支持依赖注入与声明式配置,是构建清晰、高内聚应用的理想选择。
342 0
|
13天前
|
XML Java 数据格式
《深入理解Spring》:AOP面向切面编程深度解析
Spring AOP通过代理模式实现面向切面编程,将日志、事务等横切关注点与业务逻辑分离。支持注解、XML和编程式配置,提供五种通知类型及丰富切点表达式,助力构建高内聚、低耦合的可维护系统。
|
2月前
|
人工智能 监控 安全
Spring AOP切面编程颠覆传统!3大核心注解+5种通知类型,让业务代码纯净如初
本文介绍了AOP(面向切面编程)的基本概念、优势及其在Spring Boot中的使用。AOP作为OOP的补充,通过将横切关注点(如日志、安全、事务等)与业务逻辑分离,实现代码解耦,提升模块化程度、可维护性和灵活性。文章详细讲解了Spring AOP的核心概念,包括切面、切点、通知等,并提供了在Spring Boot中实现AOP的具体步骤和代码示例。此外,还列举了AOP在日志记录、性能监控、事务管理和安全控制等场景中的实际应用。通过本文,开发者可以快速掌握AOP编程思想及其实践技巧。
|
2月前
|
人工智能 监控 安全
如何快速上手【Spring AOP】?核心应用实战(上篇)
哈喽大家好吖~欢迎来到Spring AOP系列教程的上篇 - 应用篇。在本篇,我们将专注于Spring AOP的实际应用,通过具体的代码示例和场景分析,帮助大家掌握AOP的使用方法和技巧。而在后续的下篇中,我们将深入探讨Spring AOP的实现原理和底层机制。 AOP(Aspect-Oriented Programming,面向切面编程)是Spring框架中的核心特性之一,它能够帮助我们解决横切关注点(如日志记录、性能统计、安全控制、事务管理等)的问题,提高代码的模块化程度和复用性。
|
2月前
|
设计模式 Java 开发者
如何快速上手【Spring AOP】?从动态代理到源码剖析(下篇)
Spring AOP的实现本质上依赖于代理模式这一经典设计模式。代理模式通过引入代理对象作为目标对象的中间层,实现了对目标对象访问的控制与增强,其核心价值在于解耦核心业务逻辑与横切关注点。在框架设计中,这种模式广泛用于实现功能扩展(如远程调用、延迟加载)、行为拦截(如权限校验、异常处理)等场景,为系统提供了更高的灵活性和可维护性。
|
XML Java 数据库
Spring5入门到实战------10、操作术语解释--Aspectj注解开发实例。AOP切面编程的实际应用
这篇文章是Spring5框架的实战教程,详细解释了AOP的关键术语,包括连接点、切入点、通知、切面,并展示了如何使用AspectJ注解来开发AOP实例,包括切入点表达式的编写、增强方法的配置、代理对象的创建和优先级设置,以及如何通过注解方式实现完全的AOP配置。
|
XML Java 数据格式
[Spring实战系列](18)注解切面
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/SunnyYoona/article/details/50659876 使用注解来创建切面是AspectJ 5所引入的关键特性。
943 0
|
10天前
|
XML 前端开发 Java
一文搞懂 Spring Boot 自动配置原理
Spring Boot 自动配置原理揭秘:通过 `@EnableAutoConfiguration` 加载 `META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports` 中的配置类,结合 `@Conditional` 按条件注入 Bean,实现“开箱即用”。核心在于约定大于配置,简化开发。
206 0
|
3月前
|
Java Spring 容器
SpringBoot自动配置的原理是什么?
Spring Boot自动配置核心在于@EnableAutoConfiguration注解,它通过@Import导入配置选择器,加载META-INF/spring.factories中定义的自动配置类。这些类根据@Conditional系列注解判断是否生效。但Spring Boot 3.0后已弃用spring.factories,改用新格式的.imports文件进行配置。
795 0

热门文章

最新文章

下一篇
开通oss服务