Spring AOP
Spring 的 AOP(面向切面编程)是 Spring 框架中的一个核心特性,它允许开发者在不修改原有代码的情况下,通过添加额外的逻辑来实现横切关注点(cross-cutting concerns)的功能。
在传统的面向对象编程中,应用程序的业务逻辑通常分散在多个对象中,例如数据持久化、日志记录、事务管理等。这些横切关注点会导致代码重复和散乱,使得维护和扩展变得困难。AOP 通过将这些横切关注点从主要业务逻辑中剥离出来,以切面的方式进行统一管理和配置,从而提高代码的可维护性和可重用性。
Spring 的 AOP 基于代理模式实现。它通过动态代理或者字节码生成技术,在运行时为目标对象生成一个代理对象,并将切面逻辑织入到代理对象的方法调用中。当应用程序调用代理对象的方法时,切面逻辑会在目标方法执行前、执行后或抛出异常时被触发执行。
以下是 Spring AOP 的核心概念:
- Aspect(切面):切面是横切关注点的模块化组合,它定义了要在目标对象的哪些连接点上执行什么样的切面逻辑。切面可以包含通知(advice)和切点(pointcut)。
- Advice(通知):通知是切面在特定连接点上执行的动作。常见的通知类型包括前置通知(在目标方法执行前执行)、后置通知(在目标方法执行后执行)、返回通知(在目标方法返回结果后执行)和异常通知(在目标方法抛出异常时执行)。
- Pointcut(切点):切点定义了哪些连接点(目标对象的方法)将被切面的通知所影响。切点可以通过表达式、注解或者 XML 配置来指定。
- Joinpoint(连接点):连接点表示在应用程序执行过程中可以插入切面的点,例如方法调用、方法执行、异常处理等。
- 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 类的对象指定类加载器。
- 如果为 Enhancer 对象指定了类加载器,那么就使用这个加载器。
- Enhancer 的父类中定义了一个 getDefaultClassLoader 的抽象方法,如果有实现,那么就用这个加载器。
- 获取 Enhancer 类的类加载器。
- 如果上面的都没有获取到,最后会尝试获取当前线程绑定的类加载器,否则就会抛出:
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()); }
创建代理的方法,大概总结下执行的几个关键步骤:
- 创建代理工厂对象
- 代理工厂属性初始化及一些判断
- 关于通知的一些操作
- 调用工厂创建代理对象
通过代理工厂创建代理对象的核心逻辑如下(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
对代理对象进行进一步的定制和拓展。
总结