專治不會看源碼的毛病--spring源碼解析AOP篇


  昨天有個大牛說我啰嗦,眼光比較細碎,看不到重點。太他爺爺的有道理了!要說看人品,還是女孩子強一些。原來記得看到一個男孩子的抱怨,說怎么兩人剛剛開始在一起,女孩子在心里就已經和他過完了一輩子。哥哥們,不想這么遠行嗎?看看何潔,看看帶着倆娃跳樓的媽媽。所以現在的女孩子是很明白的,有些男孩子個子不高,其貌不揚,但是一看那人品氣質就知道能找個不錯的女盆友。不過要說看人的技術能力,男孩子確實更勝一籌,咱得努力了。

  總結一下要形成的習慣:

  1>有空時隔一段時間要做幾道算法題,C語言和JAVA都可以,主要是訓練思維。

       2>定期閱讀spring的源碼。因為spring是框架,重設計,能夠培養大局觀。

  3>閱讀底層的書籍,如linux方面,虛擬機方面,這是內功。越高級的語言只是招式。

  4>不要忘記做了一半的東西,如搜索引擎方面,redis方面,可以過一段時間再做,因為到時候自己的境界有提升,深入程度也會有所增加。

  

  下面是今天的正題。我也很菜,看源碼也很費力,所以都會從最容易的入手。先了解其原理,再去看源碼。看源碼看熟了,以后再遇到問題,就可以通過源碼去了解原理了。spring的AOP,原理懂了,代碼相當簡單。這也是為什么我記得我還是個菜鳥的時候,面試人家經常問我這個。

  先有個大局觀,畫張整體的spring結構圖。以下是備受吐槽的手繪時間:

  如果你覺得我左手字寫的實在是不能再難看了的話,我有空可以展示一下右手字

  天生做不好的兩件事:寫不好字,梳不整齊頭發。自我感覺最近梳頭技術有所改觀。

 

  AOP面向方面編程是面向對象的補充。它利用一種橫切技術,將一些公共行為封裝成叫做“方面”的可重用模塊,解耦,增加可維護性。AOP將系統分為核心關注點和橫切關注點兩部分。核心關注點就是主業務流程,橫切關注點就是上面提到的“方面”。那么看AOP的源碼就是要看橫切關注點是怎樣和核心關注點整合來發揮作用的。

  主業務流程歸根到底是一個java方法,而且是對象的方法。在AOP中被稱為被通知或被代理對象POJO。AOP的作用就是將核心關注點和橫切關注點組合起來,術語叫做“增強”。最后實際用的是增強后的代理對象。

  對核心關注點進行增強就涉及到在哪些地方增強的問題。如方法調用或者異常拋出時做增強這些時機叫做連接點Joinpoint。一個通知將被引發的連接點集合叫做切入點,理解時就可以想正則表達式,通配符來指定多個,而不是單單一個連接點。在連接點都做了哪些增強呢?增強的內容AOP術語叫“通知”Advice。Spring里定義了四種Advice:BeforeAdvice,AfterAdvice,ThrowAdvice,DynamicIntroducationAdvice。許多AOP框架包括spring都是以攔截器作為通知模型。維護一個圍繞連接點的攔截器鏈。其中DynamicIntroducationAdvice是可以引入方法或者字段到核心關注點。這里有個Introduction,AOP術語叫引入。將增強后的AOP代理組裝到系統叫做織入。

  上面就是AOP的核心概念了。總結一下:

方面(Aspect)
連接點(Joinpoint)
通知(Advice)
切入點(Pointcut)
引入(Introduction)
目標對象(Target Object)
AOP代理(AOP Proxy)
織入(Weaving)

  AOP要做的事情就是:生成代理對象,然后織入。

  生成代理對象是經常會被問到的一個問題:Spring提供了兩種方式來生成代理對象,JDKProxy和Cglib。具體使用哪種方式由AopProxyFactory根據AdvisedSupport對象的配置來決定。默認的策略是如果目標類是接口,則使用JDK動態代理技術,否則使用Cglib來生成代理。Cglib是基於字節碼技術的,使用的是ASM。asm是一個java字節碼操縱框架,它能被用來動態生成類或者增強既有類的功能。ASM可以直接產生二進制class文件,也可以在類被加載入JVM之前動態改變類行為。下面重點來看看JDK動態代理技術。這是我還是個很菜很菜的菜鳥時為數不多能看懂的源碼。因為之前看過Java設計模式,寫過類似的例子,所以會比較順暢。今天先講這一部分。

下面是調用測試類: 

package dynamic.proxy; 

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 實現自己的InvocationHandler
 * @author zyb
 * @since 2012-8-9
 *
 */
public class MyInvocationHandler implements InvocationHandler {
    
    // 目標對象 
    private Object target;
    
    /**
     * 構造方法
     * @param target 目標對象 
     */
    public MyInvocationHandler(Object target) {
        super();
        this.target = target;
    }


    /**
     * 執行目標對象的方法
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        
        // 在目標對象的方法執行之前簡單的打印一下
        System.out.println("------------------before------------------");
        
        // 執行目標對象的方法
        Object result = method.invoke(target, args);
        
        // 在目標對象的方法執行之后簡單的打印一下
        System.out.println("-------------------after------------------");
        
        return result;
    }

    /**
     * 獲取目標對象的代理對象
     * @return 代理對象
     */
    public Object getProxy() {
        return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), 
                target.getClass().getInterfaces(), this);
    }
}

