当前位置: 首页 > 知识库问答 >
问题:

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

党航
2023-03-14

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

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个答案

湛铭
2023-03-14

请阅读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代理也适用于不实现任何接口的类,因此也适用于受保护的和包范围的方法(但不适用私有方法,因为您无法重写这些方法,因此称为私有)。
  • 但是,关键的一点是,在上述两种情况下,当创建代理时,原始对象已经(并且仍然)存在,因此不存在多态性。情况是,我们有一个动态创建的代理对象委托给原始对象,即我们有两个对象:代理和委托。
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

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

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

 类似资料:
  • 我找不到任何原因来解释为什么每个自动连接的bean都不是通过代理自动连接的。我知道,因为事务注释不起作用,我在eclipse中调试时检查了自动连线组件。当然,每个组件都实现了一些接口,我使用了与接口相关的注释。我只有一种aop配置: 我将JPA与hibernate、spring mvc、spring webflow、spring security和spring数据一起使用。扩展组织的接口。spri

  • 当我编写一些spring代码时,我使用了带有class和annotation-config的Spring4。我已经声明一个bean将接口实现为组件。我正在尝试制作另一个bean来依赖于它的接口时间。但它不起作用,因为spring抛出一个错误,在该名称中找不到bean。我想这可能是因为只靠和实体类的自动电线工作,但我不知道为什么它会这样设置?有人能解释为什么依赖注释不允许类型自动连接到接口吗? 简单

  • 我正在使用forEach循环一个nodeList。我的代码如下 此代码引发错误为 未捕获的TypeError:Array.Foreach不是函数 然而,一些较旧的浏览器还没有实现nodelist.foreach()和array.from()。但是这些限制可以通过使用array.prototype.foreach()来规避(本文档中有更多内容)。 参考:MDN

  • 问题内容: 我有一个expressjs应用程序,在特定的路由上,我调用了一个函数,该函数通过使用数据库文档作为参数来响应数据库中的用户。我使用基于promise的库,并且想在将数据库文档放入响应中的回调内联。但是当我这样做时程序会失败。有人可以解释为什么吗?我还想知道为什么内联调用才能真正起作用。两种方法和之间有一些根本区别吗? 这是一个有效和无效的示例。假定返回用户文档的承诺。 问题答案: 像这

  • 为什么JSF2/Facelet的ui:repeat不接受java。util。迭代器的值?可以在迭代器后面隐藏如此多的实现和内存节约,因为长度不需要知道,所以拥有它是非常有用的。但是相反,我需要将迭代器转换为列表,并抛弃所有优点,以便ui:repeat。 可能有阶段、时间或可序列化的原因,但我对可用文档的浏览并没有给出这些原因。我们还没有使这一不可能成为可能的科学吗?

  • 我有一个接口,有两个实现。使用哪个实现取决于环境(生产、开发、测试……)。因此,我使用Spring配置文件。我正在使用一个配置文件来实例化正确的实现。 当Spring容器启动时(使用特定的概要文件),正确的bean就会被带入类中,所有的工作都很好。 我还有一个测试类,我想在其中autowire这个bean。我正在使用@mock进行自动化。在测试类中,配置文件被设置为“test-unit”。因此,我