核心概念:Spring AOP 和代理模式
Spring AOP(面向切面编程)是基于代理模式实现的。代理模式的核心思想是通过一个中间对象(代理)来控制对实际对象(目标对象)的访问。Spring AOP 使用代理来实现横切关注点(如日志、事务管理、性能监控等)的功能,而不需要直接修改目标对象的代码。
在你的代码示例中,Spring AOP 使用代理包装了目标对象(SimplePojo
),使得方法调用不再直接发生在目标对象上,而是通过代理对象进行。这会导致行为上的差异,下面逐步解析。
1. 没有代理的情况(直接调用)
在没有代理的情况下,代码的行为非常直观:
public class SimplePojo implements Pojo {
public void foo() {
// 直接调用 this.bar()
this.bar();
}
public void bar() {
// 一些逻辑...
}
}
public class Main {
public static void main(String[] args) {
Pojo pojo = new SimplePojo();
pojo.foo(); // 直接调用 SimplePojo 的 foo 方法
}
}
行为分析:
-
pojo
是一个SimplePojo
对象的直接引用。 - 调用
pojo.foo()
时,JVM 直接执行SimplePojo
类的foo
方法。 - 在
foo
方法中,this.bar()
是对当前对象的直接方法调用,调用的是SimplePojo
的bar
方法。 - 没有任何额外的逻辑插入,所有方法调用都在同一个对象内部完成,行为完全由目标对象的代码决定。
2. 有代理的情况(通过代理调用)
当使用 Spring AOP 的代理时,情况发生了变化。以下是代码片段:
public class Main {
public static void main(String[] args) {
ProxyFactory factory = new ProxyFactory(new SimplePojo());
factory.addInterface(Pojo.class);
factory.addAdvice(new RetryAdvice());
Pojo pojo = (Pojo) factory.getProxy();
pojo.foo(); // 通过代理调用 foo 方法
}
}
行为分析:
-
ProxyFactory
创建了一个代理对象,包装了SimplePojo
实例。 -
pojo
不再是SimplePojo
的直接引用,而是一个代理对象(实现Pojo
接口)。 - 当调用
pojo.foo()
时,实际上调用的是代理对象的foo
方法,而不是直接调用SimplePojo
的foo
方法。 - 代理对象会根据配置的 Advice(通知)(如
RetryAdvice
)在方法调用前后插入额外的逻辑。例如,RetryAdvice
可能会在方法失败时重试。 - 因此,方法调用会经过以下步骤:
- 客户端调用代理的
foo
方法。 - 代理执行前置通知(如果有)。
- 代理将调用委托给目标对象(
SimplePojo
)的foo
方法。 - 目标对象的
foo
方法执行,并可能调用this.bar()
。 - 代理执行后置通知(如果有)。
- 客户端调用代理的
3. 为什么加代理就不一样了?
代理的引入改变了方法调用的行为,具体原因如下:
(1)方法调用的拦截
-
无代理:
pojo.foo()
直接调用SimplePojo
的foo
方法,JVM 直接执行目标对象的代码。 -
有代理:
pojo.foo()
调用的是代理对象的foo
方法。代理对象可以拦截这个调用,执行额外的逻辑(由 Advice 定义),然后决定是否将调用传递给目标对象。 - 例如,
RetryAdvice
可能在foo
方法失败时自动重试,这是在无代理情况下不可能实现的。
(2)this
的行为差异
- 在
SimplePojo
的foo
方法中,this.bar()
是对当前对象的直接调用。 -
无代理:
this
指向SimplePojo
实例,因此this.bar()
直接调用SimplePojo
的bar
方法。 -
有代理:
this
仍然指向SimplePojo
实例(目标对象),而不是代理对象。因此,this.bar()
不会经过代理,而是直接调用目标对象的bar
方法。这意味着bar
方法的调用不会被代理拦截,也无法应用 Advice(如RetryAdvice
)。 - 如果你希望
bar
方法也被代理拦截,必须通过代理对象调用bar
(例如,通过pojo.bar()
而不是this.bar()
),或者使用其他技术(如 AspectJ 编译时织入)。
(3)代理带来的额外功能
- 代理允许在方法调用前后插入横切逻辑(Advice),例如:
- 前置通知(Before Advice):在方法调用前执行某些操作(如日志记录)。
- 后置通知(After Advice):在方法调用后执行某些操作(如清理资源)。
- 环绕通知(Around Advice):完全控制方法的执行,甚至可以决定是否调用目标方法。
- 在示例中,
RetryAdvice
可能是一个环绕通知,用于在方法失败时重试。这种功能是直接调用无法实现的。
(4)代理的类型
Spring AOP 支持两种代理方式:
-
JDK 动态代理:基于接口,代理对象实现目标对象的接口(如
Pojo
)。这是示例中使用的代理方式(factory.addInterface(Pojo.class)
)。 - CGLIB 代理:基于子类,适用于没有接口的类。Spring 会根据情况自动选择合适的代理方式。
- 无论是哪种代理,调用都会先到达代理对象,而不是直接到达目标对象。
4. 为什么需要理解代理的语义?
正如文档开头所说,“Spring AOP 是基于代理的”,理解代理的语义至关重要,因为:
- 方法调用的路径不同:通过代理调用和直接调用的行为不同,代理可能会插入额外的逻辑。
-
内部调用问题:如前所述,
this.bar()
不会经过代理,这可能导致 Advice 不生效。开发者需要明确哪些方法调用会被代理拦截。 - 性能影响:代理引入了额外的调用层,可能会对性能产生轻微影响。
- 配置复杂性:正确配置代理(选择接口、Advice、代理类型等)需要理解代理的工作原理。
5. 举个例子说明差异
假设 RetryAdvice
是一个重试逻辑,代码如下:
public class RetryAdvice implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
int retries = 3;
while (retries > 0) {
try {
return invocation.proceed(); // 调用目标方法
} catch (Exception e) {
retries--;
if (retries == 0) throw e;
System.out.println("Retrying...");
}
}
return null;
}
}
无代理:
- 调用
pojo.foo()
直接执行SimplePojo
的foo
方法。 - 如果
foo
方法抛出异常,不会触发任何重试逻辑。
有代理:
- 调用
pojo.foo()
时,调用被RetryAdvice
拦截。 - 如果
foo
方法抛出异常,RetryAdvice
会捕获异常并重试最多 3 次。 - 但如果
foo
方法内部调用this.bar()
,bar
方法不会被代理拦截,因此不会触发重试逻辑。
6. 总结
- 无代理:方法调用直接发生在目标对象上,行为由目标对象的代码决定。
- 有代理:方法调用先到达代理对象,代理可以插入额外的逻辑(如重试、日志、事务等),然后再决定是否调用目标对象。
-
差异原因:代理模式允许拦截和增强方法调用,但内部调用(
this.method()
)不会经过代理,导致 Advice 不生效。 - 解决内部调用问题:可以通过依赖注入获取代理对象,或者使用 AspectJ(编译时或加载时织入)来解决。
希望这个解析清晰地说明了为什么加了代理后行为会不同!如果有进一步的问题,欢迎继续问我。
Top comments (0)