IoC
概念
所謂控制反轉,指的是獲取對象的方式發生了反轉。在傳統面向對象編程中,我們都是在要使用某一個對象實例時創建一個對象實例,對象的控制權在我們自己手里,如果對於一個接口的多個實現類,我們要自己選擇判斷使用具體的實現類,使得我們進行軟件開發耦合度高,維護起來不方便;spring的IOC則是將某一接口的具體實現類的選擇控制權從調用者中移除,轉由spring容器進行控制。在spring初始化對象的關聯關系的過程是通過“依賴注入”實現的。
依賴注入類型
1、 構造器注入;
2、 屬性注入;
3、 接口注入。
實現原理
我們一般是通過ApplicationContext來完成spring中bean的初始化,如下圖所示,這看似簡單的一個創建對象操作,實踐上里面涉及的技術非常復雜。
在實例化context對象時,spring容器做了如下圖所示的操作。
1、 首先通過解析XML配置文件取得bean的配置信息,並將bean注冊到bean定義注冊表里;
2、 將注冊表里的bean根據注冊信息實例化(通過工廠模式+反射),並進行依賴注入,如果是bean依賴,先初始化依賴的bean。
3、 將實例化的bean放入spring容器(bean存放的數據結構本質為map)。
ApplicationContext接口為對BeanFactory接口的擴展。ApplicationContext 在初始化時就把 xml 的配置信息讀入內存,對 XML 文件進行檢驗,如果配置文件沒有錯誤,就創建所有的Bean ,直接為應用程序服務;而BeanFactory是延遲加載,是在調用getBean方法時才進行bean的實例化,如果Bean的某一個屬性沒有注入,BeanFacotry加載后,直至第一次使用調用getBean方法才會拋出異常。
詳細的源碼講解請參考 https://javadoop.com/post/spring-ioc#toc18
設計思想
1、 單例模式
2、 工廠模式
3、 反射思想
使用IoC的好處
1、不用自己組裝,拿來就用。
2、享受單例的好處,效率高,不浪費空間。
3、便於單元測試,方便切換mock組件。
4、便於進行AOP操作,對於使用者是透明的。
5、統一配置,便於修改。
AOP
概念
面向切面編程(AOP),適合那些具有橫切邏輯的應用場合,如性能監測、訪問控制、事務管理及日志記錄。AOP可以說是對OOP的補充和完善。OOP引入封裝、繼承和多態性等概念來建立一種對象層次結構,用以模擬公共行為的一個集合。當我們需要為分散的對象引入公共行為的時候,OOP則顯得無能為力。也就是說,OOP允許你定義從上到下的關系,但並不適合定義從左到右的關系。例如日志功能。日志代碼往往水平地散布在所有對象層次中,而與它所散布到的對象的核心功能毫無關系。在OOP設計中,它導致了大量代碼的重復,而不利於各個模塊的重用。將程序中的交叉業務邏輯(比如安全,日志,事務等),封裝成一個切面,然后注入到目標對象(具體業務邏輯)中去。實現AOP的技術,主要分為兩大類:一是采用動態代理技術,利用截取消息的方式,對該消息進行裝飾,以取代原有對象行為的執行;二是采用靜態織入的方式,引入特定的語法創建“方面”,從而使得編譯器可以在編譯期間織入有關“方面”的代碼。
AOP到底是什么?先看看下圖
觀察上圖,可以發現createForum方法和removeTopic方法除了①②處代碼不一樣外,其他代碼一樣。我們知道,我們開發軟件講究的是“懶”,重復代碼一般都會抽出來形成一個類或方法,但上面示例的我們無法抽出,兩個不同的業務代碼被相同非業務代碼包圍,所以我們無法通過抽象父類進行抽取。這時候,AOP閃亮登場,AOP通過橫向切取(底層通過代理模式實現)將那些重復的代碼抽取出來,讓業務邏輯代碼不摻雜其他非業務邏輯代碼並且可以不改變原來的業務流程,這就是AOP要做的事。這種將分散在各個邏輯代碼的相同代碼通過橫向切取的方式抽取到一個獨立模塊的編程方式就叫AOP。
實現原理
1、 基於JDK的動態代理;下面幾張圖是樣例程序
接口:
實現類
代理類
使用樣例
JDK代理的底層實現過程:
主要在調用Proxy.newProxyInstance過程中會根據傳入的類加載器、接口動態生成一個叫$proxy0類的字節碼,編譯后加入到jvm中。動態生成的過程就是利用java的反射機制獲取的到類加載器的名稱,接口名稱,接口方法等然后拼接成字符串存入磁盤后進行編譯這樣大致的過程。看看下面的反編譯字節碼的結果,在構造方法處使用到了傳入的InvocationHadler實現類MyAOP,然后在doSomething方法是調用MyAOP的invoke方法。

