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

总结



相关文章
|
7天前
|
XML Java 开发者
Spring Boot中的AOP实现
Spring AOP(面向切面编程)允许开发者在不修改原有业务逻辑的情况下增强功能,基于代理模式拦截和增强方法调用。Spring Boot通过集成Spring AOP和AspectJ简化了AOP的使用,只需添加依赖并定义切面类。关键概念包括切面、通知和切点。切面类使用`@Aspect`和`@Component`注解标注,通知定义切面行为,切点定义应用位置。Spring Boot自动检测并创建代理对象,支持JDK动态代理和CGLIB代理。通过源码分析可深入了解其实现细节,优化应用功能。
|
2天前
|
XML 监控 前端开发
Spring Boot中的WebFlux编程模型
Spring WebFlux 是 Spring Framework 5 引入的响应式编程模型,基于 Reactor 框架,支持非阻塞异步编程,适用于高并发和 I/O 密集型应用。本文介绍 WebFlux 的原理、优势及在 Spring Boot 中的应用,包括添加依赖、编写响应式控制器和服务层实现。WebFlux 提供高性能、快速响应和资源节省等优点,适合现代 Web 应用开发。
41 15
|
6天前
|
人工智能 Java API
阿里云工程师跟通义灵码结伴编程, 用Spring AI Alibaba来开发 AI 答疑助手
本次分享的主题是阿里云工程师跟通义灵码结伴编程, 用Spring AI Alibaba来开发 AI 答疑助手,由阿里云两位工程师分享。
阿里云工程师跟通义灵码结伴编程, 用Spring AI Alibaba来开发 AI 答疑助手
|
16天前
|
存储 安全 Java
Spring Boot 3 集成Spring AOP实现系统日志记录
本文介绍了如何在Spring Boot 3中集成Spring AOP实现系统日志记录功能。通过定义`SysLog`注解和配置相应的AOP切面,可以在方法执行前后自动记录日志信息,包括操作的开始时间、结束时间、请求参数、返回结果、异常信息等,并将这些信息保存到数据库中。此外,还使用了`ThreadLocal`变量来存储每个线程独立的日志数据,确保线程安全。文中还展示了项目实战中的部分代码片段,以及基于Spring Boot 3 + Vue 3构建的快速开发框架的简介与内置功能列表。此框架结合了当前主流技术栈,提供了用户管理、权限控制、接口文档自动生成等多项实用特性。
65 8
|
1月前
|
Java Spring
一键注入 Spring 成员变量,顺序编程
介绍了一款针对Spring框架开发的插件,旨在解决开发中频繁滚动查找成员变量注入位置的问题。通过一键操作(如Ctrl+1),该插件可自动在类顶部添加`@Autowired`注解及其成员变量声明,同时保持光标位置不变,有效提升开发效率和代码编写流畅度。适用于IntelliJ IDEA 2023及以上版本。
一键注入 Spring 成员变量,顺序编程
|
2月前
|
监控 安全 Java
什么是AOP?如何与Spring Boot一起使用?
什么是AOP?如何与Spring Boot一起使用?
99 5
|
2月前
|
Java 开发者 Spring
Spring AOP 底层原理技术分享
Spring AOP(面向切面编程)是Spring框架中一个强大的功能,它允许开发者在不修改业务逻辑代码的情况下,增加额外的功能,如日志记录、事务管理等。本文将深入探讨Spring AOP的底层原理,包括其核心概念、实现方式以及如何与Spring框架协同工作。
|
2月前
|
XML 监控 安全
深入调查研究Spring AOP
【11月更文挑战第15天】
54 5
|
Java 测试技术 Spring
SPRING实践总结--参数注解的使用
今天用spring 搭建测试模型过程中发现web接收参数的注解使用方式各有不同,在不同场景下的使用方式总结了一下 @RequestBody 获取POST请求中的参数,请求参数会放到MAP里 @RequestMapping(value = "/doQueryTempReq", method = RequestMethod.
1024 0
|
2天前
|
XML Java 应用服务中间件
Spring Boot 两种部署到服务器的方式
本文介绍了Spring Boot项目的两种部署方式:jar包和war包。Jar包方式使用内置Tomcat,只需配置JDK 1.8及以上环境,通过`nohup java -jar`命令后台运行,并开放服务器端口即可访问。War包则需将项目打包后放入外部Tomcat的webapps目录,修改启动类继承`SpringBootServletInitializer`并调整pom.xml中的打包类型为war,最后启动Tomcat访问应用。两者各有优劣,jar包更简单便捷,而war包适合传统部署场景。需要注意的是,war包部署时,内置Tomcat的端口配置不会生效。
72 17
Spring Boot 两种部署到服务器的方式