Spring AOP 切面中获取目标类原始对象之TargetSource 底层机制与原型 Bean 代理

 更新时间:2026年10月31日 15:08:23   作者:程序员鸭梨  
argetSource是Spring AOP代理对象获取目标实例的间接层,代理内部并不直接持有目标对象,而是通过 TargetSource.getTarget() 每次调用时动态获取‌,这篇文章给大家介绍Spring AOP 切面中获取目标类原始对象:TargetSource底层机制与原型Bean代理,感兴趣的朋友一起看看吧

argetSource 是 Spring AOP 代理对象获取目标实例的间接层,代理内部并不直接持有目标对象,而是通过 TargetSource.getTarget() 每次调用时动态获取‌。这是理解原型 Bean 代理和热替换的关键。

🎯 TargetSource 核心机制

代理对象(JDK 或 CGLIB)内部持有 AdvisedSupport,其中包含 TargetSource 引用。每次方法调用,拦截器通过 targetSource.getTarget() 拿目标实例,执行完后若 !targetSource.isStatic() 则调用 releaseTarget(target) 释放。‌‌

TargetSource 接口定义四个方法:‌‌

  • ‌getTargetClass()‌:返回目标类的 Class 类型。
  • ‌isStatic()‌:返回目标是否为静态(单例)——静态目标可缓存,每次返回同一对象。
  • ‌getTarget()‌:获取当前要执行的目标实例。
  • ‌releaseTarget(Object target)‌:用完目标后释放,池化或原型场景下做清理。

📦 常见 TargetSource 实现

  • ‌SingletonTargetSource‌:默认实现,getTarget() 永远返回同一个单例,isStatic() 返回 true。
  • ‌PrototypeTargetSource‌:每次 getTarget() 都通过 BeanFactory.getBean() 创建全新实例,releaseTarget() 调用 destroyPrototypeInstance() 销毁。要求目标 Bean 必须声明为 prototype 作用域,否则启动报错。
  • ‌HotSwappableTargetSource‌:专为运行时热替换设计,通过 swap(newTarget) 方法无缝切换目标实例,下一微秒的请求全部落到新实例上,全程零停机、无锁并发安全。
  • ‌ThreadLocalTargetSource‌:每个线程绑定独立的目标实例。
  • ‌CommonsPool2TargetSource‌:从对象池借出实例执行,用完归还,适合非线程安全或初始化代价高的目标类。‌‌

在绝大多数 Java 开发者的认知里,Spring AOP 的工作模型极其简单直白:
容器启动时,Spring 找到被切面修饰的目标类(Target),通过 JDK 动态代理或者 CGLIB 生成一个代理对象(Proxy);我们在切面类中写 @Around 通知,通过 joinPoint.getTarget() 就能轻松拿到那个被代理的目标实例。

平时写写普通的日志审计或者单单的事务注解,这套认知模型完全够用。

但如果你有幸参与过底层框架的研发,或者在双 11 架构攻坚中遇到过以下“高阶需求”时,原有的认知会瞬间崩塌:

  1. 多租户动态热拔插(Hot Swapping):同一个 Service 代理对象注入给了 Controller,但在运行时,根据当前请求头里的商户 ID,需要动态将背后的真实业务实现切换为不同的类实例,且绝对不能重启应用或重新创建代理对象;
  2. 多线程并发池化(Pooling Target):某些被代理的目标类是非线程安全的(或者初始化代价极高,如某些老旧的加密或模板解析引擎),我们希望代理对象在每次方法调用时,从一个物理对象池里“借”出一个干净的原型(Prototype)实例执行,执行完后自动归还;
  3. 延迟懒加载(Lazy Initialization):代理对象必须立即初始化并注入,但真实的目标底层物理对象希望等到第一次真实方法调用时才去远程网络拉取。

如果只靠普通的动态代理,目标对象在代理生成时就被死死“写死”了。

而在 Spring AOP 的骨髓深处,藏着一个极少被普通开发者知晓、但在框架设计中堪称“核武级解耦接口”的灵魂抽象——TargetSource(目标源)。

今天我们就一竿子插到底,扒开 Spring AOP 的 TargetSource 源码,彻底讲透切面在运行时是如何动态获取物理实例的,以及如何利用它实现酷炫的高阶架构扩展。

一、TargetSource:Spring 动态代理的“移花接木”术

很多同学误以为 Spring 代理对象直接持有着目标对象 target 的引用。
错!代理对象内部真正持有的,其实是一个 TargetSource 接口的句柄!

[普通的直觉模型 (错误)]
  Proxy ---> 直接强引用 ---> 固定的 Target 实例
