为什么自调用不适用于Spring代理(例如AOP)?

时间:2019-06-15 21:37:53

标签: spring proxy spring-aop

请解释一下,为什么对目标执行代理的自我调用而不执行代理?如果那是故意的,那为什么呢?如果代理是通过子类创建的,则即使在自调用时,也可能在每个方法调用之前执行一些代码。我试过了,我有自己的代理权

public class DummyPrinter {
    public void print1() {
        System.out.println("print1");
    }

    public void print2() {
        System.out.println("print2");
    }

    public void printBoth() {
        print1();
        print2();
    }
}
public class PrinterProxy extends DummyPrinter {
    @Override
    public void print1() {
        System.out.println("Before print1");
        super.print1();
    }

    @Override
    public void print2() {
        System.out.println("Before print2");
        super.print2();
    }

    @Override
    public void printBoth() {
        System.out.println("Before print both");
        super.printBoth();
    }
}
public class Main {
    public static void main(String[] args) {
        DummyPrinter p = new PrinterProxy();
        p.printBoth();
    }
}

输出:

Before print both
Before print1
print1
Before print2
print2

这里在代理上调用了每个方法。为什么在文档中提到在进行自调用的情况下应该使用AspectJ?

1 个答案:

答案 0 :(得分:1)

请阅读Spring手册中的this chapter,您将了解。甚至在这里使用了“自我调用”一词。如果您仍然不理解,请随时提出后续问题,只要它们在上下文中即可。


更新:好的,现在,在我们确定您确实阅读了本章之后,并且在重新阅读了您的问题并分析了您的代码之后,我发现该问题实际上非常深刻(我什至赞成它),并且值得更详细地回答。

关于其工作原理的(假)假设

您的误解是关于动态代理的工作方式,因为它们不像您的示例代码中那样工作。让我将对象ID(哈希码)添加到日志输出中,以说明您自己的代码:

package de.scrum_master.app;

public class DummyPrinter {
  public void print1() {
    System.out.println(this + " print1");
  }

  public void print2() {
    System.out.println(this + " print2");
  }

  public void printBoth() {
    print1();
    print2();
  }
}
package de.scrum_master.app;

public class PseudoPrinterProxy extends DummyPrinter {
  @Override
  public void print1() {
    System.out.println(this + " Before print1");
    super.print1();
  }

  @Override
  public void print2() {
    System.out.println(this + " Before print2");
    super.print2();
  }

  @Override
  public void printBoth() {
    System.out.println(this + " Before print both");
    super.printBoth();
  }

  public static void main(String[] args) {
    new PseudoPrinterProxy().printBoth();
  }
}

控制台日志:

de.scrum_master.app.PseudoPrinterProxy@59f95c5d Before print both
de.scrum_master.app.PseudoPrinterProxy@59f95c5d Before print1
de.scrum_master.app.PseudoPrinterProxy@59f95c5d print1
de.scrum_master.app.PseudoPrinterProxy@59f95c5d Before print2
de.scrum_master.app.PseudoPrinterProxy@59f95c5d print2

看到了吗?总会有相同的对象ID,这并不奇怪。由于多态性,您的“代理”(实际上不是代理,而是静态编译的子类)的自调用起作用。 Java编译器负责解决此问题。

它如何真正起作用

现在,请记住我们在这里谈论的是动态代理,即在运行时创建的子类和对象:

  • JDK代理用于实现接口的类,这意味着在运行时正在创建实现这些接口的类。在这种情况下,仍然没有超类,这也解释了为什么它仅适用于公共方法:接口仅具有公共方法。
  • CGLIB代理还适用于未实现任何接口的类,因此也适用于受保护的和程序包作用域的方法(非私有方法,因为您无法覆盖它们,因此称为私有)。
  • 但是,关键点是,在上述两种情况下,创建代理时原始对象已经存在(并且仍然存在),因此,不存在多态性。情况是我们有一个动态创建的代理对象,该代理对象委托给原始对象,即我们有两个对象:代理和委托

我想这样说明:

package de.scrum_master.app;

public class DelegatingPrinterProxy extends DummyPrinter {
  DummyPrinter delegate;

  public DelegatingPrinterProxy(DummyPrinter delegate) {
    this.delegate = delegate;
  }

  @Override
  public void print1() {
    System.out.println(this + " Before print1");
    delegate.print1();
  }

  @Override
  public void print2() {
    System.out.println(this + " Before print2");
    delegate.print2();
  }

  @Override
  public void printBoth() {
    System.out.println(this + " Before print both");
    delegate.printBoth();
  }

  public static void main(String[] args) {
    new DelegatingPrinterProxy(new DummyPrinter()).printBoth();
  }
}

看到区别了吗?因此,控制台日志更改为:

de.scrum_master.app.DelegatingPrinterProxy@59f95c5d Before print both
de.scrum_master.app.DummyPrinter@5c8da962 print1
de.scrum_master.app.DummyPrinter@5c8da962 print2

这是使用动态代理甚至通常使用JDK或CGLIB代理的非Spring应用程序在Spring AOP或Spring其他部分看到的行为。

这是功能还是限制?我作为AspectJ(不是Spring AOP)用户认为这是一个限制。也许其他人可能认为这是一个功能,因为由于在Spring中实现了代理用法,因此您原则上可以在运行时动态地(取消)注册方面建议或拦截器,即,每个原始对象(代理)有一个代理,但是对于每个代理,在调用代理的原始方法之前和/或之后都有一个动态的拦截器列表。在非常动态的环境中,这可能是一件好事。我不知道您可能要多久使用一次。但是在AspectJ中,您还可以使用if()切入点指示符,可以在运行时确定是否使用某些建议(拦截器的AOP语言)。