[TOC]
本文改編自[Spring的本質系列](https://mp.weixin.qq.com/s?__biz=MzAxOTc0NzExNg==&mid=2665513179&idx=1&sn=772226a5be436a0d08197c335ddb52b8&scene=21#wechat_redirect)
## 對象的創建
面向對象的編程語言是用類 (Class) 來對現實世界進行抽象, 在運行時這些類會生成對象(Object)。當然單獨的對象沒有辦法完成復雜的業務,實際系統是由千千萬萬個對象組成的,而且他們需要相互協作。那么對象A入耳才能獲得對象B的引用呢?
最簡單的方法無非是,當對象A需要使用B的時候,才把它**new**出來。例如
```
Apple a = new Apple();
```
后來業務更復雜了,就抽象出一個Fruit類,創建的對象都賦給fruit的一個實例。
~~~
Fruit f1 = new Apple();
Fruit f2 = new Banana();
Fruit f3 = ......
~~~
這樣的代碼如果散落在各處,維護起來將會痛苦不堪。
那么也可以能出現如下的工廠類

工廠模式,以及抽象工廠, Builder 模式提供的都是創建對象的方法。背后體現的都是**封裝變化**的思想
這些模式只是一些最佳實踐而已: 起了一個名稱、描述一下解決的問題、使用的范圍和場景,碼農們在項目中還得自己去編碼實現他們。
## 解除依賴
一個訂單處理類,它會被定時調用: 查詢數據庫中訂單的處理情況, 必要時給下訂單的用戶發信。

這里就需要很多類一起協作了,尤其是OrderProcessor,它依賴于OrderService和EmailService兩個服務,它獲取依賴的方式就是通過**單例方法**
但是如果你想對這個 process 方法進行單元測試,就會發現
* OrderService會從數據庫中取到Order的信息,需要確保數據庫中有數據,而且數據庫連接有問題。如果是數據連接Container(例如Tomcat)管理數據連接的,那么沒有Tomcat很難建立數據庫連接
* 這個 EmailService 真的會對外發郵件, 你可不想對真正的用戶發測試郵件,當然你可以修改數據庫,把郵件地址改成假的,但那樣很麻煩, 并且 EmailService 會拋出一堆錯誤來,很不爽
所有的這些障礙, 最終會導致脆弱的單元測試: **速度慢, 不可重復,需要手工干預,不能獨立運行。**
一個可行的辦法就是不在方法中直接調用OrderService和EmailService的getInstance()方法,而是通過setter方法傳進來。

也就是構建一個假的 OrderService 和假的 EmailService 了。
例如 OrderService 的冒牌貨可以是 MockOrderService , 它可以返回你想要的任何 Order 對象, 而不是從數據庫取。MockEmailService 也不會真的發郵件, 而是把代碼中試圖發的郵件保存下來, 測試程序可以檢查是否正確。

那么你會發現需要把OrderService和EmailService變成接口或者抽象類,這樣才能把Mock對象傳進來。
這其實也遵循了面向對象編程的另外一個要求:**對接口編程, 而不是對實現編程**
## Spring依賴注入
上面的代碼其實就是實現了一個依賴的注入,把兩個冒牌貨注入到業務類中 (通過 set 方法), 這個注入的過程是在一個測試類中通過代碼完成的。
既然能把冒牌貨注入進去, 那毫無疑問,肯定也能把一個正經的類安插進去, 因為 setter 方法接受的是接口,而不是具體類。

用這種方式來處理對象之間的依賴, 會強迫你對接口編程, 好處顯而易見。
隨著系統復雜度的增長, 這樣的代碼會越來越多, 最后也會變得難于維護。
能不能把各個類之間的依賴關系統一維護呢?
能不能把系統做的更加靈活一點,用聲明的方式而不是用代碼的方式來描述依賴關系呢?
在 Java 世界里,如果想描述各種邏輯關系, XML 是不二之選:

只有xml還不夠,還缺少一個解析器,假設為XmlAppContext
* 解析xml,獲得各種元素
* 通過**Java反射**把各個bean的實例創建起來。com.coderising.OrderProcessor , OrderServiceImpl, EmailServiceImpl.
* 通過**Java反射**調用OrderProcessor 的兩個方法:setOrderService(....) 和 setEmailService(...) 把 orderService , emailService 實例 注入進去。
應用程序使用起來就簡單了:
~~~
XmlAppContext ctx = new XmlAppContext("c:\\bean.xml");
OrderProcessor op = (OrderProcessor) ctx.getBean("order-processor");
op.process();
~~~
pring 的處理方式和上面說的非常類似, 當然 Spring 處理了更多的細節,例如不僅僅是 setter 方法注入, 還可以構造函數注入,init 方法, destroy 方法等等, 基本思想是一致的。

既然對象的創建過程和裝配過程都是 Spring 做的, 那 Spring 在這個過程中就可以玩很多把戲了, 比如對你的業務類做點字節碼級別的增強, 搞點 AOP 什么的, 這都不在話下了。
## IoC vs DI
在好萊塢,把簡歷遞交給演藝公司后就只有回家等待。由演藝公司對整個娛樂項目完全控制,演員只能被動式的接受公司的差使, 在需要的環節中,完成自己的演出。
這和軟件開發有一定的相似性, 演員們就像一個個 Java Object, 最早的時候自己去創建自己所依賴的對象, 有了演藝公司(Spring 容器)的介入,所有的依賴關系都是演藝公司搞定的, 于是控制就翻轉了
Inversion of Control, 簡稱 IoC。
但是 IoC 這個詞不能讓人更加直觀和清晰的理解背后所代表的含義, 于是 Martin Flower 先生就創造了一個新詞 : 依賴注入 (Dependency Injection,簡稱 DI), 是不是更加貼切一點?
## AOP
AOP(Aspect Oriented Programming)也就是面向切面編程
### 問題來源
做系統設計的時候,非常重要的工作是把一個大系統分解,按照業務功能分解成一個一個低耦合、高內聚的模塊
分解后會發現有很多有趣的東西,比如下面的模塊是通用的。
* 日志
* 安全
* 性能
* 事務

這些非功能需求,但他們是多個業務模塊都需要的,最簡單的方法是把這些通用的模塊寫好,然后寫業務模塊的時候去調用就可以了。但是這樣的話,日志、性能等無關的代碼可以把真正的業務代碼淹沒了。

## 模板方法
用設計模式可以部分解決如上的問題。比如模板方法
在父類(BaseCommand)中把亂七八糟的非功能代碼都寫好,只留了一個口最長(抽象方法doBusiness)讓子類實現

子類就只需要關注業務邏輯了。
```
BaseCommand cmd = ... 獲得 PlaceOrderCommand 的實例...
cmd.execute();
```
但是這種方案的巨大缺陷在于父類會定義一切,要執行那些非功能代碼,以什么順序執行等等。
子類只能無條件接受,完全沒有反抗余地
## 裝飾者模式

讓PlaceOrderCommand能打印日志,進行性能統計
```
Command cmd = new LoggerDecorator(
new PerformanceDecorator(
new PlaceOrderCommand()));
cmd.execute();
```
如果 PaymentCommand 只需要打印日志,裝飾一次就可以了
```
Command cmd = new LoggerDecorator(
new PaymentCommand());
cmd.execute();
```
可以使用任意數量的裝飾器,而且還可以以任意次序執行,非常的靈活
## AOP
但是裝飾者有很多問題:
* 一個處理日志、性能、事務的類為什么要實現業務接口(Command)呢?
* 如果別的業務模塊,沒有實現Command接口,但是也想利用日志、性能、事務等,應該怎么辦呢?
最好把日志、安全、事務這些代碼與業務代碼隔離出來,因為他們的關注點與業務代碼的關注點完全不同。

如果把這個業務功能看成一層層面包的話, 這些日志 / 安全 / 事務 像不像一個個 “切面”(Aspect) ?
如果我們能讓這些 “切面“能和業務獨立, 并且能夠非常靈活的“織入” 到業務方法中, 那就實現了面向切面編程(AOP)!
## 實現AOP
現在我們來實現 AOP 吧, 首先我們得有一個所謂的 “切面“類(Aspect), 這應該是一個普通的 java 類 , 不用實現什么“亂七八糟” 的接口。

我們對于 com.coderising 這個包中所有類的 execute 方法, 在方法調用之前,需要執行 Transaction.beginTx() 方法, 在調用之后, 需要執行 Transaction.commitTx() 方法。
“對于 com.coderising 這個包中所有類的 execute 方法” , 用一個時髦的詞來描述就是切入點(PointCut) , 它可以是一個方法或一組方法(可以通過通配符來支持,你懂的)
” 在方法調用之前 / 之后 , 需要執行 xxx“ , 用另外一個時髦的詞來描述就是通知(Advice)

注意:現在 Transaction 這個類和業務類在源代碼層次上沒有一點關系,完全隔離了。
但是Java是一門靜態的強類型語言, 代碼一旦寫好, 編譯成 java class 以后 ,可以在運行時通過反射(Reflection)來查看類的信息, 但是想對類進行修改非常困難。
而 AOP 要求的恰恰就是在不改變業務類的源代碼(其實大部分情況下你也拿不到)的情況下, 修改業務類的方法, 進行功能的增強,**就像上面給所有的業務類增加事務支持。**
目前有如下幾種技術:
* 編譯的時候把日志、安全、事務等切面代碼與業務邏輯編譯到一起去。
* 運行的時候,業務類加載以后, 通過 Java 動態代理技術為業務類生產一個代理類, 把 “切面” 代碼放到代理類中, Java 動態代理要求業務類需要實現接口才行。
* 在運行期, 業務類加載以后, 動態的使用**字節碼**構建一個業務類的子類,將 “切面” 邏輯加入到子類當中去, CGLIB 就是這么做的。
Spring 采用的就是 (1) +(2) 的方式