目标对象 (Target Object) 和 代理对象 (Proxy Object) 有什么区别?我们最终调用的是哪一个?

在这里插入图片描述

简单来说:

  • 目标对象 (Target Object):就是你亲手编写的、包含核心业务逻辑的那个原始对象(比如 UserService, ProductServiceImpl)。它是一个纯粹的业务类,完全不知道 AOP 的存在。
  • 代理对象 (Proxy Object):是 Spring AOP 框架在运行时动态创建的一个“包装”对象。它在外部看起来和目标对象一模一样(实现了相同的接口或继承了相同的类),但内部包含了额外的“切面逻辑”(即你的通知代码)。

我们最终调用的是哪一个?

答案是:我们调用的代理对象 (Proxy Object)。


详细区别与图解

让我们用一个生动的比喻来解释:

  • 目标对象 (Target Object):一位重要的 VIP(比如一位科学家)。他的职责是进行核心的科学研究(业务逻辑)。
  • 代理对象 (Proxy Object):这位 VIP 的 贴身保镖/经纪人。这位保镖看起来和 VIP 穿得差不多,甚至能模仿他的签名。他的职责是在 VIP 工作前后处理各种事务(比如安检、记录来访者、安排媒体采访等 —— 这些就是切面逻辑)。
  • 你 (调用方):一个想要拜访这位 VIP 的访客。

当你去拜访时,你不会直接冲到 VIP 面前。你首先接触到的是保镖(代理对象)

  1. 你向保镖提出请求:“我想见 VIP,让他帮我解决一个科学问题。” (你调用了代理对象的方法)
  2. 保镖(代理对象)收到请求后,并不会立刻让你去见 VIP。他会先执行一系列操作:
    • @Before:检查你的身份和预约(前置通知)。
    • @Around (前置部分):记录下你的来访时间(环绕通知的前半部分)。
  3. 一切准备就绪后,保镖才会去通知 VIP(代理对象调用目标对象):“这位访客可以见了,请您开始工作吧。”
  4. VIP(目标对象)开始埋头进行科学研究(执行核心业务逻辑)。
  5. 研究完成后,VIP 把结果交给了保镖。
  6. 保镖拿到结果后,他还会做一些收尾工作:
    • @Around (后置部分):记录下会谈结束的时间,并计算总耗时(环绕通知的后半部分)。
    • @AfterReturning:将研究成果整理成一份漂亮的报告(返回通知)。
    • @After:打扫会议室,做好清理工作(后置通知)。
  7. 最后,保镖(代理对象)把这份漂亮的报告交给你。

在这个过程中,你自始至终只和保镖(代理对象)打交道,甚至可能都没意识到 VIP 本人只负责了中间最核心的那一小段工作。


调用流程图

                +---------------------------------+
                |      你 (调用方,例如 Controller)   |
                +---------------------------------+
                           | 1. 调用 userService.findUser()
                           v
+-------------------------------------------------------------------------+
|                      代理对象 (Proxy Object)                             |
|       (由 Spring AOP 在运行时动态创建, e.g., UserServiceProxy)          |
|                                                                         |
|  +---------------------------+                                          |
|  | 2. @Before 通知执行        |                                          |
|  +---------------------------+                                          |
|              |                                                          |
|  +---------------------------+   3. 代理内部调用目标对象的原始方法      +-----------------+
|  | @Around (前半部分) 执行     |------------------------------------->| 目标对象 (Target)|
|  +---------------------------+                                          | (你写的UserService)|
|              |                                                          |  | 4. 核心业务逻辑 |
|  +---------------------------+   5. 目标方法返回结果给代理              |  |    执行         |
|  | @Around (后半部分) 执行     |<--------------------------------------|  +-------+---------+
|  +---------------------------+                                          |          ^
|              |                                                          |          |
|  +---------------------------+                                          |          |
|  | @AfterReturning 通知执行   |                                          |          |
|  +---------------------------+                                          |          |
|              |                                                          |          |
|  +---------------------------+                                          |          |
|  | @After 通知执行            |                                          |          |
|  +---------------------------+                                          |
|                                                                         |
+-------------------------------------------------------------------------+
                           | 6. 最终结果返回给调用方
                           v
                +---------------------------------+
                |      你 (调用方,例如 Controller)   |
                +---------------------------------+

区别总结

