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天前
|
安全 Java 开发者
在Spring框架中,IoC和AOP是如何实现的?
【4月更文挑战第30天】在Spring框架中,IoC和AOP是如何实现的?
7 0
|
1天前
|
Java 测试技术 开发者
【亮剑】如何通过自定义注解来实现 Spring AOP,以便更加灵活地控制方法的拦截和增强?
【4月更文挑战第30天】通过自定义注解实现Spring AOP,可以更灵活地控制方法拦截和增强。首先定义自定义注解,如`@MyCustomAnnotation`,然后创建切面类`MyCustomAspect`,使用`@Pointcut`和`@Before/@After`定义切点及通知。配置AOP代理,添加`@EnableAspectJAutoProxy`到配置类。最后,在需拦截的方法上应用自定义注解。遵循保持注解职责单一、选择合适保留策略等最佳实践,提高代码可重用性和可维护性。记得测试AOP逻辑。
|
6天前
|
安全 Java Maven
[AIGC] Spring Boot中的切面编程和实例演示
[AIGC] Spring Boot中的切面编程和实例演示
|
18天前
|
Java Spring
代码优雅的转变:基于注解的AOP编程在Spring中的实践
代码优雅的转变:基于注解的AOP编程在Spring中的实践
17 0
|
18天前
|
Java 数据库 Spring
切面编程的艺术:Spring动态代理解析与实战
切面编程的艺术:Spring动态代理解析与实战
27 0
切面编程的艺术:Spring动态代理解析与实战
|
3月前
|
Java 数据库连接 应用服务中间件
Spring5源码(39)-Aop事物管理简介及编程式事物实现
Spring5源码(39)-Aop事物管理简介及编程式事物实现
24 0
|
4月前
AOP&面向切面编程
AOP&面向切面编程
55 0
|
4月前
|
Java 程序员 Maven
Spring AOP入门指南:轻松掌握面向切面编程的基础知识
Spring AOP入门指南:轻松掌握面向切面编程的基础知识
|
4月前
|
数据库
AOP(面向切面编程)的基本概念和原理
AOP(面向切面编程)的基本概念和原理
83 0
|
6月前
|
缓存 监控 Java
Spring框架之AOP(面向切面编程)
Spring框架之AOP(面向切面编程)
34 0