========================================================================
[Spring AOP 真实的物理内核 (TargetSource 解耦)]
  Proxy (动态代理对象)
    |
    v (每次方法调用均经过拦截器 proceed)
  ReflectiveMethodInvocation
    |
    v
  TargetSource.getTarget() <----+ 关键抽象! 每次调用时动态向 TargetSource 索要实例!
    |                           |
    +---> [1. SingletonTargetSource]  : 永远返回同一个单例 (Spring 默认行为)
    |
    +---> [2. PrototypeTargetSource]  : 每次方法调用都全新 new 一个实例!
    |
    +---> [3. ThreadLocalTargetSource]: 每个线程绑定独立的目标实例!
    |
    +---> [4. CustomHotSwapTargetSource]: 运行时动态无缝热替换目标实例!

我们来看 Spring 源码中 org.springframework.aop.TargetSource 的接口定义:

// org.springframework.aop.TargetSource
public interface TargetSource extends TargetClassAware {
    @Override
    @Nullable
    Class<?> getTargetClass();
    boolean isStatic();
    // 核心方法 1: 获取当前调用的目标对象
    @Nullable
    Object getTarget() throws Exception;
    // 核心方法 2: 调用完毕后的释放钩子 (用于池化对象归还)
    void releaseTarget(Object target) throws Exception;
}

请仔细观察那两个核心方法:

  • getTarget():在代理对象的拦截链执行到最终目标方法前夕被调用;
  • releaseTarget(target):在目标方法执行完毕、切面出栈时被调用。

只要我们控制了 TargetSource 的实现,我们就可以在不改变外部任何依赖注入关系的前提下,操纵每一次方法调用底层真正落在哪一个 Java 物理对象上!

二、源码追踪:CglibAopProxy 是如何向 TargetSource 索要对象的?

我们直接深入 Spring AOP 的核心代理拦截器 CglibAopProxy.DynamicAdvisedInterceptor,看它在执行 intercept() 时的底层物理流转:

// org.springframework.aop.framework.CglibAopProxy.DynamicAdvisedInterceptor
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    TargetSource targetSource = this.advised.getTargetSource();
    Object target = null;
    try {
        // 关键点!在真正调用方法前,向 targetSource 索要具体的物理对象!
        target = targetSource.getTarget();
        Class<?> targetClass = (target != null ? target.getClass() : null);
        // 获取该方法的拦截器链 (Interceptors)
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
        Object retVal;
        if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
            // 没有拦截器,直接反射调用 target
            retVal = methodProxy.invoke(target, args);
        } else {
            // 进入经典的责任链驱动 ReflectiveMethodInvocation
            retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
        }
        return retVal;
    } finally {
        if (target != null && !targetSource.isStatic()) {
            // 关键点!如果是非静态 TargetSource (如 Prototype 或池化),执行安全释放归还!
            targetSource.releaseTarget(target);
        }
    }
}

看清楚 finally 块里的那行神仙代码:
如果 targetSource.isStatic() 返回 false(说明它不是普通的单例),在方法执行完后,Spring 会立刻调用 releaseTarget(target)!

这就是为什么 Spring 能原生支持把一个多例 Bean(Prototype)安全代理给一个单例引用的底层秘密所在!

三、生产高阶实战:基于 HotSwappableTargetSource 实现双 11 零重启热切换

在双 11 大促现场,我们遇到了一个真实需求:
营销算价引擎有两个版本:V1Engine(基于本地复杂规则)与 V2Engine(接入了大模型推理预测)。
在压测或大促期间,如果外部大模型集群突然发生不可恢复的故障,我们希望在不重启任何服务、不修改 Controller 注入引用的前提下,在 1 毫秒内将所有请求无缝秒级切回 V1 本地规则引擎。

借助 Spring 内置的 HotSwappableTargetSource,实现简直像魔术一样优雅:

1. 配置热替换代理 Bean

package com.yali.trade.engine;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.target.HotSwappableTargetSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class EngineHotSwapConfiguration {
    @Bean
    public CalculationEngine localRuleEngine() {
        return new LocalRuleCalculationEngine(); // 备用稳定引擎
    }
    @Bean
    public CalculationEngine aiDrivenEngine() {
        return new AiDrivenCalculationEngine(); // 主力智能引擎
    }
    /**
     * 构建 HotSwappable 目标源,初始指向 AI 引擎
     */
    @Bean
    public HotSwappableTargetSource calculationTargetSource(CalculationEngine aiDrivenEngine) {
        return new HotSwappableTargetSource(aiDrivenEngine);
    }
    /**
     * 对外暴露同一个 CalculationEngine 门面代理对象!
     */
    @Bean("dynamicCalculationEngine")
    public CalculationEngine dynamicCalculationEngine(HotSwappableTargetSource targetSource) {
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setInterfaces(CalculationEngine.class);
        // 将 HotSwappableTargetSource 绑定到底层代理工厂中!
        proxyFactory.setTargetSource(targetSource);
        return (CalculationEngine) proxyFactory.getProxy();
    }
}

2. 运行时毫秒级切换控制器

在运维或故障监控中,一旦检测到异常,调用切换命令:

