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

为什么Spring-proxy使用委托模式而不是继承+超级?

景俊良
2023-03-14

众所周知,如果没有AspectJ,bean方法的自调用在Spring中是行不通的。

例如,请看这个问题。

class MyClass {

    @Autowired
    private MyClass self; // actually a MyProxy instance

    @Transactional // or any other proxy magic
    public void myMethod() {}

    public void myOtherMethod() {
        this.myMethod(); // or self.myMethod() to avoid self-invokation problem
    }
}

class MyProxy extends MyClass { // or implements MyInterface if proxyMode is not TARGET_CLASS and MyClass also implements MyInterface

    private final MyClass delegate;

    @Override
    public void myMethod() {
        // some proxy magic: caching, transaction management etc
        delegate.myMethod();
        // some proxy magic: caching, transaction management etc
    }

    @Override
    public void myOtherMethod() {
        delegate.myOtherMethod();
    }
}

使用以下代码

public void myOtherMethod() {
    this.myMethod();
}

this.mymethod()将绕过代理(所以所有@transactional@cacheable魔术),因为它只是内部委托的调用...因此,我们应该在myclass中注入一个myclassbean(实际上是myproxy实例),并调用self.mymethod()。这是可以理解的。

但是为什么代理是以这种方式实现的呢?为什么它不只是扩展目标类,重写所有公共方法并调用super而不是delegate?像这样:

class MyProxy extends MyClass {
    // private final MyClass delegate; // no delegate
    @Override
    public void myMethod() {
        // some proxy magic: caching, transaction management etc
        super.myMethod();
        // some proxy magic: caching, transaction management etc
    }
    @Override
    public void myOtherMethod() {
        super.myOtherMethod();
    }
}

