核心概念: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)