譯-Spring-理解AOP代理

更多請移步: 我的博客

引入

之前寫過一篇關於Spring代理流程的博客,當時沒有深入思考,最近碰到一個有趣的事情,類內部調用帶有spring註解,但註解不生效的問題,舉例說明:

public class SimplePojo implements Pojo {

    public void foo() {
        // this next method invocation is a direct call on the 'this' reference
        this.bar();
    }

    @Transaction
    public void bar() {
        // some logic...
    }
}

外部一個類直接調用SimplePojo.bar()事務是有效的,但如果調用SimplePojo.foo(),那麼bar()方法上的事務不會生效。
查了一下spring文檔,其中特別對AOP代理做了講解,現翻譯記錄。

對照譯文

原文鏈接:https://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html#aop-understanding-aop-proxies

Spring AOP is proxy-based. It is vitally important that you grasp the semantics of what that last statement actually means before you write your own aspects or use any of the Spring AOP-based aspects supplied with the Spring Framework.

Srping AOP基於proxy-based。在我們基於Spring框架自定義切面或者使用Spring基於AOP-based提供的切面之前,理解proxy-based的含義很重要。

Consider first the scenario where you have a plain-vanilla, un-proxied, nothing-special-about-it, straight object reference, as illustrated by the following code snippet.

看下面一個最簡單的調用片段。

public class SimplePojo implements Pojo {

    public void foo() {
        // this next method invocation is a direct call on the 'this' reference
        this.bar();
    }

    public void bar() {
        // some logic...
    }
}

If you invoke a method on an object reference, the method is invoked directly on that object reference, as can be seen below.

如果你使用對象的引用調用其一個方法,這個方法是被直接調用的,看下圖。

直接調用

public class Main {

    public static void main(String[] args) {

        Pojo pojo = new SimplePojo();

        // this is a direct method call on the 'pojo' reference
        pojo.foo();
    }
}

Things change slightly when the reference that client code has is a proxy. Consider the following diagram and code snippet.

當客戶端代碼的引用是代理時,會略有改變。請看下圖和代碼片段。

代理調用

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();

        // this is a method call on the proxy!
        pojo.foo();
    }
}

The key thing to understand here is that the client code inside the main(..) of the Main class has a reference to the proxy. This means that method calls on that object reference will be calls on the proxy, and as such the proxy will be able to delegate to all of the interceptors (advice) that are relevant to that particular method call. However, once the call has finally reached the target object, the SimplePojo reference in this case, any method calls that it may make on itself, such as this.bar() or this.foo(), are going to be invoked against the this reference, and not the proxy. This has important implications. It means that self-invocation is not going to result in the advice associated with a method invocation getting a chance to execute.

着重看下Main類中main方法中對代理的引用。這意味着對該對象引用的方法調用實際調用的是代理,因此代理能夠將調用委託給與該方法調用相關的所有攔截器(advice)。然而,一旦調用最終到達目標對象,例子中的SimplePojo引用,那麼其任何自我方法的調用,例如this.bar()或者this.foo(),都將是直接調用,而不是調用的代理。這點特別重要。這意味着自我調用不會被其相關聯advice不會被執行。

Okay, so what is to be done about this? The best approach (the term best is used loosely here) is to refactor your code such that the self-invocation does not happen. For sure, this does entail some work on your part, but it is the best, least-invasive approach. The next approach is absolutely horrendous, and I am almost reticent to point it out precisely because it is so horrendous. You can (choke!) totally tie the logic within your class to Spring AOP by doing this:

那麼我們要做些什麼呢?最好的方式是重構你自己的代碼使得不會發生自我調用。當然,這需要一些工作量,但這是最好並且低侵入的方式。另外一種方式絕對是可怕的。你可以將你的邏輯與Spring AOP完全耦合,比如:

public class SimplePojo implements Pojo {

    public void foo() {
        // this works, but... gah!
        ((Pojo) AopContext.currentProxy()).bar();
    }

    public void bar() {
        // some logic...
    }
}

This totally couples your code to Spring AOP, and it makes the class itself aware of the fact that it is being used in an AOP context, which flies in the face of AOP. It also requires some additional configuration when the proxy is being created:

這完全將你的代碼與Spring AOP結合在一起,它使得這個類本身就意識到它被用在一個面向AOP的AOP上下文中。它也使用時也需要加入一些配置:

public class Main {

    public static void main(String[] args) {

        ProxyFactory factory = new ProxyFactory(new SimplePojo());
        factory.adddInterface(Pojo.class);
        factory.addAdvice(new RetryAdvice());
        factory.setExposeProxy(true);

        Pojo pojo = (Pojo) factory.getProxy();

        // this is a method call on the proxy!
        pojo.foo();
    }
}

Finally, it must be noted that AspectJ does not have this self-invocation issue because it is not a proxy-based AOP framework.

最後,必須注意的是,AspectJ沒有這種自我調用問題,因爲它不是基於proxy-based的AOP框架。

總結

回顧了下上一篇關於AOP源碼閱讀的博客,其實代碼中已經給出答案。只是當時並沒想這麼多。
代理類實際上是對目標類做了聚合封裝。這也就解釋了爲什麼自我調用時advisor不會被執行。

//AbstractAutoProxyCreator
protected Object createProxy(
            Class<?> beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource) {

        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.copyFrom(this);

        ...

        Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
        for (Advisor advisor : advisors) {
            proxyFactory.addAdvisor(advisor);
        }

        proxyFactory.setTargetSource(targetSource);
        customizeProxyFactory(proxyFactory);

        proxyFactory.setFrozen(this.freezeProxy);
        if (advisorsPreFiltered()) {
            proxyFactory.setPreFiltered(true);
        }

        return proxyFactory.getProxy(getProxyClassLoader());
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章