天天看點

Spring AOP源碼分析(八)SpringAOP要注意的地方

springaop要注意的地方有很多,下面就舉一個,之後想到了再列出來: 

(1)springaop對于最外層的函數隻攔截public方法,不攔截protected和private方法,另外不會對最外層的public方法内部調用的其他方法也進行攔截,即隻停留于代理對象所調用的方法。如下案例: 

b類有兩個public方法,foo1()和foo2(),foo1内部調用了foo2,簡單如下: 

<a href="http://my.oschina.net/pingpangkuangmo/blog/376309#">?</a>

1

2

3

4

5

6

7

8

<code>public</code> <code>void</code> <code>foo2() { </code>

<code>        </code><code>system.out.println(</code><code>"foo2"</code><code>); </code>

<code>    </code><code>} </code>

<code>    </code> 

<code>    </code><code>public</code> <code>void</code> <code>foo1(){</code>

<code>        </code><code>system.out.println(</code><code>"foo1"</code><code>);</code>

<code>        </code><code>this</code><code>.foo2();</code>

<code>    </code><code>}</code>

假如都對這兩個方法進行攔截。當你調用,b對象.foo1()僅僅對foo1整個方法攔截,對于它内部調用的foo2()方法不會進行攔截。 

源碼分析: 

判斷上述this.foo2()方法是否被攔截的最本質的東西是看this到底是誰?有如下對象b類的對象b,和cglib生成的代理對象bproxy,代理對象bproxy内部擁有b。如果調用b對象的任何方法,肯定不會發生任何攔截,當調用bproxy的方法則都會進入攔截函數。 

當我們調用bproxy對象的foo1()方法時,先執行cglib之前設定的callback對象的intercept攔截函數,如下: 

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

