用小說的形式講解Spring(1) —— 為什麼需要依賴注入
大雄的練級之路:
- 第一級:為什麼需要依賴注入
- 第二級:注入方式哪家強
- 第三級:配置方式如何選
- 第四級:Spring Boot - NoXml Web Application
- 第五級:如何給老婆解釋什麼是Restful
不斷打怪升級中...
本集概要:
- 使用依賴注入前,代碼是什麼樣子,有什麼缺點?
- 依賴注入是什麼?為什麼要使用依賴注入?
- Spring如何使用xml配置的方式進行依賴注入?
大雄是一個剛踏入社會的95後,熱愛編程的他,在畢業之後進入了一家互聯網公司,負責公司內一個電商項目的開發工作。
為了讓大雄更快的成長,公司安排了哆啦作為大雄的導師。春風得意
在哆啦的指導下,大雄很快對這個項目的代碼有了大致的了解,於是哆啦準備給大雄安排點任務。
「大雄,我們這項目現在缺少日誌列印,萬一到時上線後發現bug了,很難定位。你看看有什麼辦法可以把一些必要的信息列印到日誌文件中。」
「沒問題!」大雄爽快地答應了。大雄以前在學校時,經常上網找各種資源,於是很快就鎖定了一個叫PerfectLogger的工具。「資料很完善,很多大神都推薦它,嗯,就用它了」。
大雄看了一下PerfectLogger的官方文檔,發現裡面提供了很多種日誌列印功能,有列印到文件的,有列印到控制台的,還有列印到遠程伺服器上的,這些類都實現了一個叫ILogger的介面:
- ILogger
- FileLogger
- ConsoleLogger
- ServerLogger
- …
「哆啦說要列印到文件,那就用FileLogger吧!」
於是,大雄先在支付介面的代碼中,加入了日誌列印(本文使用的代碼,可以到 SpringNovel 下載):public class PaymentAction { private ILogger logger = new FileLogger(); public void pay(BigDecimal payValue) { logger.log("pay begin, payValue is " + payValue); // do otherthing // ... logger.log("pay end"); }}
接著,大雄又在登錄、鑒權、退款、退貨等介面,都加上和支付介面類似的日誌功能,要加的地方還真不少,大雄加了兩天兩夜,終於加完了,大功告成!想到自己第一個任務就順利完成了,大雄不禁有點小得意…
改需求了
很快公司升級了系統,大雄做的日誌功能也將第一次迎來生產環境的考驗。
兩天後,哆啦找到了大雄。 「大雄,測試那邊說,日誌文件太多了,不能都列印到本地的目錄下,要我們把日誌列印到一台日誌伺服器上,你看看改動大不大。」
「這個簡單,我只需要做個全局替換,把FileLogger都替換成ServerLogger就完事了。」
哆啦聽完,皺了皺眉頭,問道,「那要是下次公司讓我們把日誌列印到控制台,或者又突然想讓我們列印到本地文件呢,你還是繼續全局替換嗎?」大雄聽完,覺得是有點不妥……代碼如何解耦
「我看了一下你現在的代碼,每個Action中的logger都是由Action自己創造的,所以如果要修改logger的實現類,就要改很多地方。有沒有想過可以把logger對象的創建交給外部去做呢?」
大雄聽完,覺得這好像是某種自己以前學過的設計模式,「工廠模式!」大雄恍然大悟。很快,大雄對代碼做了重構:
public class PaymentAction { private ILogger logger = LoggerFactory.createLogger(); public void pay(BigDecimal payValue) { logger.log("pay begin, payValue is " + payValue); // do otherthing // ... logger.log("pay end"); }}
public class LoggerFactory { public static ILogger createLogger() { return new ServerLogger(); }}
有了這個LoggerFactory,以後要是要換日誌列印的方式,只需要修改這個工廠類就好了。
啪!一盤冷水
大雄高興地給哆啦提了代碼檢視的請求,但是,很快,一盤冷水就潑了過來,哆啦的回復是這樣的:
- 工廠類每次都new一個新對象,是不是很浪費,能不能做成單例的,甚至是做成單例和多例是可以配置;
- 如果有這種需求:支付信息比較多而且比較敏感,日誌要列印到遠程伺服器,其他信息都列印到本地,怎麼實現;
- …
大雄看完,頓時感覺自己2young2simple了,準備今晚留下來好好加班……
Spring! Spring!
正當大雄鬱悶著的時候,屏幕右下角哆啦的頭像突然蹦了出來。
「其實這種將對象交給外部去創建的機制,不僅僅是工廠模式,它還被稱為控制反轉(Inverse of Control),它還有另一個更常用的名稱,依賴注入(Dependency Injection)。這種機制,業界已經有很成熟的實現了,它就是Spring Framework,晚上早點回去,有空可以看看Spring,明天再過來改。」
那天晚上,大雄在網上找了下Spring的資料,他似乎發現了另一個世界…
使用Spring改造代碼
第二天大雄早早地就來到了公司,他迫不及待地想把原來的代碼使用Spring的方式改造一遍。
在使用gradle引入了必要的jar包後,大雄對原來的PaymentAction做了修改,不再在類內部創建logger對象,同時給PaymentAction添加了一個構造函數,方便Spring進行注入:
public class PaymentAction { private ILogger logger; public PaymentAction(ILogger logger) { super(); this.logger = logger; } public void pay(BigDecimal payValue) { logger.log("pay begin, payValue is " + payValue); // do otherthing // ... logger.log("pay end"); }}
接著創建了一個以<beans>為根節點的xml文件,引入必要的XSD文件,並且配置了兩個bean對象,使用了<constructor-arg>標籤,指定了ServerLogger作為PaymentAction構造函數的入參:
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="paymentAction" class="com.springnovel.paymentwithspringxml.PaymentAction"> <constructor-arg ref="serverLogger" /> </bean> <bean id="serverLogger" class="com.springnovel.perfectlogger.ServerLogger" /></beans>
差不多了,現在測試一下:
ApplicationContext context = new ClassPathXmlApplicationContext("payment.xml");PaymentAction paymentAction = (PaymentAction) context.getBean("paymentAction");paymentAction.pay(new BigDecimal(2));
Output:
ServerLogger: pay begin, payValue is 2ServerLogger: pay end
很棒!ServerLogger對象已經被注入到PaymentAction中了。
就這樣,大雄很快就使用Spring實現了自己昨天寫的工廠類的功能,修復了之前代碼耦合性過高的問題。學以致用
這邊大雄正高興呢,突然發現旁邊的測試妹妹靜香眉頭緊鎖,於是過去關心了一番。
原來靜香正在測試一個刪除訂單的功能,但是現在測試用的資料庫突然掛了,導致靜香不能進行測試。
大雄看了看訂單刪除介面的代碼:
public class OrderAction { public void deleteOrder(String orderId) { // 鑒權 // 此處略去一萬字... IOrderDao orderDao = new OrderDao(); orderDao.deleteOrder(orderId); }}
「這又是一個代碼耦合過緊的問題!」大雄脫口而出。
「這個刪除訂單的介面有幾個邏輯:鑒權、刪除、回滾等,但是這裡把刪除的資料庫操作和OrderDao綁定死了,這樣就要求測試這個介面時必須要連接到資料庫中,但是作為單元測試,我們只是想測刪除訂單的邏輯是否合理,而訂單是否真的刪除,應該屬於另一個單元測試了」 大雄很是激動,嘴裡唾沫橫飛。「我來幫你改一下。」「控制反轉」後的OrderAction:
public class OrderAction { private IOrderDao orderDao; public OrderAction(IOrderDao orderDao) { super(); this.orderDao = orderDao; } public void deleteOrder(String orderId) { // 鑒權 // 此處略去一萬字... orderDao.deleteOrder(orderId); }}
改造後的OrderAction,不再和OrderDao這個實現類耦合在一起,做單元測試的時候,可以寫一個「Mock」測試,就像這樣:
public void mockDeleteOrderTest() { IOrderDao orderDao = new MockOrderDao(); OrderAction orderAction = new OrderAction(orderDao); orderAction.deleteOrder("1234567@#%^$");}
而這個MockOrderDao是不需要連接資料庫的,因此即便資料庫掛了,也同樣可以進行單元測試。
一旁的哆啦一直在靜靜地看著,然後拍了拍大雄的肩膀,「晚上請你和靜香去擼串啊」,說完,鬼魅的朝大雄挑了挑眉毛。
大雄的筆記
這兩天大雄可謂是收穫頗豐,見識了依賴注入的必要性,還了解了如何使用Spring實現依賴注入。擼完串後,回到家,大雄在記事本上寫下了心得:
- 為什麼要使用依賴注入
- 傳統的代碼,每個對象負責管理與自己需要依賴的對象,導致如果需要切換依賴對象的實現類時,需要修改多處地方。同時,過度耦合也使得對象難以進行單元測試。
- 依賴注入把對象的創造交給外部去管理,很好的解決了代碼緊耦合(tight couple)的問題,是一種讓代碼實現松耦合(loose couple)的機制。
- 松耦合讓代碼更具靈活性,能更好地應對需求變動,以及方便單元測試。
- 為什麼要使用Spring
- 使用Spring框架主要是為了簡化Java開發(大多數框架都是為了簡化開發),它幫我們封裝好了很多完善的功能,而且Spring的生態圈也非常龐大。
- 基於XML的配置是Spring提供的最原始的依賴注入配置方式,從Spring誕生之時就有了,功能也是最完善的(但是貌似有更好的配置方法,明天看看!)。
未完待續
寫完筆記,大雄繼續看之前只看了一小部分的Spring指南,他發現除了構造器注入,還有一種注入叫set注入;除了xml配置,還可以使用註解、甚至是Java進行配置。Spring真是強大啊,給了用戶那麼多選擇,可具體什麼情況下該使用哪種注入方式和哪種配置方式呢,大雄陷入了沉思……
參考內容
- 《Spring in Action》
- tutorialspoint - Spring Tutorial
- javatpoint - Spring Tutorial
- Why does one use dependency injection?
- Dependency Injection and Unit Testing
推薦閱讀:
※周末薦書 | 《Spring實戰》(第4版)
※實體類的欄位的驗證應該寫在service層嗎?
※如何學好ssh框架,spring學起來怎麼這麼難呢?
※MyBatis不是完整的ORM框架?