特性目标对象 (Target Object)代理对象 (Proxy Object)
创建者开发者 (你写的 .java 文件)Spring AOP 框架 (在运行时动态生成)
包含内容纯粹的、核心的业务逻辑切面逻辑 (所有通知) + 一个对目标对象的引用
对AOP的感知完全无感知,它是一个 POJO (Plain Old Java Object)为 AOP 而生,它的存在就是为了执行切面逻辑
谁调用它代理对象在内部调用它外部调用方 (如 Controller, 其他 Service)
在Spring容器中原始的 Bean 定义是你写的 Target 类当需要 AOP 增强时,Spring 用代理对象替换了容器中的目标对象

这为什么重要?—— “自我调用”失效问题

理解这个区别能帮我们避开一个常见的 AOP 陷阱:自我调用 (self-invocation) 失效

看下面的例子:

@Service
public class UserServiceImpl implements UserService {

    public void methodA() {
        System.out.println("--- Executing method A ---");
        // 这里是关键!`this` 指向的是目标对象本身,而不是代理对象!
        this.methodB(); // 这个调用会绕过 AOP 代理
    }

    // 我们希望这个方法被 AOP 拦截
    @MyAopAnnotation
    public void methodB() {
        System.out.println("--- Executing method B ---");
    }
}

当你从外部调用 userService.methodA() 时:

  1. 你调用的是代理对象methodA
  2. methodA 的 AOP 通知(如果有的话)会正常执行。
  3. 进入 methodA目标对象内部。
  4. 当它执行 this.methodB() 时,this 指的是目标对象本身,而不是代理对象。这次调用就像是在对象内部调用一个普通的私有方法一样,它直接访问了 methodB 的代码,完全绕过了代理对象
  5. 因此,methodB 上的 @MyAopAnnotation 所对应的 AOP 通知不会被触发

结论:AOP 的增强功能只在通过代理对象进行外部调用时才会生效。对象内部的自我调用(this.xxx())会使 AOP 失效。

在Spring框架中,获取Bean的原始目标对象而非代理对象一个常见的需求,尤其是在需要绕过AOP增强逻辑或直接访问目标对象的业务方法时。Spring的AOP功能通过动态代理机制对目标对象进行包装,当调用Bean的方法时,实际调用的是代理对象,而不是原始的目标对象 [^1]。 ### 获取原始目标对象的方式 1. **使用 `AopTargetUtils.getTarget()` 方法** Spring提供了 `AopTargetUtils` 工具类,可以用于获取代理对象背后的目标对象。该方法适用于JDK动态代理CGLIB代理的场景: ```java RepositoryService proxy = (RepositoryService) context.getBean("repositoryService"); RepositoryService target = (RepositoryService) AopTargetUtils.getTarget(proxy); ``` 该方式能够穿透代理层,获取到原始的目标对象 [^1]。 2. **使用 `TargetSource` 接口** Spring的 `TargetSource` 接口用于管理目标对象的获取与释放。通过 `AbstractBeanFactory` 获取目标对象时,可以结合 `TargetSource` 实现对目标对象的访问: ```java TargetSource targetSource = (TargetSource) context.getBean(TargetSource.class, "repositoryService"); Object target = targetSource.getTarget(); ``` 此方法适用于需要自定义目标对象管理的高级场景 [^1]。 3. **配置 `proxy-target-class="true"`** 在Spring配置中,可以通过设置 `proxy-target-class="true"` 强制使用CGLIB代理,而不是JDK动态代理。CGLIB代理通过继承目标类生成子类代理对象调用链路更接近目标类本身。虽然这不会直接获取原始目标对象,但能减少代理层对调用链路的影响 [^1]。 4. **避免代理对象的生成** 如果不需要AOP增强功能,可以通过移除相关的注解(如 `@Transactional`、`@Loggable`)或切面配置,来避免生成代理对象。这样,Spring容器返回的Bean就是原始的目标对象 [^1]。 ### 代理对象目标对象区别 - **代理对象**:是Spring AOP机制生成的中间对象,用于在方法调用前后插入增强逻辑。调用代理对象的方法时,会首先经过 `InvocationHandler` 或 `MethodInterceptor` 的处理 [^1]。 - **目标对象**:是实际包含业务逻辑的Bean实例。在AOP场景中,目标对象被封装在代理对象内部,只有通过特定方式才能直接访问 [^1]。 ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰糖心书房

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值