<code>public</code> <code>object intercept(object proxy, method method, object[] args, methodproxy methodproxy)</code><code>throws</code> <code>throwable {</code>

<code>            </code><code>object oldproxy =</code><code>null</code><code>;</code>

<code>            </code><code>boolean</code> <code>setproxycontext =</code><code>false</code><code>;</code>

<code>            </code><code>class&lt;?&gt; targetclass =</code><code>null</code><code>;</code>

<code>            </code><code>object target =</code><code>null</code><code>;</code>

<code>            </code><code>try</code> <code>{</code>

<code>                </code><code>if</code> <code>(</code><code>this</code><code>.advised.exposeproxy) {</code>

<code>                    </code><code>// make invocation available if necessary.</code>

<code>                    </code><code>oldproxy = aopcontext.setcurrentproxy(proxy);</code>

<code>                    </code><code>setproxycontext =</code><code>true</code><code>;</code>

<code>                </code><code>}</code>

<code>                </code><code>// may be null. get as late as possible to minimize the time we</code>

<code>                </code><code>// "own" the target, in case it comes from a pool...</code>

<code>                </code><code>target = gettarget();</code>

<code>                </code><code>if</code> <code>(target !=</code><code>null</code><code>) {</code>

<code>                    </code><code>targetclass = target.getclass();</code>

<code>                </code><code>list&lt;object&gt; chain =</code><code>this</code><code>.advised.getinterceptorsanddynamicinterceptionadvice(method, targetclass);</code>

<code>                </code><code>object retval;</code>

<code>                </code><code>// check whether we only have one invokerinterceptor: that is,</code>

<code>                </code><code>// no real advice, but just reflective invocation of the target.</code>

<code>                </code><code>if</code> <code>(chain.isempty() &amp;&amp; modifier.ispublic(method.getmodifiers())) {</code>

<code>                    </code><code>// we can skip creating a methodinvocation: just invoke the target directly.</code>

<code>                    </code><code>// note that the final invoker must be an invokerinterceptor, so we know</code>

<code>                    </code><code>// it does nothing but a reflective operation on the target, and no hot</code>

<code>                    </code><code>// swapping or fancy proxying.</code>

<code>                    </code><code>retval = methodproxy.invoke(target, args);</code>

<code>                </code><code>else</code> <code>{</code>

<code>                    </code><code>// we need to create a method invocation...</code>

<code>                    </code><code>retval =</code><code>new</code> <code>cglibmethodinvocation(proxy, target, method, args, targetclass, chain, methodproxy).proceed();</code>

<code>                </code><code>retval = processreturntype(proxy, target, method, retval);</code>

<code>                </code><code>return</code> <code>retval;</code>

<code>            </code><code>}</code>

<code>            </code><code>finally</code> <code>{</code>

<code>                    </code><code>releasetarget(target);</code>

<code>                </code><code>if</code> <code>(setproxycontext) {</code>

<code>                    </code><code>// restore old proxy.</code>

<code>                    </code><code>aopcontext.setcurrentproxy(oldproxy);</code>

<code>        </code><code>}</code>

這個過程之前的文章已經分析過,這裡就是首先取出攔截器鍊list&lt;object&gt; chain,當foo1方法不符合我們所配置的pointcut時,攔截器鍊必然為空,然後就是直接執行目标對象的方法。 

當foo1方法符合所配置的pointcut時,攔截器鍊不為空,執行相應的通知advice,currentinterceptorindex 從-1開始,如下: 

<code>public</code> <code>object proceed()</code><code>throws</code> <code>throwable {</code>

<code>        </code><code>//  we start with an index of -1 and increment early.</code>

<code>        </code><code>if</code> <code>(</code><code>this</code><code>.currentinterceptorindex ==</code><code>this</code><code>.interceptorsanddynamicmethodmatchers.size() -</code><code>1</code><code>) {</code>

<code>            </code><code>return</code> <code>invokejoinpoint();</code>

<code>        </code><code>object interceptororinterceptionadvice =</code>

<code>                </code><code>this</code><code>.interceptorsanddynamicmethodmatchers.get(++</code><code>this</code><code>.currentinterceptorindex);</code>

<code>        </code><code>if</code> <code>(interceptororinterceptionadvice</code><code>instanceof</code> <code>interceptoranddynamicmethodmatcher) {</code>

<code>            </code><code>// evaluate dynamic method matcher here: static part will already have</code>

<code>            </code><code>// been evaluated and found to match.</code>

<code>            </code><code>interceptoranddynamicmethodmatcher dm =</code>

<code>                    </code><code>(interceptoranddynamicmethodmatcher) interceptororinterceptionadvice;</code>

<code>            </code><code>if</code> <code>(dm.methodmatcher.matches(</code><code>this</code><code>.method,</code><code>this</code><code>.targetclass,</code><code>this</code><code>.arguments)) {</code>

<code>                </code><code>return</code> <code>dm.interceptor.invoke(</code><code>this</code><code>);</code>

<code>            </code><code>else</code> <code>{</code>

<code>                </code><code>// dynamic matching failed.</code>

<code>                </code><code>// skip this interceptor and invoke the next in the chain.</code>

<code>                </code><code>return</code> <code>proceed();</code>

<code>        </code><code>else</code> <code>{</code>

<code>            </code><code>// it's an interceptor, so we just invoke it: the pointcut will have</code>

<code>            </code><code>// been evaluated statically before this object was constructed.</code>

<code>            </code><code>return</code> <code>((methodinterceptor) interceptororinterceptionadvice).invoke(</code><code>this</code><code>);</code>

随着通知不斷的傳遞執行,最終this.currentinterceptorindex == this.interceptorsanddynamicmethodmatchers.size() - 1将會滿足條件,将會來到執行目标對象的方法invokejoinpoint(): 

<code>protected</code> <code>object invokejoinpoint()</code><code>throws</code> <code>throwable {</code>

<code>            </code><code>if</code> <code>(</code><code>this</code><code>.publicmethod) {</code>

<code>                </code><code>return</code> <code>this</code><code>.methodproxy.invoke(</code><code>this</code><code>.target,</code><code>this</code><code>.arguments);</code>

<code>                </code><code>return</code> <code>super</code><code>.invokejoinpoint();</code>

在這裡不管要攔截的目标方法是不是public方法,最終所傳遞的對象都是this.target,他是目标對象而不是代理對象,即執行上述foo1()函數的對象是目标對象而不是代理對象,是以它内部所調用的this.foo2()也是目标對象,是以不會發生攔截,如果是執行的是代理對象.foo2()則必然會進入intercept攔截過程。是以上述調用foo1函數,其内部調用的foo2函數是不會發生攔截的,因為this指的是目标對象,不是代理對象。 

如果你想實作foo1調用時内部的foo2也進行攔截,就必須把this換成代理對象。這時就要用到了,xml配置中的expose-proxy="true",即暴露出代理對象,它使用的是threadlocal設計模式,我們可以這樣擷取代理對象,aopcontext.currentproxy()就是代理對象,然後轉換成目标對象或者目标接口,執行相應的方法: 

<code>public</code> <code>void</code> <code>foo1(){</code>

<code>        </code><code>system.out.println(</code><code>"run foo1"</code><code>);</code>

<code>        </code><code>bserviceimpl proxy=(bserviceimpl) aopcontext.currentproxy();</code>

<code>        </code><code>proxy.foo2();</code>

最後再給出spring的文檔說明: 

due to the proxy-based nature of spring’s aop framework, protected methods are by definition not intercepted, neither for jdk proxies (where this isn’t applicable) nor for cglib proxies (where this is technically possible but not recommendable for aop purposes). as a consequence, any given pointcut will be matched against public methods only! 

if your interception needs include protected/private methods or even constructors, consider the use of spring-driven native aspectj weaving instead of spring’s proxy-based aop framework. this constitutes a different mode of aop usage with different characteristics, so be sure to make yourself familiar with weaving first before making a decision.