package com.yali.trade.engine;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.target.HotSwappableTargetSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@Slf4j
@Service
@RequiredArgsConstructor
public class EngineSwitchService {
    private final HotSwappableTargetSource targetSource;
    @Qualifier("localRuleEngine")
    private final CalculationEngine localRuleEngine;
    @Qualifier("aiDrivenEngine")
    private final CalculationEngine aiDrivenEngine;
    /**
     * 应急逃生通道:一键原子热切换!
     */
    public void emergencyFallbackToLocal() {
        log.warn("🚨 触发应急指令:正在将全局算价引擎原子热替换为本地规则引擎!");
        // 核心方法:swap() 在内部通过 volatile 原语实现无锁原子引用替换!
        Object oldTarget = targetSource.swap(localRuleEngine);
        log.info("热替换成功生效!旧目标实例: {}, 新目标实例: {}", 
                oldTarget.getClass().getSimpleName(), localRuleEngine.getClass().getSimpleName());
    }
    public void restoreToAiEngine() {
        log.info("恢复 AI 驱动算价引擎...");
        targetSource.swap(aiDrivenEngine);
    }
}

看清楚:
上游 Controller 依然在持有那个唯一的 dynamicCalculationEngine 代理对象;
但一旦执行了 targetSource.swap(localRuleEngine),下一微秒进来的所有并发请求,在执行 targetSource.getTarget() 时,直接全部落在了 localRuleEngine 上!
全程零停机、零重启、无锁并发安全,堪称现代 Java 架构的一绝!

四、架构师心法

在软件工程中,有一句著名的架构格言:“任何计算机网络与软件架构的难题,都可以通过引入一个间接的中间层来解决。”

TargetSource 就是 Spring AOP 为我们留出的那个最极致的间接层。
它打破了“代理必须绑定静态实例”的思维定式,把目标对象的获取变成了动态、可塑、具备无限扩展可能的生命周期钩子。

吃透了这个接口,在面对多租户隔离、动态容灾热切换、无状态池化等极其苛刻的高阶架构需求时,你才能展现出真正举重若轻的顶级工程智慧。

到此这篇关于Spring AOP 切面中获取目标类原始对象之TargetSource 底层机制与原型 Bean 代理的文章就介绍到这了,更多相关Spring AOP TargetSource 底层机制内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • JAVA CountDownLatch与thread-join()的区别解析

    JAVA CountDownLatch与thread-join()的区别解析

    这篇文章主要介绍了JAVA CountDownLatch与thread-join()的区别解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-08-08
  • Spring声明式事务配置使用详解

    Spring声明式事务配置使用详解

    这篇文章主要介绍了在spring注解中,使用声明式事务,需要用到两个核心的注解:@Transactional注解和@EnableTransactionManagement注解。将@Transactional注解加在方法上,@EnableTransactionManagement注解加在配置类上
    2022-08-08
  • springMVC使用ajaxFailUpload上传图片的方法

    springMVC使用ajaxFailUpload上传图片的方法

    这篇文章主要介绍了springMVC使用ajaxFailUpload上传图片的相关知识,代码简单易懂,非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下
    2018-06-06
  • 教你如何使用Java输出各种形状

    教你如何使用Java输出各种形状

    本文小编将向大家介绍的是如何利用Java输出各种不同的形状,本文一共介绍了七种有趣的形状,感兴趣的小伙伴赶快收藏起来吧
    2021-09-09
  • SpringBoot整合flyway实现步骤解析

    SpringBoot整合flyway实现步骤解析

    这篇文章主要介绍了SpringBoot整合flyway实现步骤解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-08-08
  • 浅谈Java中Collection和Collections的区别

    浅谈Java中Collection和Collections的区别

    下面小编就为大家带来一篇浅谈Java中Collection和Collections的区别。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-08-08
  • 页面嵌入iframe Cookie丢失问题以及解决过程

    页面嵌入iframe Cookie丢失问题以及解决过程

    文章介绍了在页面嵌入iframe时遇到的cookie丢失问题,提出了解决方案:后端将cookie以链接参数的形式传递给前端,前端在请求接口时将cookie放在请求头中,后端通过拦截器解析并重新放置cookie
    2026-02-02
  • MapStruct实体转换及List转换的方法讲解

    MapStruct实体转换及List转换的方法讲解

    今天小编就为大家分享一篇关于MapStruct实体转换及List转换的方法讲解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-03-03
  • 关于@Value取值为NULL的解决方案

    关于@Value取值为NULL的解决方案

    这篇文章主要介绍了关于@Value取值为NULL的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-07-07
  • 手把手教你排查解决Java编译报错:找不到符号

    手把手教你排查解决Java编译报错:找不到符号

    这篇文章主要介绍了手把手教你排查解决Java编译报错:找不到符号的相关资料,提供了排查步骤和解决方法,通过这些步骤,开发者可以有效地找到并解决编译器抛出的找不到符号错误,从而提高开发效率,需要的朋友可以参考下
    2025-04-04

最新评论