package first_maven; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; public final class $Proxy0 extends Proxy implements TestService { private static Method m1; private static Method m3; private static Method m2; private static Method m0; public $Proxy0(InvocationHandler var1) throws { super(var1); } public final boolean equals(Object var1) throws { try { return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue(); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } public final void doSomething() throws { try { super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final String toString() throws { try { return (String)super.h.invoke(this, m2, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final int hashCode() throws { try { return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue(); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m3 = Class.forName("first_maven.TestService").getMethod("doSomething"); m2 = Class.forName("java.lang.Object").getMethod("toString"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }
參考鏈接 https://www.jianshu.com/p/84ffb8d0a338
jdk動態代理是通過繼承proxy類然后實現接口動態創建代理類,所以不能創建沒有實現接口的類的代理類。
與jdk靜態代理比較:jdk靜態代理只能代理一個接口(代理類內部需存在該接口方法的調用),如果其他接口的實現類也需要類似的增強,只能在創建一個代理類;而動態代理可以代理多個接口(因為代理類內部並不存在對接口實現類的實例方法的直接調用)。
Java靜態代理
2、 基於CGlib的動態代理,下面是樣例程序
代理類
被代理類及使用樣例
底層實現原理請參考
https://www.jianshu.com/p/9a61af393e41?from=timeline&isappinstalled=0
cglib采用的是動態創建子類生成代理對象,所以被代理類不能是final 或 private,但cglib代理的對象並不需要實現接口。
使用aop的好處
- 通知自定義
- 解耦和
- 統一管理權限,統一管理異常拋出
- 不需要我們自己編寫復雜的代理類
相關設計模式
一、工廠模式
1、簡單工廠模式
就是建立一個工廠類,對實現了同一接口的一些類進行實例的創建。簡單工廠模式的實質是由一個工廠類根據傳入的參數,動態決定應該創建哪一個產品類(這些產品類繼承自一個父類或接口)的實例。
運算類接口
運算類實現類
工廠類
使用樣例
當我們需要增加其他運算如乘法或除法時,只需獲取到operateServive就可以進行編寫,而與AddOperateImpl及SubOperateImpl無關,並且這些實現類都有很好的復用性。
優點:
工廠類是整個模式的關鍵。包含了必要的邏輯判斷,根據外界給定的信息,決定究竟應該創建哪個具體類的對象。通過使用工廠類,外界可以從直接創建具體產品對象的尷尬局面擺脫出來,僅僅需要負責“消費”對象就可以了。而不必管這些對象究竟如何創建及如何組織的。明確了各自的職責和權利,有利於整個軟件體系結構的優化。
缺點:
由於工廠類集中了所有實例的創建邏輯,違反了高內聚責任分配原則,將全部創建邏輯集中到了一個工廠類中;它所能創建的類只能是事先考慮到的,如果需要添加新的類,則就需要改變工廠類了。當系統中的具體產品類不斷增多時候,可能會出現要求工廠類根據不同條件創建不同實例的需求.這種對條件的判斷和對具體產品類型的判斷交錯在一起,很難避免模塊功能的蔓延,對系統的維護和擴展非常不利。
(摘抄自 https://www.cnblogs.com/yueguanguanyun/p/9584501.html)
也正是因為當我們增加新的運算時,工廠方法需要增加case分支語句,並且如果有很多“產品”工廠方法里就有大量的代碼,雖然工廠對擴展開放,當同時也開放了修改,這就違反了面向對象的“開放-封閉原則”,於是就有了工廠方法模式。
2、工廠方法模式
定義一個創建對象的接口,讓子類決定實例化哪一個類。工廠方法使一個類的加載延遲到子類。
先看看使用:
抽象工廠
具體工廠:
工廠方法使用樣例
工廠方法優點:
一方面解決了簡單工廠違反“開放-封閉原則”,另一方面某一工廠生產具體某一產品,減輕了工廠類負擔,使得程序維護性更高。
工廠方法缺點:
每加一個產品類,就要相應增加一個產品工廠類,增加了額外開發量;
一個工廠只能生產一個版本的產品,而不能生產多個產品,這時抽象工廠的出現就為解決這個問題。
3、 抽象工廠模式
提供一個創建一系列相關或相互依賴對象的接口,而無需指定他們具體的類。
抽象工廠接口,用戶只需到該工廠取到具體產品而不需要去了解其他方面
Department表類似,這里不再貼出
Mysql生產車間
Oracle生產車間
客戶端使用樣例
抽象工廠優點:
1、 便於交換產品系列,可以方便的在不同產品系列間切換;
2、 具體的創建實例過程與客戶端分離。
抽象工廠缺點:
當我們增加某一類產品時,就要增加每一個生產車間對該類產品的具體實現,同時要把該產品發布到我們的工廠(即在工廠接口提供取得該產品的抽象方法)
二、單例模式
保證一個類只有一個實例,並暴露出一個獲取該實例的渠道(公共靜態方法)
餓漢式
懶漢式
單例模式優點:
1、 由於單例模式在內存中只有一個實例,減少內存開支,特別是一個對象需要頻繁地創建銷毀時,而且創建或銷毀時性能又無法優化,單例模式就非常明顯了;
2、 由於單例模式只生成一個實例,所以,減少系統的性能開銷,當一個對象產生需要比較多的資源時,如讀取配置,產生其他依賴對象時,則可以通過在應用啟動時直接產生一個單例對象,然后永久駐留內存的方式來解決;
3、 單例模式可以避免對資源的多重占用,例如一個寫文件操作,由於只有一個實例存在內存中,避免對同一個資源文件的同時寫操作;
4、 單例模式可以在系統設置全局的訪問點,優化和共享資源訪問,例如,可以設計一個單例類,負責所有數據表的映射處理。
單例模式缺點:
1、 單例模式一般沒有接口,擴展很困難,若要擴展,除了修改代碼基本上沒有第二種途徑可以實現;
2、 單例對象如果持有Context,那么很容易引發內存泄漏,此時需要注意傳遞給單例對象的Context最好是Application Context。
(摘抄自 https://blog.csdn.net/lijizhi19950123/article/details/78150213/ )
主要應用場景
1、 資源共享的情況下,避免由於資源操作時導致的性能或損耗等。如上述中的日志文件,應用配置;
2、 控制資源的情況下,方便資源之間的互相通信。如線程池等
三、 代理模式
為其他對象提供一種代理以控制對這個對象的訪問。
Java中的三種代理模式樣例代碼見上面AOP實現原理模塊。
應用場景:
1、 遠程代理:為一個對象在不同地址空間提供局部代表,這樣可以隱藏一個對象存在於不同地址空間的事實。例如Java的RMI。
2、 虛擬代理:是根據需要創建開銷很大的對象,通過它來存放實例化需要很長時間的真實對象。例如當我們打開一個網頁,考慮到有些網頁圖片比較多,而且某些圖片文件比較大,因此將先以圖標的方式顯示圖片,不同類型的圖片使用不同的圖標,並且在圖標下面標注該圖片的文件名,用戶單擊圖標后可查看真正的圖片,此時代理存放的是圖片的真實路徑,但它並沒有把要代理的圖片存在自己內存中,而是在用戶需要是去被代理里“取貨“。
3、 安全代理:用來控制真實對象訪問時的權限。例如,對某一對象,不同的用戶有不同的訪問權限,那么我們就可以創建擁有不同權限的代理類來進行代理。
4、 智能代理:是指當調用真實的對象時,代理去處理另外一些事情。我對這種代理的理解就是spring中的aop,增強被代理類。