javascript
Spring AOP 源码分析 - 创建代理对象
1.簡介
在上一篇文章中,我分析了 Spring 是如何為目標 bean 篩選合適的通知器的。現在通知器選好了,接下來就要通過代理的方式將通知器(Advisor)所持有的通知(Advice)織入到 bean 的某些方法前后。與篩選合適的通知器相比,創建代理對象的過程則要簡單不少,本文所分析的源碼不過100行,相對比較簡單。在接下里的章節中,我將會首先向大家介紹一些背景知識,然后再去分析源碼。那下面,我們先來了解一下背景知識。
?2.背景知識
?2.1 proxy-target-class
在 Spring AOP 配置中,proxy-target-class 屬性可影響 Spring 生成的代理對象的類型。以 XML 配置為例,可進行如下配置:
| 1 2 3 4 5 6 7 | <aop:aspectj-autoproxy proxy-target-class="true"/><aop:config proxy-target-class="true"><aop:aspect id="xxx" ref="xxxx"><!-- 省略 --></aop:aspect> </aop:config> |
如上,默認情況下 proxy-target-class 屬性為 false。當目標 bean 實現了接口時,Spring 會基于 JDK 動態代理為目標 bean 創建代理對象。若未實現任何接口,Spring 則會通過 CGLIB 創建代理。而當 proxy-target-class 屬性設為 true 時,則會強制 Spring 通過 CGLIB 的方式創建代理對象,即使目標 bean 實現了接口。
關于 proxy-target-class 屬性的用途這里就說完了,下面我們來看看兩種不同創建動態代理的方式。
?2.2 動態代理
?2.2.1 基于 JDK 的動態代理
基于 JDK 的動態代理主要是通過 JDK 提供的代理創建類 Proxy 為目標對象創建代理,下面我們來看一下 Proxy 中創建代理的方法聲明。如下:
| 1 2 3 | public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) |
簡單說一下上面的參數列表:
- loader - 類加載器
- interfaces - 目標類所實現的接口列表
- h - 用于封裝代理邏輯
JDK 動態代理對目標類是有一定要求的,即要求目標類必須實現了接口,JDK 動態代理只能為實現了接口的目標類生成代理對象。至于 InvocationHandler,是一個接口類型,定義了一個 invoke 方法。使用者需要實現該方法,并在其中封裝代理邏輯。
關于 JDK 動態代理的介紹,就先說到這。下面我來演示一下 JDK 動態代理的使用方式,如下:
目標類定義:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | public interface UserService {void save(User user);void update(User user); }public class UserServiceImpl implements UserService {@Overridepublic void save(User user) {System.out.println("save user info");}@Overridepublic void update(User user) {System.out.println("update user info");} } |
代理創建者定義:
| 1 2 3 4 5 6 7 8 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 | public interface ProxyCreator {Object getProxy(); }public class JdkProxyCreator implements ProxyCreator, InvocationHandler {private Object target;public JdkProxyCreator(Object target) {assert target != null;Class<?>[] interfaces = target.getClass().getInterfaces();if (interfaces.length == 0) {throw new IllegalArgumentException("target class don`t implement any interface");}this.target = target;}@Overridepublic Object getProxy() {Class<?> clazz = target.getClass();// 生成代理對象return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println(System.currentTimeMillis() + " - " + method.getName() + " method start");// 調用目標方法Object retVal = method.invoke(target, args);System.out.println(System.currentTimeMillis() + " - " + method.getName() + " method over");return retVal;} } |
如上,invoke 方法中的代理邏輯主要用于記錄目標方法的調用時間,和結束時間。下面寫點測試代碼簡單驗證一下,如下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public class JdkProxyCreatorTest {@Testpublic void getProxy() throws Exception {ProxyCreator proxyCreator = new JdkProxyCreator(new UserServiceImpl());UserService userService = (UserService) proxyCreator.getProxy();System.out.println("proxy type = " + userService.getClass());System.out.println();userService.save(null);System.out.println();userService.update(null);} } |
測試結果如下:
如上,從測試結果中。我們可以看出,我們的代理邏輯正常執行了。另外,注意一下 userService 指向對象的類型,并非是 xyz.coolblog.proxy.UserServiceImpl,而是 com.sun.proxy.$Proxy4。
關于 JDK 動態代理,這里先說這么多。下一節,我來演示一下 CGLIB 動態代理,繼續往下看吧。
?2.2.2 基于 CGLIB 的動態代理
當我們要為未實現接口的類生成代理時,就無法使用 JDK 動態代理了。那么此類的目標對象生成代理時應該怎么辦呢?當然是使用 CGLIB 了。在 CGLIB 中,代理邏輯是封裝在 MethodInterceptor 實現類中的,代理對象則是通過 Enhancer 類的 create 方法進行創建。下面我來演示一下 CGLIB 創建代理對象的過程,如下:
本節的演示環節,打算調侃(無貶低之意)一下59式坦克,這是我們國家大量裝備過的一款坦克。59式坦克有很多種改款,一般把改款統稱為59改,59改這個梗也正是源于此。下面我們先來一覽59式坦克的風采:
圖片來源:百度圖片搜索
下面我們的工作就是為咱們的 59 創建一個代理,即 59改。好了,開始我們的魔改吧。
目標類,59式坦克:
| 1 2 3 4 5 6 7 8 9 10 | public class Tank59 {void run() {System.out.println("極速前行中....");}void shoot() {System.out.println("轟...轟...轟...轟...");} } |
CGLIB 代理創建者
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | public class CglibProxyCreator implements ProxyCreator {private Object target;private MethodInterceptor methodInterceptor;public CglibProxyCreator(Object target, MethodInterceptor methodInterceptor) {assert (target != null && methodInterceptor != null);this.target = target;this.methodInterceptor = methodInterceptor;}@Overridepublic Object getProxy() {Enhancer enhancer = new Enhancer();// 設置代理類的父類enhancer.setSuperclass(target.getClass());// 設置代理邏輯enhancer.setCallback(methodInterceptor);// 創建代理對象return enhancer.create();} } |
方法攔截器 - 坦克再制造:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public class TankRemanufacture implements MethodInterceptor {@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {if (method.getName().equals("run")) {System.out.println("正在重造59坦克...");System.out.println("重造成功,已獲取 ?59改 之 超音速飛行版?");System.out.print("已起飛,正在突破音障。");methodProxy.invokeSuper(o, objects);System.out.println("已擊落黑鳥 SR-71,正在返航...");return null;}return methodProxy.invokeSuper(o, objects);} } |
好了,下面開始演示,測試代碼如下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public class CglibProxyCreatorTest {@Testpublic void getProxy() throws Exception {ProxyCreator proxyCreator = new CglibProxyCreator(new Tank59(), new TankRemanufacture());Tank59 tank59 = (Tank59) proxyCreator.getProxy();System.out.println("proxy class = " + tank59.getClass() + "\n");tank59.run();System.out.println();System.out.print("射擊測試:");tank59.shoot();} } |
測試結果如下:
如上,“極速前行中…” 和 “轟…轟…轟…轟…” 這兩行字符串是目標對象中的方法打印出來的,其他的則是由代理邏輯打印的。由此可知,我們的代理邏輯生效了。
好了,最后我們來看一下,經過魔改后的 59,也就是超音速59改的效果圖:
圖片來源:未知
本節用59式坦克舉例,僅是調侃,并無惡意。作為年輕的一代,我們應感謝那些為國防事業做出貢獻的科技人員們。沒有他們貢獻,我們怕是不會有像今天這樣安全的環境了(盡管不完美)。
到此,背景知識就介紹完了。下一章,我將開始分析源碼。源碼不是很長,主邏輯比較容易懂,所以一起往下看吧。
?3.源碼分析
為目標 bean 創建代理對象前,需要先創建 AopProxy 對象,然后再調用該對象的 getProxy 方法創建實際的代理類。我們先來看看 AopProxy 這個接口的定義,如下:
| 1 2 3 4 5 6 7 | public interface AopProxy {/** 創建代理對象 */Object getProxy();Object getProxy(ClassLoader classLoader); } |
在 Spring 中,有兩個類實現了 AopProxy,如下:
Spring 在為目標 bean 創建代理的過程中,要根據 bean 是否實現接口,以及一些其他配置來決定使用 AopProxy 何種實現類為目標 bean 創建代理對象。下面我們就來看一下代理創建的過程,如下:
| 1 2 3 4 5 6 7 8 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 46 | protected Object createProxy(Class<?> beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource) {if (this.beanFactory instanceof ConfigurableListableBeanFactory) {AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);}ProxyFactory proxyFactory = new ProxyFactory();proxyFactory.copyFrom(this);/** 默認配置下,或用戶顯式配置 proxy-target-class = "false" 時,* 這里的 proxyFactory.isProxyTargetClass() 也為 false*/if (!proxyFactory.isProxyTargetClass()) {if (shouldProxyTargetClass(beanClass, beanName)) {proxyFactory.setProxyTargetClass(true);}else {/** 檢測 beanClass 是否實現了接口,若未實現,則將 * proxyFactory 的成員變量 proxyTargetClass 設為 true*/ evaluateProxyInterfaces(beanClass, proxyFactory);}}// specificInterceptors 中若包含有 Advice,此處將 Advice 轉為 AdvisorAdvisor[] advisors = buildAdvisors(beanName, specificInterceptors);proxyFactory.addAdvisors(advisors);proxyFactory.setTargetSource(targetSource);customizeProxyFactory(proxyFactory);proxyFactory.setFrozen(this.freezeProxy);if (advisorsPreFiltered()) {proxyFactory.setPreFiltered(true);}// 創建代理return proxyFactory.getProxy(getProxyClassLoader()); }public Object getProxy(ClassLoader classLoader) {// 先創建 AopProxy 實現類對象,然后再調用 getProxy 為目標 bean 創建代理對象return createAopProxy().getProxy(classLoader); } |
getProxy 這里有兩個方法調用,一個是調用 createAopProxy 創建 AopProxy 實現類對象,然后再調用 AopProxy 實現類對象中的 getProxy 創建代理對象。這里我們先來看一下創建 AopProxy 實現類對象的過程,如下:
| 1 2 3 4 5 6 7 8 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 | protected final synchronized AopProxy createAopProxy() {if (!this.active) {activate();}return getAopProxyFactory().createAopProxy(this); }public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {@Overridepublic AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {/** 下面的三個條件簡單分析一下:** 條件1:config.isOptimize() - 是否需要優化,這個屬性沒怎么用過,* 細節我不是很清楚* 條件2:config.isProxyTargetClass() - 檢測 proxyTargetClass 的值,* 前面的代碼會設置這個值* 條件3:hasNoUserSuppliedProxyInterfaces(config) * - 目標 bean 是否實現了接口*/if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {Class<?> targetClass = config.getTargetClass();if (targetClass == null) {throw new AopConfigException("TargetSource cannot determine target class: " +"Either an interface or a target is required for proxy creation.");}if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {return new JdkDynamicAopProxy(config);}// 創建 CGLIB 代理,ObjenesisCglibAopProxy 繼承自 CglibAopProxyreturn new ObjenesisCglibAopProxy(config);}else {// 創建 JDK 動態代理return new JdkDynamicAopProxy(config);}} } |
如上,DefaultAopProxyFactory 根據一些條件決定生成什么類型的 AopProxy 實現類對象。生成好 AopProxy 實現類對象后,下面就要為目標 bean 創建代理對象了。這里以 JdkDynamicAopProxy 為例,我們來看一下,該類的 getProxy 方法的邏輯是怎樣的。如下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public Object getProxy() {return getProxy(ClassUtils.getDefaultClassLoader()); }public Object getProxy(ClassLoader classLoader) {if (logger.isDebugEnabled()) {logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());}Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);// 調用 newProxyInstance 創建代理對象return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this); } |
如上,請把目光移至最后一行有效代碼上,會發現 JdkDynamicAopProxy 最終調用 Proxy.newProxyInstance 方法創建代理對象。到此,創建代理對象的整個過程也就分析完了,不知大家看懂了沒。好了,關于創建代理的源碼分析,就先說到這里吧。
?4.總結
本篇文章對 Spring AOP 創建代理對象的過程進行了較為詳細的分析,并在分析源碼前介紹了相關的背景知識。總的來說,本篇文章涉及的技術點不是很復雜,相信大家都能看懂。限于個人能力,若文中有錯誤的地方,歡迎大家指出來。好了,本篇文章到此結束,謝謝閱讀。
?參考
- 《Spring 源碼深度解析》- 郝佳
?附錄:Spring 源碼分析文章列表
?Ⅰ. IOC
| 2018-05-30 | Spring IOC 容器源碼分析系列文章導讀 |
| 2018-06-01 | Spring IOC 容器源碼分析 - 獲取單例 bean |
| 2018-06-04 | Spring IOC 容器源碼分析 - 創建單例 bean 的過程 |
| 2018-06-06 | Spring IOC 容器源碼分析 - 創建原始 bean 對象 |
| 2018-06-08 | Spring IOC 容器源碼分析 - 循環依賴的解決辦法 |
| 2018-06-11 | Spring IOC 容器源碼分析 - 填充屬性到 bean 原始對象 |
| 2018-06-11 | Spring IOC 容器源碼分析 - 余下的初始化工作 |
?Ⅱ. AOP
| 2018-06-17 | Spring AOP 源碼分析系列文章導讀 |
| 2018-06-20 | Spring AOP 源碼分析 - 篩選合適的通知器 |
| 2018-06-20 | Spring AOP 源碼分析 - 創建代理對象 |
| 2018-06-22 | Spring AOP 源碼分析 - 攔截器鏈的執行過程 |
?Ⅲ. MVC
| 2018-06-29 | Spring MVC 原理探秘 - 一個請求的旅行過程 |
| 2018-06-30 | Spring MVC 原理探秘 - 容器的創建過程 |
- 本文鏈接:?https://www.tianxiaobo.com/2018/06/20/Spring-AOP-源碼分析-創建代理對象/
http://www.tianxiaobo.com/2018/06/20/Spring-AOP-%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90-%E5%88%9B%E5%BB%BA%E4%BB%A3%E7%90%86%E5%AF%B9%E8%B1%A1/?
總結
以上是生活随笔為你收集整理的Spring AOP 源码分析 - 创建代理对象的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring AOP 源码分析 - 筛选
- 下一篇: Spring MVC 原理探秘 - 容器