Spring AOP源碼解析——專治你不會(huì)看源碼的壞毛病!

image

昨天有個(gè)大牛說我啰嗦,眼光比較細(xì)碎,看不到重點(diǎn)。太他爺爺?shù)挠械览砹耍∫f看人品,還是女孩子強(qiáng)一些。

原來記得看到一個(gè)男孩子的抱怨,說怎么兩人剛剛開始在一起,女孩子在心里就已經(jīng)和他過完了一輩子。哥哥們,不想這么遠(yuǎn)行嗎?看看何潔,看看帶著倆娃跳樓的媽媽。

所以現(xiàn)在的女孩子是很明白的,有些男孩子個(gè)子不高,其貌不揚(yáng),但是一看那人品氣質(zhì)就知道能找個(gè)不錯(cuò)的女盆友。不過要說看人的技術(shù)能力,男孩子確實(shí)更勝一籌,咱得努力了。

總結(jié)一下要形成的習(xí)慣:

  1. 有空時(shí)隔一段時(shí)間要做幾道算法題,C語言和JAVA都可以,主要是訓(xùn)練思維。
  2. 定期閱讀spring的源碼。因?yàn)閟pring是框架,重設(shè)計(jì),能夠培養(yǎng)大局觀
  3. 閱讀底層的書籍,如linux方面,虛擬機(jī)方面,這是內(nèi)功。越高級(jí)的語言只是招式。
  4. 不要忘記做了一半的東西,如搜索引擎方面,redis方面,可以過一段時(shí)間再做,因?yàn)榈綍r(shí)候自己的境界有提升,深入程度也會(huì)有所增加。

下面是今天的正題。我也很菜,看源碼也很費(fèi)力,所以都會(huì)從最容易的入手。先了解其原理,再去看源碼。

看源碼看熟了,以后再遇到問題,就可以通過源碼去了解原理了。

spring的AOP,原理懂了,代碼相當(dāng)簡單。這也是為什么我記得我還是個(gè)菜鳥的時(shí)候,面試人家經(jīng)常問我這個(gè)。

先有個(gè)大局觀,畫張整體的spring結(jié)構(gòu)圖。以下是備受吐槽的手繪時(shí)間:

image

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

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

AOP面向切面編程是面向?qū)ο蟮难a(bǔ)充。它利用一種橫切技術(shù),將一些公共行為封裝成叫做“方面”的可重用模塊,解耦,增加可維護(hù)性。

AOP將系統(tǒng)分為核心關(guān)注點(diǎn)和橫切關(guān)注點(diǎn)兩部分。核心關(guān)注點(diǎn)就是主業(yè)務(wù)流程,橫切關(guān)注點(diǎn)就是上面提到的“方面”。

那么看AOP的源碼就是要看橫切關(guān)注點(diǎn)是怎樣和核心關(guān)注點(diǎn)整合來發(fā)揮作用的。

主業(yè)務(wù)流程歸根到底是一個(gè)java方法,而且是對(duì)象的方法。在AOP中被稱為被通知或被代理對(duì)象POJO。

AOP的作用就是將核心關(guān)注點(diǎn)和橫切關(guān)注點(diǎn)組合起來,術(shù)語叫做“增強(qiáng)”。最后實(shí)際用的是增強(qiáng)后的代理對(duì)象。

對(duì)核心關(guān)注點(diǎn)進(jìn)行增強(qiáng)就涉及到在哪些地方增強(qiáng)的問題。如方法調(diào)用或者異常拋出時(shí)做增強(qiáng)這些時(shí)機(jī)叫做連接點(diǎn)Joinpoint。一個(gè)通知將被引發(fā)的連接點(diǎn)集合叫做切入點(diǎn),理解時(shí)就可以想正則表達(dá)式,通配符來指定多個(gè),而不是單單一個(gè)連接點(diǎn)。

在連接點(diǎn)都做了哪些增強(qiáng)呢?增強(qiáng)的內(nèi)容AOP術(shù)語叫“通知”Advice。

Spring里定義了四種Advice:BeforeAdvice,AfterAdvice,ThrowAdvice,DynamicIntroducationAdvice。

許多AOP框架包括spring都是以攔截器作為通知模型。維護(hù)一個(gè)圍繞連接點(diǎn)的攔截器鏈。其中DynamicIntroducationAdvice是可以引入方法或者字段到核心關(guān)注點(diǎn)。

這里有個(gè)Introduction,AOP術(shù)語叫引入。將增強(qiáng)后的AOP代理組裝到系統(tǒng)叫做織入。

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

image

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

生成代理對(duì)象是經(jīng)常會(huì)被問到的一個(gè)問題:Spring提供了兩種方式來生成代理對(duì)象,JDKProxy和Cglib。

具體使用哪種方式由AopProxyFactory根據(jù)AdvisedSupport對(duì)象的配置來決定。

默認(rèn)的策略是如果目標(biāo)類是接口,則使用JDK動(dòng)態(tài)代理技術(shù),否則使用Cglib來生成代理。

Cglib是基于字節(jié)碼技術(shù)的,使用的是ASM。asm是一個(gè)java字節(jié)碼操縱框架,它能被用來動(dòng)態(tài)生成類或者增強(qiáng)既有類的功能。

ASM可以直接產(chǎn)生二進(jìn)制class文件,也可以在類被加載入JVM之前動(dòng)態(tài)改變類行為。

下面重點(diǎn)來看看JDK動(dòng)態(tài)代理技術(shù)。這是我還是個(gè)很菜很菜的菜鳥時(shí)為數(shù)不多能看懂的源碼。因?yàn)橹翱催^Java設(shè)計(jì)模式,寫過類似的例子,所以會(huì)比較順暢。今天先講這一部分。

下面是調(diào)用測試類:

package dynamic.proxy; 
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
 * 實(shí)現(xiàn)自己的InvocationHandler
 * @author zyb
 * @since 2012-8-9
 *
 */
public class MyInvocationHandler implements InvocationHandler {
 // 目標(biāo)對(duì)象 
 private Object target;
 /**
 * 構(gòu)造方法
 * @param target 目標(biāo)對(duì)象 
 */
 public MyInvocationHandler(Object target) {
 super();
 this.target = target;
 }
 /**
 * 執(zhí)行目標(biāo)對(duì)象的方法
 */
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 // 在目標(biāo)對(duì)象的方法執(zhí)行之前簡單的打印一下
 System.out.println("------------------before------------------");
 // 執(zhí)行目標(biāo)對(duì)象的方法
 Object result = method.invoke(target, args);
 // 在目標(biāo)對(duì)象的方法執(zhí)行之后簡單的打印一下
 System.out.println("-------------------after------------------");
 return result;
 }
 /**
 * 獲取目標(biāo)對(duì)象的代理對(duì)象
 * @return 代理對(duì)象
 */
 public Object getProxy() {
 return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), 
 target.getClass().getInterfaces(), this);
 }
}
package dynamic.proxy;
/**
 * 目標(biāo)對(duì)象實(shí)現(xiàn)的接口,用JDK來生成代理對(duì)象一定要實(shí)現(xiàn)一個(gè)接口
 * @author zyb
 * @since 2012-8-9
 *
 */
public interface UserService {
 /**
 * 目標(biāo)方法 
 */
 public abstract void add();
}
package dynamic.proxy; 
/**
 * 目標(biāo)對(duì)象
 * @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;
/**
 * 動(dòng)態(tài)代理測試類
 * @author zyb
 * @since 2012-8-9
 *
 */
public class ProxyTest {
 @Test
 public void testProxy() throws Throwable {
 // 實(shí)例化目標(biāo)對(duì)象
 UserService userService = new UserServiceImpl();
 // 實(shí)例化InvocationHandler
 MyInvocationHandler invocationHandler = new MyInvocationHandler(userService);
 // 根據(jù)目標(biāo)對(duì)象生成代理對(duì)象
 UserService proxy = (UserService) invocationHandler.getProxy();
 // 調(diào)用代理對(duì)象的方法
 proxy.add();
 }
}

執(zhí)行結(jié)果如下:

------------------before---------------

--------------------add---------------

-------------------after-----------------

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

/**
 * Returns an instance of a proxy class for the specified interfaces
 * that dispatches method invocations to the specified invocation
 * handler.
 *
 * 
{@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, s, is present
 * and any of the following conditions is met:
 * 
 * 
 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;
 * 
 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};
 * 
 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.
 * 
 * @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() {
 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);
 }
 }

最后附上這四個(gè)項(xiàng)目的百度網(wǎng)盤地址:鏈接:https://pan.baidu.com/s/1Q1R9I7IkQJ78sJUaokD04這個(gè)代碼是JDK1.8中的,里面用到了1.8的一些語法,如果不太了解,建議先看看這本書。

代碼看著不少,實(shí)際上都在進(jìn)行一些安全校驗(yàn),包裝之類的,真正有用的就兩句:

Class cl = getProxyClass0(loader, intfs);這句話查找或者生成代理類。跟進(jìn)去:

/**
 * 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);
 }

對(duì),就是從緩存里把接口拿將出來。然后用return cons.newInstance(new Object[]{h}) 這一句將接口用invocationHandler進(jìn)行包裝。

具體源碼可以跟進(jìn)去看,不詳述。想必看到這里,JDK動(dòng)態(tài)代理的原理都已經(jīng)很明白了。

這里要說一點(diǎn)理論性的東西:

  • AOP解決的問題往往可以用代理模式來解決。Java開發(fā)中常說動(dòng)態(tài)代理和靜態(tài)代理,而AOP就是動(dòng)態(tài)代理,因?yàn)榇淼念愂窃谶\(yùn)行時(shí)才生成的。
  • 而一般說的代理模式寫成的代碼是編譯期就已經(jīng)生成的,叫靜態(tài)代理。

最后

針對(duì)于上面的面試題我總結(jié)出了互聯(lián)網(wǎng)公司java程序員面試涉及到的絕大部分面試題及答案做成了文檔和架構(gòu)視頻資料免費(fèi)分享給大家(包括Dubbo、Redis、Netty、zookeeper、Spring cloud、分布式、高并發(fā)等架構(gòu)技術(shù)資料),希望能幫助到您面試前的復(fù)習(xí)且找到一個(gè)好的工作,也節(jié)省大家在網(wǎng)上搜索資料的時(shí)間來學(xué)習(xí).

資料領(lǐng)取方式:

加群:219571750,領(lǐng)取往期Java高級(jí)架構(gòu)資料、源碼、筆記、視頻

Dubbo、Redis、設(shè)計(jì)模式、Netty、zookeeper、Spring cloud、分布式、

高并發(fā)等架構(gòu)技術(shù)

image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 前言 只有光頭才能變強(qiáng) 上一篇已經(jīng)講解了Spring IOC知識(shí)點(diǎn)一網(wǎng)打盡!,這篇主要是講解Spring的AOP模...
    Java3y閱讀 7,028評(píng)論 8 181
  • AOP實(shí)現(xiàn)可分為兩類(按AOP框架修改源代碼的時(shí)機(jī)): 靜態(tài)AOP實(shí)現(xiàn):AOP框架在編譯階段對(duì)程序進(jìn)行修改,即實(shí)現(xiàn)...
    數(shù)獨(dú)題閱讀 2,401評(píng)論 0 22
  • 本文主要講實(shí)現(xiàn)AOP的 代理模式原理,以及靜態(tài)代理,動(dòng)態(tài)代理的區(qū)別和具體實(shí)現(xiàn)。 對(duì)SpringAOP的概念和使用,...
    _Zy閱讀 812評(píng)論 0 1
  • 今天討論的是有關(guān)政治的話題,然而到說的時(shí)候才發(fā)現(xiàn)對(duì)政治的了解之少,思考之淺薄,簡單的詞匯也并不會(huì)表達(dá),亟待提高。 ...
    claresun閱讀 218評(píng)論 0 0
  • 文 | 網(wǎng)絡(luò) (侵刪) 1. 并不是所有的人都理解和支持我們的建設(shè),但并不能否認(rèn)它的偉大。我們的作家,神圣的信仰,...
    且以滄海寄余生閱讀 452評(píng)論 4 3

友情鏈接更多精彩內(nèi)容