它应该可以解决自调用问题,即this.mymethod()绕过代理,因为在本例中,从MyClass.myothermethod()调用的this.mymethod()将调用overriden子方法(MyProxy.mymethod()()。

所以,我的主要问题是为什么不这样实施?

共有1个答案

慕璞
2023-03-14

您认为Spring AOP对其代理使用委托是正确的。这也被记录在案。

使用CGLIB,理论上可以使用proxy.invokesuper()来实现您想要的效果,即自调用由代理的方法拦截器实现的方面注册(我在这里使用的是Spring的嵌入式CGLIB,因此是包名):

package spring.aop;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

class SampleClass {
  public void x() {
    System.out.println("x");
    y();
  }

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

  public static void main(String[] args) {
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(SampleClass.class);
    enhancer.setCallback(new MethodInterceptor() {
      @Override
      public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
        throws Throwable {
        if(method.getDeclaringClass() == Object.class)
          return proxy.invokeSuper(obj, args);
        System.out.println("Before proxy.invokeSuper " + method.getName());
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("After proxy.invokeSuper " + method.getName());
        return result;
      }
    });
    SampleClass proxy = (SampleClass) enhancer.create();
    proxy.x();
  }
}

控制台日志:

Before proxy.invokeSuper x
x
Before proxy.invokeSuper y
y
After proxy.invokeSuper y
After proxy.invokeSuper x

>

  • 选项1:每个方面都有自己的代理。除非您根据方面优先级将代理嵌套到彼此中,否则这显然是行不通的。但是将它们嵌套在一起意味着继承,即一个代理必须从另一个从外到内继承。尝试代理一个CGLIB代理,它不工作,你会得到异常。此外,CGLIB代理非常昂贵,并且使用perm-gen内存,请参见本CGLIB入门中的描述。

    选项2:使用组合而不是继承。构图更加灵活。如果有一个代理,您可以根据需要向其注册方面,那么就可以解决继承问题,但也意味着委托:代理注册方面,并在运行时按照正确的顺序在实际对象的代码执行之前/之后调用它们的方法(如果@around建议从未调用procede())的话,则不调用这些方法)。请参阅Spring手册中关于将方面手动注册到代理的示例:

    // create a factory that can generate a proxy for the given target object
    AspectJProxyFactory factory = new AspectJProxyFactory(targetObject);
    
    // add an aspect, the class must be an @AspectJ aspect
    // you can call this as many times as you need with different aspects
    factory.addAspect(SecurityManager.class);
    
    // you can also add existing aspect instances, the type of the object supplied must be an @AspectJ aspect
    factory.addAspect(usageTracker);
    
    // now get the proxy object...
    MyInterfaceType proxy = factory.getProxy();
    

    至于Spring开发人员为什么选择这种方法,以及是否可以使用one-proxy方法,但仍然要确保自调用像上面我的小CGLIB示例“Logging Aspect”中那样工作,我只能猜测。您也许可以在开发人员邮件列表上询问他们,或者查看源代码。原因可能是CGLIB代理的行为应该与默认的Java动态代理相似,以便在两者之间无缝地切换接口类型。也许原因是另一个。

    我在评论中无意粗鲁,只是直截了当,因为您的问题真的不适合StackOverflow,因为它不是一个可以有人找到解决方案的技术问题。这是一个历史上的设计问题,本质上是哲学问题,因为使用AspectJ,在实际问题之下已经存在一个技术问题(自调用)的解决方案。但是,也许您仍然想深入研究Spring源代码,将Spring AOP实现从deleration更改为proxy.invokesuper()并提交一个拉请求。不过,我不确定这样一个突破性的变化会被接受。

  •  类似资料:
    • 操作步骤: 菜单栏: Refactor —> Replace Inheritance with Delegation...

    • 问题内容: 我查看了Android中SensorManager的源代码,发现当您将侦听器的passs控制注册到时,就会发现。 我仅以此为例。我阅读了有关委托编程的Wikipedia文章,但仍不确定其目的。为什么要使用“委托”?它如何帮助控制程序?使用(或不使用)一个的缺点是什么?与听众一起使用最实用吗? 编辑:在第487行,有问题的方法在第1054行附近。 问题答案: 在GoF书中,委托并不完全是

    • 问题内容: 我不理解javascript中的这种行为来继承,我一直都这样定义它: 但就我而言,这些行: 当我在Spaceship构造函数中执行console.log(this)时,可以看到 proto 属性设置为Spaceship而不是GameObject,如果删除它们,则将其设置为GameObject。 如果我使用: 我对此没有更多问题。之所以阻止我,是因为我有另一个具有add()方法的对象,并

    • 本文向大家介绍C#中的委托是什么?事件是不是一种委托?相关面试题,主要包含被问及C#中的委托是什么?事件是不是一种委托?时的应答技巧和注意事项,需要的朋友参考一下 答: 委托是将一种方法作为参数代入到另一种方法。 是,事件是一种特殊的委托。 //比如:onclick事件中的参数就是一种方法。

    • 问题内容: 偏重于继承而不是继承 是非常流行的短语。我读了几篇文章,最后每篇文章都说 当类之间存在纯IS-A关系时,请使用继承。 本文中的一个示例: 在 Apple 和 Fruit 之间存在明显的IS-A关系,即Apple IS-A Fruit,但作者也将其显示为Apple HAS-A Fruit(组成),以显示通过继承实现时的陷阱。 我在这里变得有些困惑,声明的含义是什么 当类之间存在纯IS-A

    • 本文向大家介绍C#中的委托是什么?事件是不是一种委托?事件和委托的关系。相关面试题,主要包含被问及C#中的委托是什么?事件是不是一种委托?事件和委托的关系。时的应答技巧和注意事项,需要的朋友参考一下 委托可以把一个方法作为参数代入另一个方法。 委托可以理解为指向一个函数的指针。 委托和事件没有可比性,因为委托是类型,事件是对象,下面说的是委托的对象(用委托方式实现的事件)和(标准的event方式实