package dynamic.proxy;

/**
 * 目標對象實現的接口,用JDK來生成代理對象一定要實現一個接口
 * @author zyb
 * @since 2012-8-9
 *
 */
public interface UserService {

    /**
     * 目標方法 
     */
    public abstract void add();

}

package dynamic.proxy; 

/**
 * 目標對象
 * @author zyb
 * @since 2012-8-9
 *
 */
public class UserServiceImpl implements UserService {

    /* (non-Javadoc)
     * @see dynamic.proxy.UserService#add()
     */
    public void add() {
        System.out.println("--------------------add---------------");
    }
}

package dynamic.proxy; 

import org.junit.Test;

/**
 * 動態代理測試類
 * @author zyb
 * @since 2012-8-9
 *
 */
public class ProxyTest {

    @Test
    public void testProxy() throws Throwable {
        // 實例化目標對象
        UserService userService = new UserServiceImpl();
        
        // 實例化InvocationHandler
        MyInvocationHandler invocationHandler = new MyInvocationHandler(userService);
        
        // 根據目標對象生成代理對象
        UserService proxy = (UserService) invocationHandler.getProxy();
        
        // 調用代理對象的方法
        proxy.add();
        
    }
}
View Code

執行結果如下: 
------------------before------------------ 
--------------------add--------------- 
-------------------after------------------
 

很簡單,核心就是 invocationHandler.getProxy(); 這個方法調用的Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),target.getClass().getInterfaces(), this);  怎么生成對象的。

    /**
     * Returns an instance of a proxy class for the specified interfaces
     * that dispatches method invocations to the specified invocation
     * handler.
     *
     * <p>{@code Proxy.newProxyInstance} throws
     * {@code IllegalArgumentException} for the same reasons that
     * {@code Proxy.getProxyClass} does.
     *
     * @param   loader the class loader to define the proxy class
     * @param   interfaces the list of interfaces for the proxy class
     *          to implement
     * @param   h the invocation handler to dispatch method invocations to
     * @return  a proxy instance with the specified invocation handler of a
     *          proxy class that is defined by the specified class loader
     *          and that implements the specified interfaces
     * @throws  IllegalArgumentException if any of the restrictions on the
     *          parameters that may be passed to {@code getProxyClass}
     *          are violated
     * @throws  SecurityException if a security manager, <em>s</em>, is present
     *          and any of the following conditions is met:
     *          <ul>
     *          <li> the given {@code loader} is {@code null} and
     *               the caller's class loader is not {@code null} and the
     *               invocation of {@link SecurityManager#checkPermission
     *               s.checkPermission} with
     *               {@code RuntimePermission("getClassLoader")} permission
     *               denies access;</li>
     *          <li> for each proxy interface, {@code intf},
     *               the caller's class loader is not the same as or an
     *               ancestor of the class loader for {@code intf} and
     *               invocation of {@link SecurityManager#checkPackageAccess
     *               s.checkPackageAccess()} denies access to {@code intf};</li>
     *          <li> any of the given proxy interfaces is non-public and the
     *               caller class is not in the same {@linkplain Package runtime package}
     *               as the non-public interface and the invocation of
     *               {@link SecurityManager#checkPermission s.checkPermission} with
     *               {@code ReflectPermission("newProxyInPackage.{package name}")}
     *               permission denies access.</li>
     *          </ul>
     * @throws  NullPointerException if the {@code interfaces} array
     *          argument or any of its elements are {@code null}, or
     *          if the invocation handler, {@code h}, is
     *          {@code null}
     */
    @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);

        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /*
         * Look up or generate the designated proxy class.
         */
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }

            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }
View Code

 這個代碼是JDK1.8中的,里面用到了1.8的一些語法,如果不太了解,建議先看看<java8 in action>這本書。代碼看着不少,實際上都在進行一些安全校驗,包裝之類的,真正有用的就兩句: Class<?> cl = getProxyClass0(loader, intfs);這句話查找或者生成代理類。跟進去:

    /**
     * Generate a proxy class.  Must call the checkProxyAccess method
     * to perform permission checks before calling this.
     */
    private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        // If the proxy class defined by the given loader implementing
        // the given interfaces exists, this will simply return the cached copy;
        // otherwise, it will create the proxy class via the ProxyClassFactory
        return proxyClassCache.get(loader, interfaces);
    }
View Code

對,就是從緩存里把接口拿將出來。然后用return cons.newInstance(new Object[]{h});這一句將接口用invocationHandler進行包裝。具體源碼可以跟進去看,不詳述。想必看到這里,JDK動態代理的原理都已經很明白了。這里要說一點理論性的東西:

  AOP解決的問題往往可以用代理模式來解決。Java開發中常說動態代理和靜態代理,而AOP就是動態代理,因為代理的類是在運行時才生成的。而一般說的代理模式寫成的代碼是編譯期就已經生成的,叫靜態代理。

 

介紹我的家鄉

   下面是一如既往的跑題時間:俺是山東人。生在濰坊,長在棗庄。濰坊有一些名勝古跡,文化名人。棗庄也有一些名人,鐵道游擊隊的故鄉。額,但是也有些名聲不太好。特別是我們那個縣,叫“薛城”,是孟嘗君被封的薛地,所以……盛產雞鳴狗盜之徒。傳乾隆下江南路過此地,進一家人家討口水喝,被此人家婆娘打將出來,乾隆怒嗔曰:此地窮山惡水,潑婦刁民。

 

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM