为什么自调用不适用于spring代理(例如,使用aop)?

dwthyt8l  于 2021-07-13  发布在  Java
关注(0)|答案(1)|浏览(404)

请解释一下,为什么代理上的自调用是在目标上执行的,而不是在代理上执行的?如果这是故意的,那为什么?如果代理是通过子类化创建的,那么在每次方法调用之前都有可能执行一些代码,甚至在自调用时也是如此。我试过了,我有一个自我调用的代理

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?

sqxo8psd

sqxo8psd1#

请阅读Spring手册中的这一章,你就会明白了。这里甚至使用了“自我调用”这个词。如果你仍然不明白,请随时提出后续问题,只要是在上下文中。
更新:好的,在我们确定你真的读了那一章之后,在重新阅读了你的问题并分析了你的代码之后,我发现这个问题实际上是相当深刻的(我甚至投了更高的票),值得更详细地回答。

你对它的工作原理的(错误的)假设

您的误解是动态代理如何工作,因为它们不像示例代码中那样工作。让我将对象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代理也适用于没有实现任何接口的类,因此也适用于受保护和包作用域的方法(但不是私有方法,因为您不能重写这些方法,因此术语private)。
然而,关键的一点是,在上述两种情况下,当代理被创建时,原始对象已经(并且仍然)存在,因此不存在多态性。这种情况是,我们有一个动态创建的代理对象委托给原始对象,即我们有两个对象:一个代理和一个委托。
我想这样说明:

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

这是springaop或spring的其他部分使用动态代理的行为,甚至是使用jdk或cglib代理的非spring应用程序。
这是特性还是限制?作为aspectj(不是SpringAOP)用户,我认为这是一个限制。也许其他人会认为这是一个特性,因为根据spring中实现代理使用的方式,原则上,您可以在运行时动态(取消)注册方面建议或拦截器,即每个原始对象(delegate)有一个代理,但是对于每个代理,都有一个在调用委托的原始方法之前和/或之后调用的拦截器的动态列表。在非常动态的环境中,这是一件好事。我不知道你会多久用一次。但在aspectj中,您也有 if() 切入点指示符,您可以使用它在运行时确定是否应用某些通知(用于拦截器的aop语言)。

相关问题